Pagination and filtering

Cursors, filters, field selection, languages and expansion in the Sway Public API.

Every list endpoint shares one envelope, one cursor pagination and the same conventions for filters. This page covers them once; the endpoint reference says which parameters each endpoint takes.


The list envelope

{
  "data": [ { "id": 4521, "title": "Insomnia Night: Autumn Opening" } ],
  "pagination": {
    "next_cursor": "eyJzb3J0X2tleSI6IjIwMjYtMTAtMDMiLCJpZCI6NDUyMX0",
    "has_more": true,
    "limit": 25
  }
}

A single resource (/v1/me, /v1/events/{id}, a POST answer...) comes back as the object itself, with no data wrapper and no pagination. A few small fixed lists (ticket tiers, the days of an event) are wrapped in { "data": [...] } without pagination.


Cursor pagination

Cursors are opaque: do not parse or build them.

  1. Ask for the first page without a cursor.
  2. While has_more is true, ask for the next page with ?cursor={next_cursor}.
  3. Stop when has_more is false.
curl "https://www.sway.events/api/v1/events?limit=50" \
  -H "Authorization: Bearer sway_sk_YOUR_KEY"

curl "https://www.sway.events/api/v1/events?limit=50&cursor=eyJzb3J0X2tleSI6..." \
  -H "Authorization: Bearer sway_sk_YOUR_KEY"
ParameterDefaultRange
limit251 to 100
cursornonethe next_cursor of the previous page

A malformed or stale cursor answers 400 invalid_cursor: start again from the first page. There are no page numbers and no totals, and cursors stay correct when data changes between two requests.

A page of more than 25 items costs more of your work budget (one unit per 25): ask for what you display.


Filtering events

GET /v1/events and GET /v1/events/search take these filters, all optional and combinable:

ParameterValuesMeaning
statusupcoming, past, allDefault upcoming: events that have not ended. past: events that have.
from, toISO 8601 date or date-timeEvents starting at or after from, at or before to
promoter_id, artist_id, venue_idan idEvents of that page (it must be in your crew's reach)
genreup to 10 genre ids, comma-separatedEvents with any of these genres (GET /v1/genres lists them)
citytextEvents in that city
countryISO 3166-1 alpha-2 (BE)Events in that country
nearlat,lngEvents around a point
radius_km1 to 500, default 25The radius around near; only with near
has_ticketstrue, falseEvents with a public ticket tier on sale right now (in its sale window, not sold out), or without one
updated_sinceISO 8601 date-timeEvents changed since then: the way to sync incrementally
idsup to 50 ids, comma-separatedExactly these events
q2 to 100 charactersSearch: only on /v1/events/search
curl "https://www.sway.events/api/v1/events?country=BE&from=2026-10-01&to=2026-10-31&genre=12,31" \
  -H "Authorization: Bearer sway_sk_YOUR_KEY"

The other feeds (/v1/promoters/{id}/events, /v1/artists/{id}/events, /v1/venues/{id}/events) take status, and the promoter and venue feeds from and to as well. Upcoming events come soonest first, past events latest first; there is no sort parameter. An invalid value answers 400 validation_error, naming the parameter.

Batch lookups

ids= on /v1/events, /v1/artists, /v1/promoters and /v1/venues fetches up to 50 known ids in one request, instead of 50 detail requests. It costs one work unit per started block of ten ids.


Choosing fields

fields= keeps only the fields you name, on the detail and list endpoints that support it. id is always kept, and one level of nesting is allowed:

curl "https://www.sway.events/api/v1/events/4521?fields=title,starts_at,venue.name,lineup.name" \
  -H "Authorization: Bearer sway_sk_YOUR_KEY"
{
  "id": 4521,
  "title": "Insomnia Night: Autumn Opening",
  "starts_at": "2026-10-03T20:00:00+00:00",
  "venue": { "id": 77, "name": "Fuse" },
  "lineup": [ { "id": 501, "name": "Lucia Vega" } ]
}

On a list, fields applies to each item in data. Up to 40 names; an unknown name answers 400 validation_error. Smaller answers travel faster and count less against your daily data allowance.


Languages

locale= sets the language of the text the API writes itself: labels, calendar and structured-data text, the Stripe checkout page. It takes en, fr, de, es, it, nl or uk (fr-BE reads as fr). What organisers and artists wrote comes back as they wrote it.


Expansion

Two endpoints embed related resources on request with ?expand= (one level deep):

  • GET /v1/events/{id}: the venue and the line-up are always there; expand=ticket_tiers adds the tiers.
  • GET /v1/promoters/{id}/events: events carry a light venue ({ id, name }) and no line-up; expand=venue gives the full venue, expand=lineup and expand=ticket_tiers add those arrays.
ValueEmbeds
venueThe venue, public fields
lineupThe line-up with each artist, stage and set times
ticket_tiersWhat GET /v1/events/{id}/ticket-tiers returns

Each value costs one more work unit on an uncached request, and saves a request per event. Other endpoints answer 400 validation_error to expand.


HEAD and conditional requests

Every detail endpoint answers HEAD with the headers of the GET and no body. Every cacheable read carries an ETag: send it back as If-None-Match and an unchanged resource answers 304 Not Modified, free. See Caching.


Why a resource answers 404

The API tells managed resources (your crew's pages and their events) from public ones reached through your events (a guest artist, a venue you do not manage); see the overview.

  • Public resources appear only inside the resources that reference them, with "managed": false and public fields.
  • Fetching one directly (GET /v1/artists/{id} for a guest artist) answers 404.
  • Anything outside your crew's reach answers 404, never 403.
  • Your roster artists are returned even while unpublished. Every other resource answers 404 (or is left out of line-ups) until it is published.

Treat every 404 the same way: the resource does not exist for your key.