Endpoint Reference

Complete reference of all Sway Public API v1 endpoints.

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.

#EndpointScope
1GET /v1/meβ€”
2GET /v1/crewread:profile
3GET /v1/artistsread:artists
4GET /v1/artists/{id}read:artists
5GET /v1/artists/{id}/eventsread:artists + read:events
6GET /v1/promotersread:promoters
7GET /v1/promoters/{id}read:promoters
8GET /v1/promoters/{id}/eventsread:promoters + read:events
9GET /v1/eventsread:events
10GET /v1/events/{id}read:events
11GET /v1/events/{id}/ticket-tiersread:events
12GET /v1/venues/{id}read:venues
13POST /v1/booking-requestswrite:bookings
14POST /v1/checkout-sessionswrite: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:

FieldTypeRequiredNotes
artist_idsnumber[]yesNon-empty, at most 50 ids, each a managed roster artist of your crew
contact_emailstringyesValid email, ≀ 320 chars
contact_namestringno≀ 200 chars
contact_phonestringno≀ 200 chars
event_namestringno≀ 200 chars
event_datestringnoISO date YYYY-MM-DD
venuestringno≀ 200 chars
citystringno≀ 200 chars
fee_offerstringnoFree-form, ≀ 200 chars
capacitystringnoFree-form, ≀ 200 chars
ticket_pricestringnoFree-form, ≀ 200 chars
organizationstringno≀ 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:

FieldTypeRequiredNotes
event_idnumberyesEvent to sell for β€” must be published and in your crew's graph
line_itemsarrayyesNon-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_emailstringyesValid email, ≀ 320 chars β€” the tickets are delivered here
idempotency_keystringnoA UUID per buy-click; a retry with the same key returns the same session instead of double-charging
discount_idstringnoA coupon id for the event; the discount is re-validated and its Stripe coupon applied server-side
access_codestringnoA presale / access code that unlocks a hidden tier
referencestringnoFree-form ≀ 64 chars of [A-Za-z0-9._:-], echoed to analytics metadata (never trusted for fulfilment)
marketing_consentbooleannoAdvisory 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_…" }
codeHTTPWhen
missing_scope403Key lacks write:checkout (or write:checkout:free for a free order)
not_found404Event not published or outside your crew's graph
validation_error422Bad body, unknown / unavailable tier, quantity over max_per_order, payee not managed by your crew, invalid coupon / code, or out of stock
conflict409The promoter's payment account is not ready yet
rate_limited429Per-crew / per-email / per-IP limit β€” check Retry-After
upstream_error502The payment provider could not create the session
Free (€0) orders. A cart whose total is €0 β€” a 100%-off coupon, or a genuinely free tier β€” is a distinct, higher-trust capability: the key must additionally hold the 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.