Pagination and filtering
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.
- Ask for the first page without a
cursor. - While
has_moreistrue, ask for the next page with?cursor={next_cursor}. - Stop when
has_moreisfalse.
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"
| Parameter | Default | Range |
|---|---|---|
limit | 25 | 1 to 100 |
cursor | none | the 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:
| Parameter | Values | Meaning |
|---|---|---|
status | upcoming, past, all | Default upcoming: events that have not ended. past: events that have. |
from, to | ISO 8601 date or date-time | Events starting at or after from, at or before to |
promoter_id, artist_id, venue_id | an id | Events of that page (it must be in your crew's reach) |
genre | up to 10 genre ids, comma-separated | Events with any of these genres (GET /v1/genres lists them) |
city | text | Events in that city |
country | ISO 3166-1 alpha-2 (BE) | Events in that country |
near | lat,lng | Events around a point |
radius_km | 1 to 500, default 25 | The radius around near; only with near |
has_tickets | true, false | Events with a public ticket tier on sale right now (in its sale window, not sold out), or without one |
updated_since | ISO 8601 date-time | Events changed since then: the way to sync incrementally |
ids | up to 50 ids, comma-separated | Exactly these events |
q | 2 to 100 characters | Search: 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_tiersadds the tiers.GET /v1/promoters/{id}/events: events carry a light venue ({ id, name }) and no line-up;expand=venuegives the full venue,expand=lineupandexpand=ticket_tiersadd those arrays.
| Value | Embeds |
|---|---|
venue | The venue, public fields |
lineup | The line-up with each artist, stage and set times |
ticket_tiers | What 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": falseand 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.