Endpoint Reference
All endpoints live under https://www.sway.events/api/v1 and are scoped to the crew that owns the API key. All are read-only except POST /v1/booking-requests and POST /v1/checkout-sessions.
| # | Endpoint | Scope |
|---|---|---|
| 1 | GET /v1/me | β |
| 2 | GET /v1/crew | read:profile |
| 3 | GET /v1/artists | read:artists |
| 4 | GET /v1/artists/{id} | read:artists |
| 5 | GET /v1/artists/{id}/events | read:artists + read:events |
| 6 | GET /v1/promoters | read:promoters |
| 7 | GET /v1/promoters/{id} | read:promoters |
| 8 | GET /v1/promoters/{id}/events | read:promoters + read:events |
| 9 | GET /v1/events | read:events |
| 10 | GET /v1/events/{id} | read:events |
| 11 | GET /v1/events/{id}/ticket-tiers | read:events |
| 12 | GET /v1/venues/{id} | read:venues |
| 13 | POST /v1/booking-requests | write:bookings |
| 14 | POST /v1/checkout-sessions | write:checkout |
List endpoints (/v1/artists, /v1/promoters, /v1/events, and the two event sub-feeds) use the standard { data, pagination } envelope and cursor pagination β see Pagination & filtering. Every single-resource endpoint, plus POST /v1/booking-requests and POST /v1/checkout-sessions, returns the resource object directly β no data wrapper.
Key & crew
GET /v1/me
Scope: none. Key introspection β the "is my key alive" probe. Returns the identified key (id, scopes, rate limits) and its crew.
{
"key": {
"id": "3f9a1c02-7b45-4c8e-9d21-0a1b2c3d4e5f",
"scopes": ["read:profile", "read:artists", "read:promoters", "read:events", "read:venues"],
"rate_limit": { "per_minute": 120, "per_day": 20000 }
},
"crew": { "id": "a2628d53-4f21-4c1e-9b7d-1a2b3c4d5e6f", "name": "Insomnia", "slug": "insomnia" }
}
GET /v1/crew
Scope: read:profile. The crew's profile plus a summary of its managed pages, nested under pages as arrays of ids.
{
"id": "a2628d53-4f21-4c1e-9b7d-1a2b3c4d5e6f",
"name": "Insomnia",
"slug": "insomnia",
"pages": {
"artists": [245, 512],
"promoters": [697]
}
}
Artists
GET /v1/artists
Scope: read:artists. The roster β all artists managed by your crew, as full representations. Sorted by name ascending. Roster artists are returned even while unpublished.
Query parameters: cursor, limit (1β100, default 25).
{
"data": [
{
"id": 245,
"name": "KROMATIEK",
"image_url": "https://assets.sway.events/artists/245/cover.webp",
"description": "Ghent-based techno artistβ¦",
"genres": ["Techno", "Hard Groove"],
"links": {
"spotify": "https://open.spotify.com/artist/β¦",
"instagram": "https://instagram.com/kromatiek"
},
"is_verified": true,
"managed": true,
"contact_emails": [{ "type": "booking", "value": "[email protected]" }]
}
],
"pagination": { "next_cursor": "eyJrIjoiS1JPTUFUSUVLIiwiaWQiOjI0NX0", "has_more": true, "limit": 25 }
}
Contact emails live in the dedicated contact_emails array (managed tier only); links never contains an email key. Foreign (public-tier) artists have no contact_emails.
GET /v1/artists/{id}
Scope: read:artists. Full representation of one managed artist, returned directly (no envelope) β same shape as a roster entry above. Foreign artists (lineup guests) return 404 β they only appear inline in event lineups.
GET /v1/artists/{id}/events
Scope: read:artists and read:events (both required). The artist's gig history and calendar: published events where the artist is a confirmed lineup member. Each item is an event summary carrying a light venue reference ({ id, name } or null).
Query parameters: status (upcoming | past | all, default upcoming), cursor, limit. This feed does not accept from/to or expand.
Promoters
GET /v1/promoters
Scope: read:promoters. All promoters managed by your crew, full representations. Same envelope and parameters as /v1/artists.
GET /v1/promoters/{id}
Scope: read:promoters. Full representation of one managed, published promoter, returned directly (no envelope). Managed only β otherwise 404.
GET /v1/promoters/{id}/events
Scope: read:promoters and read:events (both required). The events feed of one promoter β the main feed for a partner website. Each event carries a light venue reference ({ id, name } or null) by default; use expand to embed more.
Query parameters: status (upcoming | past | all, default upcoming), from / to (ISO 8601), expand (any comma-separated subset of venue,lineup,ticket_tiers), cursor, limit. expand=venue upgrades the venue reference to the full public-tier venue.
Events
GET /v1/events
Scope: read:events. Union feed: events of your managed promoters plus published events featuring your roster artists. Each item is an event summary with a light venue reference ({ id, name } or null).
Query parameters: promoter_id, artist_id, status (default upcoming), from / to (ISO 8601), cursor, limit. This feed does not support expand β passing it returns 400 validation_error. Use /v1/events/{id} or /v1/promoters/{id}/events for expanded data.
GET /v1/events/{id}
Scope: read:events. One published event in your crew's graph, returned directly (no envelope). venue (full public tier) and lineup are always embedded; genres are always present. Add ?expand=ticket_tiers to also include the ticket tiers.
Query parameters: expand β accepts a comma-separated subset of venue,lineup,ticket_tiers (venue and lineup are already embedded, so only ticket_tiers adds anything).
When expand=ticket_tiers is present, the response also includes a ticket_fee object β { "percent": number, "fixed": number } β the effective service-fee rates for this event's payee (the percent applies to the order subtotal, fixed is added once per order, in EUR). Use it to display a total that matches the Stripe checkout: total = subtotal + subtotal Γ percent + fixed.
Lineup entries carry the artist DTO plus stage, start_time, end_time and an artist_id, and include the managed flag: your own artists come with the full representation (incl. contact_emails), foreign guests appear at the public tier only. Custom (non-linked) acts appear as placeholder entries with artist_id: null.
{
"id": 4521,
"title": "Overload",
"description": "β¦",
"starts_at": "2026-09-12T22:00:00Z",
"ends_at": "2026-09-13T06:00:00Z",
"timezone": "Europe/Brussels",
"image_url": "https://assets.sway.events/events/4521/cover.webp",
"type": "club",
"page_url": "https://www.sway.events/event/4521",
"venue": {
"id": 88,
"name": "Jungle Bar",
"image_url": "β¦",
"description": "β¦",
"location": { "address": "Nieuwewandeling 2, Ghent" },
"genres": [],
"links": null
},
"lineup": [
{ "stage": "Main", "start_time": "23:00", "end_time": "01:00", "artist_id": 245, "id": 245, "name": "KROMATIEK", "image_url": "β¦", "description": "β¦", "genres": ["Techno"], "links": { "spotify": "β¦" }, "is_verified": true, "managed": true, "contact_emails": [{ "type": "booking", "value": "[email protected]" }] },
{ "stage": null, "start_time": null, "end_time": null, "artist_id": 1042, "id": 1042, "name": "CE$AR", "image_url": "β¦", "description": null, "genres": ["House"], "links": null, "is_verified": false, "managed": false }
],
"genres": ["Techno"]
}
GET /v1/events/{id}/ticket-tiers
Scope: read:events. The event's ticket tiers: name, description, price, status, sale windows and a checkout_url deep link to the Sway checkout. No stock counts, no orders, no buyer data. This is a small, fixed list β it is wrapped in { "data": [ β¦ ] } but has no pagination object and no cursor.
{
"data": [
{
"id": "301",
"name": "Early Bird",
"description": "Limited first release",
"price": 15.0,
"currency": "EUR",
"status": "sold_out",
"sale_start": "2026-06-01T10:00:00Z",
"sale_end": "2026-07-01T10:00:00Z",
"max_per_order": 4,
"checkout_url": "https://www.sway.events/event/4521?ref=api"
},
{
"id": "302",
"name": "Regular",
"description": null,
"price": 20.0,
"currency": "EUR",
"status": "on_sale",
"sale_start": "2026-07-01T10:00:00Z",
"sale_end": "2026-09-12T22:00:00Z",
"max_per_order": 6,
"checkout_url": "https://www.sway.events/event/4521?ref=api"
}
]
}
status is derived server-side and is one of on_sale, scheduled, sold_out, off_sale. currency defaults to EUR.
Venues
GET /v1/venues/{id}
Scope: read:venues. Public tier only, returned directly (no envelope): id, name, image_url, description, location (object β address plus any geo data, or null), genres, links (null when empty). Venues are reachable only when they host a published event in your crew's graph β otherwise 404.
{
"id": 88,
"name": "Jungle Bar",
"image_url": "https://assets.sway.events/venues/88/cover.webp",
"description": "β¦",
"location": { "address": "Nieuwewandeling 2, Ghent" },
"genres": [],
"links": null
}
Booking requests
POST /v1/booking-requests
Scope: write:bookings (opt-in at key creation). Submits a booking request from a partner site into the crew's booking pipeline and triggers a notification. This endpoint is rate-limited tighter than the read endpoints (10 requests / minute, per IP).
Request body β a flat JSON object:
| Field | Type | Required | Notes |
|---|---|---|---|
artist_ids | number[] | yes | Non-empty, at most 50 ids, each a managed roster artist of your crew |
contact_email | string | yes | Valid email, β€ 320 chars |
contact_name | string | no | β€ 200 chars |
contact_phone | string | no | β€ 200 chars |
event_name | string | no | β€ 200 chars |
event_date | string | no | ISO date YYYY-MM-DD |
venue | string | no | β€ 200 chars |
city | string | no | β€ 200 chars |
fee_offer | string | no | Free-form, β€ 200 chars |
capacity | string | no | Free-form, β€ 200 chars |
ticket_price | string | no | Free-form, β€ 200 chars |
organization | string | no | β€ 200 chars |
{
"artist_ids": [245],
"contact_email": "[email protected]",
"contact_name": "Jane Doe",
"contact_phone": "+32 470 00 00 00",
"event_name": "Warehouse Night",
"event_date": "2026-11-21",
"venue": "Fuse",
"city": "Brussels",
"fee_offer": "800 EUR",
"capacity": "1200",
"ticket_price": "20 EUR",
"organization": "Nightlife BV"
}
Response 201 Created β returned directly, no envelope:
{
"id": "9f2c1a7e-4d3b-4e21-8c7a-1b2c3d4e5f60",
"status": "new",
"created_at": "2026-07-03T14:00:00Z"
}
Invalid bodies return 422 validation_error (with an errors array). Any artist_ids that are not part of your crew's roster are rejected with 422 and listed under errors[].invalid_ids.
Checkout
POST /v1/checkout-sessions
Scope: write:checkout (opt-in at key creation). Creates a Stripe hosted-checkout session for a public storefront (e.g. a promoter's own website) and returns its url. Your site redirects the buyer to that URL; after paying or cancelling, the buyer returns to Sway's own /success and /cancel pages β never your site. Fulfilment (order, tickets, confirmation email) is handled entirely by Sway.
This is a lower-trust surface: everything money- and stock-authoritative is resolved server-side and can never be supplied, overridden or hinted by the caller β the price, currency, the payee's Stripe account, the service fee and the stock reservation. Your key's crew must manage the promoter that receives the funds for the event, otherwise 422. Rate-limited tighter than the read endpoints (a per-crew daily cap and, when buyer_email is present, a per-email cap).
Request body β a JSON object:
| Field | Type | Required | Notes |
|---|---|---|---|
event_id | number | yes | Event to sell for β must be published and in your crew's graph |
line_items | array | yes | Non-empty, β€ 20 entries of { "tier_id": string, "quantity": number }. tier_id is the tier id from GET /v1/events/{id}/ticket-tiers; quantity 1β50 |
buyer_email | string | yes | Valid email, β€ 320 chars β the tickets are delivered here |
idempotency_key | string | no | A UUID per buy-click; a retry with the same key returns the same session instead of double-charging |
discount_id | string | no | A coupon id for the event; the discount is re-validated and its Stripe coupon applied server-side |
access_code | string | no | A presale / access code that unlocks a hidden tier |
reference | string | no | Free-form β€ 64 chars of [A-Za-z0-9._:-], echoed to analytics metadata (never trusted for fulfilment) |
marketing_consent | boolean | no | Advisory only |
Fields rejected with 422 if supplied β resolved server-side, must never be sent: amount / price / unit_amount, currency, any payee / stripe_account, application_fee_amount / fee, success_url / cancel_url, coupon / coupon_code, payment_method_types, user_id.
{
"event_id": 4521,
"line_items": [{ "tier_id": "302", "quantity": 2 }],
"buyer_email": "[email protected]",
"idempotency_key": "b1e2c3d4-5f60-4a1b-9c2d-3e4f5a6b7c8d"
}
Response 200 OK β returned directly, no envelope. Redirect the buyer to url:
{ "url": "https://checkout.stripe.com/c/pay/cs_live_β¦" }
code | HTTP | When |
|---|---|---|
missing_scope | 403 | Key lacks write:checkout (or write:checkout:free for a free order) |
not_found | 404 | Event not published or outside your crew's graph |
validation_error | 422 | Bad body, unknown / unavailable tier, quantity over max_per_order, payee not managed by your crew, invalid coupon / code, or out of stock |
conflict | 409 | The promoter's payment account is not ready yet |
rate_limited | 429 | Per-crew / per-email / per-IP limit β check Retry-After |
upstream_error | 502 | The payment provider could not create the session |
write:checkout:free scope. The response is still a { "url": β¦ }, but it points to Sway's own /success page (no Stripe redirect). Free and paid tickets cannot be combined in one order.