Riferimento degli endpoint

Ogni endpoint dell'API pubblica di Sway v1, raggruppato per risorsa, con i suoi scope, parametri e la forma della risposta.

Tutti gli endpoint stanno sotto https://www.sway.events/api/v1 e sono limitati alla crew proprietaria della chiave API, inviata come token bearer:

curl https://www.sway.events/api/v1/artists \
  -H "Authorization: Bearer sway_sk_YOUR_KEY"

Gli endpoint di elenco restituiscono la busta standard { data, pagination } e la paginazione a cursore; ogni endpoint di una singola risorsa restituisce l'oggetto risorsa direttamente, senza involucro data. Vedi Paginazione e filtri per cursori, filtri e il meccanismo expand.

Ogni risposta 4xx o 5xx è un problema RFC 9457 (application/problem+json) con un campo code stabile. Vedi Autenticazione per il ciclo di vita della chiave, gli scope, i limiti di richieste e la tabella completa dei codici di errore.

Questa pagina documenta gli 80 endpoint che lavorano sui dati propri di una crew. Il server MCP e i webhook sono meccanismi di distribuzione separati sugli stessi dati e hanno pagine proprie; vedi la sezione di chiusura.


Chiave e crew

GET /v1/me

Introspezione della chiave: la prova "la mia chiave è ancora viva?". Restituisce la chiave identificata (id, tipo, scope, rate limit e limiti di lavoro, siti web registrati) e la sua crew.

Nessuno scope richiesto. Chiavi pubblicabili: no. Mai in cache (la risposta è specifica della chiave e deve essere sempre aggiornata).

{
  "key": {
    "id": "3f9a1c02-7b45-4c8e-9d21-0a1b2c3d4e5f",
    "type": "secret",
    "scopes": ["read:profile", "read:artists", "read:promoters", "read:events", "read:venues", "read:content"],
    "rate_limit": { "per_minute": 120, "per_day": 20000 },
    "limits": {
      "requests_per_minute": 2400,
      "crew_work_per_minute": 600,
      "crew_work_per_day": 120000,
      "bytes_per_day": 5368709120
    },
    "websites": []
  },
  "crew": { "id": "a2628d53-4f21-4c1e-9b7d-1a2b3c4d5e6f", "name": "Insomnia", "slug": "insomnia" }
}

rate_limit è il budget di lavoro proprio della chiave (quello che spende un cache miss); limits sono i tetti più ampi descritti nella pagina Autenticazione. Nessun errore specifico dell'endpoint oltre a quelli standard di autenticazione.

GET /v1/usage

Quanto ha usato la chiave chiamante dei suoi limiti in questo momento: lavoro in questo minuto e oggi, il tetto di richieste, i byte serviti oggi, e se la chiave è congelata dopo un pattern insolito di richieste.

Nessuno scope richiesto. Chiavi pubblicabili: no. Mai in cache; leggerla non spende mai lavoro (costo 0).

{
  "key": { "id": "3f9a1c02-7b45-4c8e-9d21-0a1b2c3d4e5f", "type": "secret" },
  "plan": "studio",
  "work": {
    "minute": { "limit": 120, "used": 12, "remaining": 108, "reset_seconds": 41 },
    "day": { "limit": 20000, "used": 340, "remaining": 19660, "reset_seconds": 61200 }
  },
  "crew_work": {
    "minute": { "limit": 600, "used": 12, "remaining": 588, "reset_seconds": 41 },
    "day": { "limit": 120000, "used": 340, "remaining": 119660, "reset_seconds": 61200 }
  },
  "requests": {
    "minute": { "limit": 2400, "used": 15, "remaining": 2385, "reset_seconds": 41 }
  },
  "bytes": { "today": 1048576, "limit": 5368709120 },
  "frozen": false
}

work è il budget proprio della chiave; crew_work è lo stesso budget condiviso tra tutte le chiavi della crew. Nessun errore specifico dell'endpoint.

GET /v1/audit-log

Le ultime chiamate fatte dalla chiave, più recenti prima: ora, metodo, percorso, stato, indirizzo chiamante, esito della cache e durata. Conservate sette giorni, al massimo 1000 voci.

Nessuno scope richiesto. Chiavi pubblicabili: no. Mai in cache (privacy: no-store).

ParametroValoriPredefinitoSignificato
limitintero, da 1 a 1000100Quante voci restituire
{
  "data": [
    {
      "at": "2026-09-26T12:00:03.000Z",
      "method": "GET",
      "path": "/api/v1/events/4521",
      "status": 200,
      "ip": "203.0.113.7",
      "request_id": "9f2c1a7e-4d3b-4e21-8c7a-1b2c3d4e5f60",
      "cache": "hit",
      "ms": 4
    }
  ],
  "count": 1
}

ip è l'indirizzo osservato della chiave chiamante stessa. cache è hit, miss, stale o null quando l'endpoint non è cacheable.

Errori: validation_error (400) quando limit è fuori intervallo.

POST /v1/keys/rotate

Sostituisce la chiave chiamante con una nuova: stesso nome, tipo, scope, limiti e siti web. La vecchia chiave continua a funzionare per un periodo di grazia (così un partner può ridistribuire senza un'interruzione), poi scade da sola.

Scope write:keys (da attivare). Chiavi pubblicabili: no. Mai in cache. Non accetta Idempotency-Key: la risposta porta il nuovo segreto, e Sway non memorizza mai una risposta che porta un segreto, quindi non c'è niente da ripetere a un nuovo tentativo.

CampoTipoObbligatorioNote
grace_hoursnumbernoNumero intero, da 1 a 168, predefinito 24: per quanto tempo la vecchia chiave continua a rispondere
{
  "key": "sway_sk_ab12...",
  "record": {
    "id": "7c5e2b41-...",
    "name": "Storefront key",
    "key_prefix": "sway_sk_ab12",
    "last4": "wxyz",
    "key_type": "secret",
    "scopes": ["read:artists", "read:events"],
    "storefront_origins": [],
    "created_at": "2026-09-26T12:00:00.000Z",
    "expires_at": null,
    "rotated_from_id": "3f9a1c02-7b45-4c8e-9d21-0a1b2c3d4e5f"
  },
  "previous": { "id": "3f9a1c02-7b45-4c8e-9d21-0a1b2c3d4e5f", "expires_at": "2026-09-27T12:00:00.000Z" }
}

Il nuovo segreto completo (key) viene mostrato una volta sola, in questa risposta, esattamente come alla creazione: memorizzalo subito.

Errori: validation_error (400, corpo o grace_hours non validi), invalid_key (401, la chiave era già sparita), conflict (409, questa chiave ha già una successora: ruota invece la chiave che l'ha sostituita), rate_limited (429, una chiave può ruotare una volta all'ora).

GET /v1/crew

Il profilo della crew più gli id delle pagine che gestisce.

Scope read:profile. Chiavi pubblicabili: sì. In cache 300 s.

{
  "id": "a2628d53-4f21-4c1e-9b7d-1a2b3c4d5e6f",
  "name": "Insomnia",
  "slug": "insomnia",
  "pages": { "artists": [245, 512], "promoters": [697] }
}

Nessun errore specifico dell'endpoint.

GET /v1/crew/pages

Le pagine che la crew gestisce (artisti, promoter, locali), ciascuna con il suo nome e se è pubblicata. A differenza di ogni altro endpoint, qui viene nominata anche una pagina in bozza: è l'elenco proprio della crew.

Scope read:profile. Chiavi pubblicabili: no. In cache 300 s.

{
  "data": [
    { "type": "artist", "id": 245, "name": "KROMATIEK", "image_url": "https://assets.sway.events/artists/245/cover.webp", "is_published": true, "page_url": "https://www.sway.events/artist/245" },
    { "type": "promoter", "id": 697, "name": "Insomnia Nights", "image_url": null, "is_published": false, "page_url": null }
  ]
}

image_url e page_url sono null finché la pagina resta non pubblicata. Nessun errore specifico dell'endpoint.

GET /v1/genres

La tassonomia dei generi: id e nomi stabili, ordinati per nome. Questi id sono ciò che accettano i filtri genre= di GET /v1/artists, GET /v1/events e GET /v1/events/search.

Nessuno scope richiesto. Chiavi pubblicabili: sì. In cache 3600 s.

{
  "data": [
    { "id": 12, "name": "Hard Groove" },
    { "id": 45, "name": "Techno" }
  ]
}

Nessun errore specifico dell'endpoint.


Scoperta

Questi quattro endpoint non richiedono nessuna chiave API: descrivono l'API stessa. Ognuno è limitato a 60 richieste al minuto per indirizzo chiamante, oltre le quali risponde 429 rate_limited.

GET /v1/openapi.json

Questa API come documento OpenAPI 3.1, generato dallo stesso elenco di endpoint su cui gira l'API, quindi non descrive mai un endpoint che non esiste né ne omette uno che esiste.

Nessuna chiave richiesta. In cache 3600 s.

Nessun errore specifico dell'endpoint oltre al limite per indirizzo qui sopra.

GET /v1/scopes

Ogni scope che una chiave può avere: cosa concede, se una chiave pubblicabile può averlo, se solo il proprietario della crew può concederlo, e gli endpoint che apre.

Nessuna chiave richiesta. In cache 3600 s.

{
  "data": [
    {
      "scope": "read:events",
      "group": "read",
      "default": true,
      "publishable": true,
      "owner_only": false,
      "summary": "Read published events, their lineups, days, tickets on sale and availability.",
      "routes": ["GET /v1/events", "GET /v1/events/:id", "GET /v1/events/:id/ticket-tiers"]
    },
    {
      "scope": "write:checkout:free",
      "group": "write",
      "default": false,
      "publishable": false,
      "owner_only": true,
      "summary": "Issue free (comp) orders. The crew owner grants it.",
      "routes": ["POST /v1/checkout-sessions"]
    }
  ]
}

La risposta reale elenca tutti e diciassette gli scope e ogni endpoint che ciascuno apre.

GET /v1/changelog

Cosa è cambiato nell'API, più recente prima, ogni data con gli endpoint usciti quel giorno.

Nessuna chiave richiesta. In cache 3600 s.

{
  "data": [
    {
      "date": "2026-08-01",
      "changes": ["Stripe checkout sessions for tickets, and newsletter sign-up."],
      "routes": ["POST /v1/checkout-sessions", "POST /v1/newsletter-signup"]
    }
  ]
}

GET /v1/status

Se l'API e ciò da cui dipende rispondono: il database, i pagamenti (Stripe) e l'email, ciascuno ok o down, controllati al massimo una volta per intervallo così l'endpoint stesso non diventa mai un carico. Il tutto è ok solo quando lo è ogni componente.

Nessuna chiave richiesta. Mai in cache.

{
  "status": "ok",
  "api_version": "v1",
  "time": "2026-09-26T12:00:00.000Z",
  "components": { "database": "ok", "payments": "ok", "email": "ok" }
}

Mai una cifra di latenza, una versione o un messaggio di errore: solo ok o down.


Artisti

Ogni endpoint qui sotto accetta una chiave pubblicabile.

GET /v1/artists

Il roster: ogni artista gestito dalla crew, per nome, crescente. Gli artisti del roster vengono restituiti anche se non pubblicati, dato che il roster sono dati propri della crew.

Scope read:artists. In cache 300 s.

ParametroValoriPredefinitoSignificato
qtesto, da 2 a 100 caratterinessunoIl nome contiene, confrontato come testo
genreelenco separato da virgole di id genere, al massimo 10nessunoUno qualsiasi di questi generi
updated_sincedata o data-ora ISO 8601nessunoCambiato a partire da questo istante o dopo
idselenco separato da virgole di id, al massimo 50nessunoSolo questi artisti
limitintero, da 1 a 10025Dimensione della pagina
cursorstringa opacanessunoDal next_cursor di una pagina precedente
{
  "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/xyz", "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 è solo di livello gestito e non compare mai dentro links. Errori: validation_error (400, q, genre, updated_since, ids o limit non validi), invalid_cursor (400).

GET /v1/artists/{id}

Un artista del roster, rappresentazione completa, restituito direttamente. Un artista esterno (un ospite della line-up non gestito dalla crew) non è raggiungibile in questo modo.

Scope read:artists. In cache 300 s.

Stessa forma di una voce del roster qui sopra. Errori: not_found (404, id malformato, id sconosciuto, o un artista che la crew non gestisce).

GET /v1/artists/{id}/events

Lo storico delle date e il calendario dell'artista: eventi pubblicati in cui è un membro confermato della line-up. Ogni voce porta un riferimento leggero a venue.

Scope read:artists e read:events (entrambi richiesti). In cache 120 s.

ParametroValoriPredefinitoSignificato
statusupcoming, past, allupcomingQuali eventi includere
limitintero, da 1 a 10025Dimensione della pagina
cursorstringa opacanessunoDa una pagina precedente

Questo feed non accetta from, to né expand.

{
  "data": [
    {
      "id": 4521,
      "title": "Overload",
      "description": "A night of hard groove.",
      "starts_at": "2026-09-12T20:00:00.000Z",
      "ends_at": "2026-09-13T04:00:00.000Z",
      "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" }
    }
  ],
  "pagination": { "next_cursor": null, "has_more": false, "limit": 25 }
}

Errori: not_found (404, id malformato o artista che la crew non gestisce), validation_error (400, status non valido), invalid_cursor (400).

GET /v1/artists/{id}/ics

Le prossime date dell'artista come feed iCalendar (text/calendar) a cui iscriversi. Al massimo 100 eventi, RFC 5545, righe piegate a 75 ottetti, orari in UTC. GET /v1/promoters/{id}/ics e GET /v1/events/{id}/ics, più avanti, sono lo stesso feed per gli eventi di un promoter e per un singolo evento.

Scope read:artists e read:events (entrambi richiesti). In cache 300 s.

BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Sway//Sway API v1//EN
CALSCALE:GREGORIAN
METHOD:PUBLISH
X-WR-CALNAME:KROMATIEK
BEGIN:VEVENT
UID:[email protected]
DTSTAMP:20260926T120000Z
DTSTART:20260912T200000Z
DTEND:20260913T040000Z
SUMMARY:Overload
LOCATION:Jungle Bar\, Ghent
URL:https://www.sway.events/event/4521
END:VEVENT
END:VCALENDAR

Il file si chiama artist-{id}.ics (Content-Disposition: inline). Errori: not_found (404, id malformato, o l'artista non è gestito o non è pubblicato).

GET /v1/artists/{id}/presskits

I press kit pubblici dell'artista, prima quello predefinito. I kit privati e le bozze sono esclusi qui; vedi GET /v1/presskits per l'elenco completo proprio della crew.

Scope read:artists e read:content (entrambi richiesti). In cache 300 s.

{
  "artist_id": 245,
  "data": [
    {
      "id": "1b2c3d4e-5f60-4a1b-9c2d-3e4f5a6b7c8d",
      "artist_id": 245,
      "title": "KROMATIEK 2026 EPK",
      "status": "public",
      "protected": false,
      "is_default": true,
      "updated_at": "2026-09-01T10:00:00.000Z",
      "url": "https://www.sway.events/artist/245/presskit/1b2c3d4e-5f60-4a1b-9c2d-3e4f5a6b7c8d"
    }
  ]
}

Errori: not_found (404, id malformato o artista che la crew non gestisce).


Promoter

Ogni endpoint qui sotto accetta una chiave pubblicabile.

GET /v1/promoters

Ogni promoter gestito dalla crew, rappresentazione completa, ordinati per nome.

Scope read:promoters. In cache 300 s.

ParametroValoriPredefinitoSignificato
qtesto, da 2 a 100 caratterinessunoIl nome contiene
idselenco separato da virgole di id, al massimo 50nessunoSolo questi promoter
limitintero, da 1 a 10025Dimensione della pagina
cursorstringa opacanessunoDa una pagina precedente

Stessa busta e stessa forma di voce di GET /v1/artists (campi da promoter invece che da artista; nessun filtro genere). Errori: validation_error (400), invalid_cursor (400).

GET /v1/promoters/{id}

Un promoter gestito e pubblicato, rappresentazione completa, restituito direttamente.

Scope read:promoters. In cache 300 s.

Errori: not_found (404, id malformato o promoter che la crew non gestisce).

GET /v1/promoters/{id}/events

Gli eventi pubblicati di un promoter gestito: il feed principale su cui si costruisce un sito partner.

Scope read:promoters e read:events (entrambi richiesti). In cache 60 s.

ParametroValoriPredefinitoSignificato
statusupcoming, past, allupcomingQuali eventi
from / todata o data-ora ISO 8601nessunoA partire da o dopo / fino a o prima di
expandelenco separato da virgole di venue, lineup, ticket_tiersnessunoIncorpora risorse collegate
limitintero, da 1 a 10025Dimensione della pagina
cursorstringa opacanessunoDa una pagina precedente
{
  "data": [
    {
      "id": 4521,
      "title": "Overload",
      "description": "A night of hard groove.",
      "starts_at": "2026-09-12T20:00:00.000Z",
      "ends_at": "2026-09-13T04:00:00.000Z",
      "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" }
    }
  ],
  "pagination": { "next_cursor": null, "has_more": false, "limit": 25 }
}

venue è di default un riferimento leggero { id, name }; expand=venue lo trasforma nell'oggetto completo di livello pubblico, ed expand=lineup / expand=ticket_tiers aggiungono quegli array a ogni voce (stesse forme di GET /v1/events/{id}). Errori: validation_error (400, status, from, to o expand non validi), invalid_cursor (400), not_found (404, id malformato o promoter che la crew non gestisce).

GET /v1/promoters/{id}/ics

Gli eventi futuri del promoter come feed iCalendar, stessa forma di GET /v1/artists/{id}/ics (vedi Artisti), scaricato come promoter-{id}.ics.

Scope read:promoters e read:events (entrambi richiesti). In cache 300 s.

Errori: not_found (404, id malformato o promoter che la crew non gestisce).

GET /v1/promoters/{id}/venues

I locali dove si svolgono gli eventi pubblicati del promoter, i più usati prima.

Scope read:promoters e read:venues (entrambi richiesti). In cache 300 s.

{
  "data": [
    {
      "id": 88,
      "name": "Jungle Bar",
      "image_url": "https://assets.sway.events/venues/88/cover.webp",
      "description": "A cellar club in Ghent.",
      "location": { "address": "Nieuwewandeling 2, Ghent" },
      "genres": [],
      "links": null,
      "capacity": { "max": 300, "layouts": { "standing": 300, "seated": 120 } },
      "event_count": 14
    }
  ]
}

Errori: not_found (404, id malformato o promoter che la crew non gestisce).

GET /v1/promoters/{id}/artists

Gli artisti che il promoter ha ingaggiato sui suoi eventi pubblicati, i più ingaggiati prima. Gli artisti del roster arrivano al livello completo, gli altri al livello pubblico.

Scope read:promoters e read:artists (entrambi richiesti). In cache 300 s.

{
  "data": [
    { "id": 245, "name": "KROMATIEK", "image_url": "https://assets.sway.events/artists/245/cover.webp", "description": "Ghent-based techno artist.", "genres": ["Techno"], "links": null, "is_verified": true, "managed": true, "contact_emails": [], "booking_count": 6 },
    { "id": 1042, "name": "CE$AR", "image_url": null, "description": null, "genres": ["House"], "links": null, "is_verified": false, "managed": false, "booking_count": 2 }
  ]
}

booking_count conta gli eventi, non i set: un doppio booking la stessa notte conta comunque una volta sola. Errori: not_found (404, id malformato o promoter che la crew non gestisce).


Eventi

Gli orari dei set portano il fuso orario proprio dell'evento.start_time ed end_time su una voce della line-up, e ogni orario dentro GET /v1/events/{id}/days e GET /v1/events/{id}/timetable, restano in RFC 3339 con lo scostamento UTC proprio dell'evento, per esempio 22:00:00+02:00 invece di 22:00:00+00:00: le cifre sono esattamente quelle digitate dall'organizzatore, e l'istante è quello corretto. Sono null finché l'organizzatore tiene la timetable spenta (event-days.ts, apiSetTime).GET /v1/events/{id}/days segue le giornate che l'organizzatore ha dichiarato nelle impostazioni delle giornate del festival, o tratta l'intero evento come una sola giornata quando non ne sono dichiarate; unscheduled contiene i set senza orario, o fuori da ogni giornata dichiarata. GET /v1/events/{id}/timetable segue l'ordine di palco che l'organizzatore ha dichiarato, i palchi non dichiarati dopo in ordine alfabetico, e mette per ultimi i set senza palco.

Ogni endpoint qui sotto accetta una chiave pubblicabile.

GET /v1/events

Il feed unificato del grafo della crew: gli eventi dei suoi promoter, le date confermate del suo roster, e l'agenda dei locali che gestisce. Ogni voce è un riepilogo leggero con un riferimento a venue.

Scope read:events. In cache 60 s.

ParametroValoriPredefinitoSignificato
statusupcoming, past, allupcomingQuali eventi
from / todata o data-ora ISO 8601nessunoFinestra di date
promoter_idintero positivonessunoUn promoter gestito
artist_idintero positivonessunoUn artista del roster
venue_idintero positivonessunoUn locale del grafo
genreelenco separato da virgole di id genere, al massimo 10nessunoUno qualsiasi di questi generi
citytesto, da 1 a 100 caratterinessunoNome esatto della città
countryISO 3166-1 alpha-2nessunoCodice paese
nearlat,lng in gradi decimalinessunoCentro di una ricerca per raggio
radius_kmnumero, da 1 a 50025Raggio intorno a near (richiede near)
updated_sincedata o data-ora ISO 8601nessunoCambiato a partire da questo istante o dopo
has_ticketstrue, falsenessunoSe l'evento ha un tipo di biglietto
idselenco separato da virgole di id, al massimo 50nessunoSolo questi eventi
limitintero, da 1 a 10025Dimensione della pagina
cursorstringa opacanessunoDa una pagina precedente

Questo feed non supporta expand: passarlo restituisce validation_error (400). Usa GET /v1/events/{id} o GET /v1/promoters/{id}/events per i dati espansi.

{
  "data": [
    {
      "id": 4521,
      "title": "Overload",
      "description": "A night of hard groove.",
      "starts_at": "2026-09-12T20:00:00.000Z",
      "ends_at": "2026-09-13T04:00:00.000Z",
      "timezone": "Europe/Brussels",
      "image_url": "https://assets.sway.events/events/4521/cover.webp",
      "type": "club",
      "page_url": "https://www.sway.events/event/4521",
      "updated_at": "2026-09-10T09:00:00.000Z",
      "venue": { "id": 88, "name": "Jungle Bar" }
    }
  ],
  "pagination": { "next_cursor": "eyJrIjoiMjAyNi0wOS0xMlQyMDowMDowMC4wMDBaIiwiaWQiOjQ1MjF9", "has_more": false, "limit": 25 }
}

Errori: validation_error (400, expand fornito, valore di filtro non valido, o radius_km senza near), invalid_cursor (400), not_found (404, promoter_id o artist_id nomina una pagina che la crew non gestisce).

GET /v1/events/search

Lo stesso feed di GET /v1/events, filtrato sugli eventi il cui titolo, descrizione o line-up confermata contiene il testo cercato.

Scope read:events. In cache 60 s.

Ogni filtro di GET /v1/events, più:

ParametroValoriPredefinitoSignificato
qtesto, da 2 a 100 caratteriobbligatorioConfrontato come testo su titolo, descrizione e line-up

Stessa busta di GET /v1/events. Errori: validation_error (400, q mancante o di lunghezza sbagliata, o uno qualsiasi dei filtri condivisi), invalid_cursor (400), not_found (404, promoter_id o artist_id fuori dal grafo).

GET /v1/events/{id}

Un evento pubblicato del grafo della crew, restituito direttamente. venue (livello pubblico completo) e lineup sono sempre incorporati; genres è sempre presente.

Scope read:events. In cache 60 s.

ParametroValoriPredefinitoSignificato
expandticket_tiersnessunovenue e lineup sono già incorporati, quindi questo è l'unico valore che aggiunge qualcosa

Quando è presente expand=ticket_tiers, la risposta porta anche un oggetto ticket_fee: { "percent": number, "fixed": number, "applies_to": "ticket" | "order" }, la commissione di servizio per il beneficiario di questo evento, in EUR. È lo stesso oggetto di GET /v1/events/{id}/fees, che spiega come applicarla.

{
  "id": 4521,
  "title": "Overload",
  "description": "A night of hard groove.",
  "starts_at": "2026-09-12T20:00:00.000Z",
  "ends_at": "2026-09-13T04:00:00.000Z",
  "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": "https://assets.sway.events/venues/88/cover.webp",
    "description": "A cellar club in Ghent.",
    "location": { "address": "Nieuwewandeling 2, Ghent" },
    "genres": [],
    "links": null
  },
  "lineup": [
    {
      "stage": "Main", "start_time": "2026-09-12T23:00:00+02:00", "end_time": "2026-09-13T01:00:00+02:00",
      "artist_id": 245, "id": 245, "name": "KROMATIEK", "image_url": "https://assets.sway.events/artists/245/cover.webp",
      "description": "Ghent-based techno artist.", "genres": ["Techno"], "links": { "spotify": "https://open.spotify.com/artist/xyz" },
      "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": null, "description": null,
      "genres": ["House"], "links": null, "is_verified": false, "managed": false
    }
  ],
  "genres": ["Techno"]
}

Le voci della line-up portano il DTO proprio dell'artista più stage, start_time, end_time e artist_id: gli artisti del roster arrivano al livello completo (con contact_emails), gli ospiti esterni al livello pubblico. Un atto personalizzato, non collegato, compare come voce segnaposto con artist_id: null e il suo nome digitato sotto name. La seconda voce della line-up qui sopra ha start_time/end_time: null perché quel set non ha un orario registrato, non perché la timetable è spenta; vedi la nota sopra la sezione.

Errori: validation_error (400, valore di expand sconosciuto), not_found (404, id malformato, evento non pubblicato, o un evento fuori dal grafo della crew).

GET /v1/events/{id}/ticket-tiers

I tipi di biglietto dell'evento: nome, descrizione, prezzo, stato, finestra di vendita e un deep link checkout_url verso il checkout di Sway. Nessuna disponibilità, nessun ordine, nessun dato degli acquirenti. Un elenco piccolo e fisso: avvolto in { "data": [...] } ma senza pagination e senza cursore.

Scope read:events. In cache 30 s.

{
  "data": [
    {
      "id": "301", "name": "Early Bird", "description": "Limited first release", "price": 15.0, "currency": "EUR",
      "fee_amount": 0.75, "price_with_fee": 15.75, "fee_applies_to": "ticket", "status": "sold_out", "sale_start": "2026-06-01T10:00:00.000Z", "sale_end": "2026-07-01T10:00:00.000Z",
      "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",
      "fee_amount": 0.9, "price_with_fee": 20.9, "fee_applies_to": "ticket",
      "sale_start": "2026-07-01T10:00:00.000Z", "sale_end": "2026-09-12T20:00:00.000Z",
      "max_per_order": 6, "checkout_url": "https://www.sway.events/event/4521?ref=api"
    }
  ]
}

status viene derivato lato server: on_sale, scheduled, sold_out o off_sale. currency di default è EUR. Errori: not_found (404).

fee_amount è la commissione di servizio su un biglietto e price_with_fee il prezzo totale pagato da chi compra (price più fee_amount), secondo il modello di commissione del promoter che incassa, le stesse cifre della pagina dell'evento e dell'MCP pubblico. Mostra prima price_with_fee e fee_amount come commissione di servizio. Con il vecchio modello per ordine (fee_applies_to: "order") entrambi sono null: la commissione dipende dall'intero carrello, vedi GET /v1/events/{id}/fees. I tipi gratuiti hanno un fee_amount di 0.

GET /v1/events/{id}/availability

Se si può comprare proprio ora: in vendita, programmato, esaurito o concluso, per tipo e per l'evento nel suo insieme, con un flag "pochi rimasti". Mai un conteggio delle scorte.

Scope read:events. In cache 30 s.

{
  "event_id": 4521,
  "status": "on_sale",
  "next_sale_start": null,
  "tiers": [
    { "id": "301", "name": "Early Bird", "status": "sold_out", "few_left": false, "sale_start": "2026-06-01T10:00:00.000Z", "sale_end": "2026-07-01T10:00:00.000Z" },
    { "id": "302", "name": "Regular", "status": "on_sale", "few_left": true, "sale_start": "2026-07-01T10:00:00.000Z", "sale_end": "2026-09-12T20:00:00.000Z" }
  ]
}

few_left si attiva quando le scorte rimanenti di un tipo sono a un decimo o meno della scorta iniziale (minimo 5), e solo mentre è on_sale. Lo status proprio dell'evento può essere no_tickets quando l'evento non ha nessun tipo di biglietto. Errori: not_found (404).

GET /v1/events/{id}/fees

La commissione di servizio che un acquirente paga sui biglietti di questo evento, in base al modello di commissioni del promoter beneficiario: di default una percentuale del prezzo di ogni biglietto pagante più un importo fisso per biglietto.

Scope read:events. In cache 300 s.

{ "event_id": 4521, "percent": 0.03, "fixed": 0.3, "applies_to": "ticket" }

Con applies_to: "ticket", ogni biglietto pagante porta round(price * percent + fixed) arrotondato al centesimo (un pacchetto conta come un biglietto, un biglietto gratuito non porta nulla), e total = subtotal + la somma di queste commissioni. Alcuni promoter mantengono un modello più vecchio, applies_to: "order": total = subtotal + subtotal * percent + fixed, una volta per ordine pagante. In entrambi i casi il risultato corrisponde al totale addebitato dal checkout Stripe. Ricade sull'aliquota standard per biglietto quando il beneficiario non si può risolvere, invece di fallire. Errori: not_found (404).

GET /v1/events/{id}/days

Le giornate che l'organizzatore ha dichiarato nelle impostazioni delle giornate del festival dell'evento, ciascuna con i set che iniziano in essa; oppure l'evento come un'unica giornata quando non ne sono dichiarate.

Scope read:events. In cache 60 s.

{
  "event_id": 4521,
  "timezone": "Europe/Brussels",
  "timetable_published": true,
  "data": [
    {
      "name": "Friday",
      "date": "2026-09-12",
      "starts_at": "2026-09-12T20:00:00+02:00",
      "ends_at": "2026-09-13T06:00:00+02:00",
      "slots": [
        { "name": "KROMATIEK", "artist_id": 245, "artists": [{ "id": 245, "name": "KROMATIEK", "managed": true }], "stage": "Main", "start_time": "2026-09-12T23:00:00+02:00", "end_time": "2026-09-13T01:00:00+02:00" }
      ]
    }
  ],
  "unscheduled": []
}

unscheduled contiene i set che non hanno un orario, o che cadono fuori da ogni giornata dichiarata. Con la timetable spenta, timetable_published è false e ogni giornata arriva con un array slots vuoto. Un set B2B è un solo slot con più artists; name è il nome personalizzato dell'organizzatore quando impostato, altrimenti gli artisti uniti con " B2B ". Errori: not_found (404).

GET /v1/events/{id}/timetable

I set con orario per palco, nell'ordine di palco che l'organizzatore ha dichiarato (il resto in ordine alfabetico, quelli senza palco per ultimi), ogni palco in ordine cronologico.

Scope read:events. In cache 60 s.

{
  "event_id": 4521,
  "timezone": "Europe/Brussels",
  "published": true,
  "stages": [
    {
      "stage": "Main",
      "slots": [
        { "name": "KROMATIEK", "artist_id": 245, "artists": [{ "id": 245, "name": "KROMATIEK", "managed": true }], "stage": "Main", "start_time": "2026-09-12T23:00:00+02:00", "end_time": "2026-09-13T01:00:00+02:00" }
      ]
    }
  ]
}

Un set senza orario non è su nessuna timetable e viene escluso del tutto (può comunque comparire in GET /v1/events/{id}/days, sotto unscheduled). Con la timetable spenta, published è false e stages è vuoto. Errori: not_found (404).

GET /v1/events/{id}/ics

L'evento come feed iCalendar, stessa forma di GET /v1/artists/{id}/ics (vedi Artisti), per un pulsante "aggiungi al calendario". Scaricato come event-{id}.ics.

Scope read:events. In cache 300 s.

Errori: not_found (404, id malformato, evento non pubblicato o fuori dal grafo della crew, o l'evento non ha una data di inizio).

GET /v1/events/{id}/schema-org

L'evento come JSON-LD schema.org (MusicEvent), pronto per essere inserito così com'è nello <script type="application/ld+json"> della pagina di un partner, per i motori di ricerca.

Scope read:events. In cache 300 s.

{
  "@context": "https://schema.org",
  "@type": "MusicEvent",
  "name": "Overload",
  "url": "https://www.sway.events/event/4521",
  "eventStatus": "https://schema.org/EventScheduled",
  "eventAttendanceMode": "https://schema.org/OfflineEventAttendanceMode",
  "description": "A night of hard groove.",
  "startDate": "2026-09-12T20:00:00.000Z",
  "endDate": "2026-09-13T04:00:00.000Z",
  "image": ["https://assets.sway.events/events/4521/cover.webp"],
  "location": {
    "@type": "Place",
    "name": "Jungle Bar",
    "address": { "@type": "PostalAddress", "streetAddress": "Nieuwewandeling 2, Ghent", "addressLocality": "Ghent", "addressCountry": "BE" }
  },
  "performer": [{ "@type": "PerformingGroup", "name": "KROMATIEK" }, { "@type": "PerformingGroup", "name": "CE$AR" }],
  "organizer": { "@type": "Organization", "name": "Insomnia Nights", "url": "https://www.sway.events/promoter/697" },
  "offers": [{ "@type": "Offer", "name": "Regular", "price": 20.0, "priceCurrency": "EUR", "availability": "https://schema.org/InStock", "url": "https://www.sway.events/event/4521?ref=api", "validFrom": "2026-07-01T10:00:00.000Z" }]
}

performer viene costruito dai set della timetable (non dalla line-up grezza), quindi elenca ogni nome una volta sola e segue la stessa regola "timetable spenta" degli endpoint sopra. Errori: not_found (404).

GET /v1/events/{id}/gallery

Gli album fotografici pubblici dell'evento, ciascuno con le foto che hanno finito di caricarsi (le prime 200) e quante ne contiene in totale.

Scope read:events. In cache 300 s.

{
  "event_id": 4521,
  "data": [
    {
      "id": 12, "name": "Warm-up", "description": null,
      "photos": [{ "id": 501, "url": "https://assets.sway.events/events/4521/gallery/501.webp", "width": 1600, "height": 1067 }],
      "photo_count": 84
    }
  ]
}

Gli album privati e le foto ancora in caricamento non compaiono mai. Errori: not_found (404).

GET /v1/events/{id}/partners

I partner dei promoter e dei locali dell'evento, nell'ordine di visualizzazione: stessa forma di GET /v1/partners (vedi Contenuti), avvolta sotto { "event_id": 4521, "data": [...] }.

Scope read:events e read:content (entrambi richiesti). In cache 300 s.

Errori: not_found (404, evento non pubblicato o fuori dal grafo della crew).

GET /v1/events/{id}/news

Le notizie pubblicate dell'evento, più recenti prima, come markdown e come HTML sanificato: stessa forma di GET /v1/news (vedi Contenuti), avvolta sotto event_id.

Scope read:events e read:content (entrambi richiesti). In cache 120 s.

ParametroValoriPredefinitoSignificato
limitintero, da 1 a 10025Dimensione della pagina
cursorstringa opacanessunoDa una pagina precedente

Errori: not_found (404, evento non pubblicato o fuori dal grafo della crew), invalid_cursor (400).

GET /v1/events/{id}/presales

Le prevendite dell'evento che non sono bozze, esattamente come le mostra la pagina pubblica di prevendita: teaser, finestra di iscrizione, se l'iscrizione è aperta proprio ora.

Scope read:events. In cache 60 s.

{
  "event_id": 4521,
  "data": [
    {
      "token": "f4c8b1a2-...", "status": "open", "registration_open": true,
      "registration_opens_at": "2026-08-01T09:00:00.000Z", "registration_closes_at": "2026-09-05T23:59:00.000Z",
      "featured": true, "featured_mode": "homepage",
      "teaser": { "title": "Early access", "subtitle": "First 100 only", "image_url": null, "description": "Register for a chance at the first release." },
      "url": "https://www.sway.events/presale/f4c8b1a2-..."
    }
  ]
}

I prodotti, i tetti, la regola di selezione, il promoter e la mailing list dietro una prevendita non sono mai esposti. Errori: not_found (404).


Locali

Ogni endpoint qui sotto accetta una chiave pubblicabile.

GET /v1/venues

I locali del grafo della crew: quelli che gestisce (managed: true) e quelli che ospitano i suoi eventi pubblicati, livello pubblico, per nome.

Scope read:venues. In cache 300 s.

ParametroValoriPredefinitoSignificato
qtesto, da 2 a 100 caratterinessunoIl nome contiene
idselenco separato da virgole di id, al massimo 50nessunoSolo questi locali
limitintero, da 1 a 10025Dimensione della pagina
cursorstringa opacanessunoDa una pagina precedente
{
  "data": [
    {
      "id": 88, "name": "Jungle Bar", "image_url": "https://assets.sway.events/venues/88/cover.webp",
      "description": "A cellar club in Ghent.", "location": { "address": "Nieuwewandeling 2, Ghent" },
      "genres": [], "links": null,
      "capacity": { "max": 300, "layouts": { "standing": 300, "seated": 120 } },
      "managed": true
    }
  ],
  "pagination": { "next_cursor": null, "has_more": false, "limit": 25 }
}

Errori: validation_error (400), invalid_cursor (400).

GET /v1/venues/{id}

Un locale del grafo della crew, livello pubblico, con la sua capienza, restituito direttamente. Raggiungibile solo quando la crew lo gestisce o ospita un evento pubblicato del grafo.

Scope read:venues. In cache 300 s.

Stessa forma di voce di sopra (con managed). Errori: not_found (404).

GET /v1/venues/{id}/events

L'agenda del locale: ogni evento pubblicato del grafo della crew che vi si svolge (tutti quando la crew gestisce il locale). Stessi filtri di GET /v1/events, senza i filtri su pagina/artista/locale.

Scope read:venues e read:events (entrambi richiesti). In cache 60 s.

ParametroValoriPredefinitoSignificato
statusupcoming, past, allupcomingQuali eventi
from / todata o data-ora ISO 8601nessunoFinestra di date
limitintero, da 1 a 10025Dimensione della pagina
cursorstringa opacanessunoDa una pagina precedente

Stessa busta e stessa forma di voce di GET /v1/events. Errori: not_found (404, id malformato o locale fuori dal grafo), validation_error (400), invalid_cursor (400).

GET /v1/venues/{id}/capacity

Quante persone contiene il locale per ogni disposizione della sala (in piedi, seduti, teatro, cabaret, banchetto, cocktail, aula), e la cifra più alta: le disposizioni sono allestimenti alternativi della stessa sala, mai zone che si sommano.

Scope read:venues. In cache 300 s.

{ "venue_id": 88, "max": 300, "layouts": { "standing": 300, "seated": 120 } }

max e layouts sono null e {} quando il locale non dichiara nessuna capienza. Errori: not_found (404).


Contenuti

Ogni endpoint qui sotto accetta una chiave pubblicabile.

GET /v1/news

Le notizie pubblicate di una pagina gestita dalla crew (un promoter, un locale o un artista), più recenti prima, come markdown e come HTML sanificato. Un post programmato per dopo resta fuori fino alla sua ora. GET /v1/events/{id}/news (vedi Eventi) è lo stesso feed per un evento invece che per una pagina.

Scope read:content. In cache 120 s.

ParametroValoriPredefinitoSignificato
promoter_id / venue_id / artist_idintero positivonessunoEsattamente uno, che nomina una pagina gestita dalla crew
limitintero, da 1 a 10025Dimensione della pagina
cursorstringa opacanessunoDa una pagina precedente
{
  "data": [
    {
      "id": 88, "title": "Lineup announced",
      "body_markdown": "The full lineup is **out now**.",
      "body_html": "<p>The full lineup is <strong>out now</strong>.</p>",
      "hero_image_url": "https://assets.sway.events/news/88/hero.webp",
      "published_at": "2026-08-20T09:00:00.000Z"
    }
  ],
  "pagination": { "next_cursor": null, "has_more": false, "limit": 25 }
}

Errori: validation_error (400, non esattamente uno tra promoter_id / venue_id / artist_id), invalid_cursor (400), not_found (404, la pagina nominata non è gestita dalla crew).

GET /v1/partners

I partner di un promoter o locale gestito dalla crew, nell'ordine di visualizzazione.

Scope read:content. In cache 300 s.

ParametroValoriPredefinitoSignificato
promoter_id / venue_idintero positivonessunoEsattamente uno, che nomina una pagina gestita dalla crew
{
  "data": [
    { "id": 9, "name": "Redbull", "logo_url": "https://assets.sway.events/partners/9/logo.webp", "website_url": "https://www.redbull.com", "owner": { "type": "promoter", "id": 697 } }
  ]
}

Errori: validation_error (400, non esattamente uno tra promoter_id / venue_id), not_found (404, la pagina nominata non è gestita dalla crew).

GET /v1/presskits

I press kit di ogni artista gestito dalla crew, con la loro visibilità (pubblico, privato, bozza) e se una password li protegge. Una chiave pubblicabile vede solo quelli pubblici; una chiave segreta vede tutti e tre gli stati.

Scope read:content. In cache 120 s.

{
  "data": [
    { "id": "1b2c3d4e-5f60-4a1b-9c2d-3e4f5a6b7c8d", "artist_id": 245, "title": "KROMATIEK 2026 EPK", "status": "public", "protected": false, "is_default": true, "updated_at": "2026-09-01T10:00:00.000Z", "url": "https://www.sway.events/artist/245/presskit/1b2c3d4e-5f60-4a1b-9c2d-3e4f5a6b7c8d" }
  ]
}

Nessun errore specifico dell'endpoint.

GET /v1/presskits/{id}

Un press kit esattamente come lo mostra la sua pagina propria: l'artista, le sezioni nell'ordine scelto, i loro testi e file. Un kit protetto vuole la sua password nell'header Presskit-Password, da ogni chiamante, crew gestore incluso.

Scope read:content. Mai in cache (la risposta dipende da un header che la cache non vede).

curl https://www.sway.events/api/v1/presskits/1b2c3d4e-5f60-4a1b-9c2d-3e4f5a6b7c8d \
  -H "Authorization: Bearer sway_sk_YOUR_KEY" \
  -H "Presskit-Password: secret"
{
  "id": "1b2c3d4e-5f60-4a1b-9c2d-3e4f5a6b7c8d",
  "artist_id": 245,
  "title": "KROMATIEK 2026 EPK",
  "status": "public",
  "protected": false,
  "is_default": true,
  "updated_at": "2026-09-01T10:00:00.000Z",
  "url": "https://www.sway.events/artist/245/presskit/1b2c3d4e-5f60-4a1b-9c2d-3e4f5a6b7c8d",
  "artist": { "id": 245, "name": "KROMATIEK", "image_url": "https://assets.sway.events/artists/245/cover.webp", "description": "Ghent-based techno artist.", "genres": ["Techno"], "links": null, "is_verified": true, "managed": true, "contact_emails": [] },
  "sections": ["hero", "music", "bio", "photos", "contact"],
  "style": { "accent_color": "#111111", "hero_image_url": null },
  "bio_version": "short",
  "bio_overrides": {},
  "press_quotes": [],
  "files": [{ "id": "a1b2...", "type": "photo", "url": "https://assets.sway.events/presskits/.../photo.webp", "filename": "kromatiek-1.webp", "size_bytes": 240811 }]
}

Un kit privato si apre solo alla chiave segreta della crew gestore; una bozza non è mai raggiungibile. Errori: not_found (404, id malformato, kit non trovato, artista non gestito, kit in bozza, o un kit privato chiamato con il tipo di chiave sbagliato), password_required (401, password mancante o sbagliata), rate_limited (429, al massimo 10 tentativi di password al minuto per chiave e kit, e per indirizzo del visitatore quando chiamato con una chiave pubblicabile).

GET /v1/forms

I moduli della crew e delle pagine che gestisce, bozze escluse, ciascuno con l'indicazione se accetta risposte proprio ora.

Scope read:content. In cache 120 s.

{
  "data": [
    {
      "slug": "backstage-crew", "title": "Join the backstage crew", "description": null,
      "availability": "open", "opens_at": null, "closes_at": null,
      "owner": { "type": "promoter", "id": 697 },
      "url": "https://www.sway.events/f/backstage-crew"
    }
  ]
}

availability è uno tra draft, scheduled, open, full, closed, empty (un modulo pubblicato senza nessuna domanda). Errori: nessuno oltre all'autenticazione.

GET /v1/forms/{slug}

Le domande, il design e la disponibilità di un modulo, per renderlo sul sito partner, esattamente come farebbe la sua pagina pubblica propria.

Scope read:content. In cache 60 s.

{
  "id": "9f2c1a7e-4d3b-4e21-8c7a-1b2c3d4e5f60",
  "slug": "backstage-crew",
  "title": "Join the backstage crew",
  "description": null,
  "availability": "open",
  "opens_at": null,
  "closes_at": null,
  "captures_contact": true,
  "definition_version": 3,
  "settings": { "show_progress": true, "submit_label": null, "consent_text": null },
  "steps": [{ "id": "s1", "fields": [{ "id": "email", "type": "email", "label": "Email", "required": true }] }],
  "design": {},
  "url": "https://www.sway.events/f/backstage-crew"
}

steps e design sono la definizione del modulo così come la memorizza il form builder, passati esattamente come sono: le risposte che invii a POST /v1/forms/{slug}/submit sono indicizzate dagli id delle domande dentro steps. captures_contact dice se un indirizzo fornito nel modulo entra nei contatti dell'organizzatore.

Errori: not_found (404, forma dello slug non valida, modulo in bozza, o un modulo non posseduto dalla crew o da una delle sue pagine).


Esportazione

Nessuno di questi endpoint accetta una chiave pubblicabile; ognuno richiede read:export più lo scope di lettura corrispondente. Mai in cache (no-store), e ognuno è limitato a un'esportazione per risorsa per chiave ogni cinque minuti, oltre le quali la risposta è rate_limited (429). Ogni file è limitato a 10.000 righe e dice se è stato troncato con un header X-Export-Truncated: true|false.

GET /v1/export/events

GET /v1/export/artists

GET /v1/export/promoters

GET /v1/export/venues

Il catalogo pubblicato della crew, una risorsa alla volta, come JSON delimitato da a capo (application/x-ndjson): un oggetto JSON per riga, senza busta, senza paginazione. Ogni riga è esattamente il DTO che restituisce l'endpoint di elenco corrispondente (GET /v1/events, GET /v1/artists, GET /v1/promoters, GET /v1/venues), quindi un'esportazione non mostra mai più di quanto già faccia l'API.

Scope read:export + read:events / read:artists / read:promoters / read:venues rispettivamente.

{"id":4521,"title":"Overload","starts_at":"2026-09-12T20:00:00.000Z", ..., "venue":{"id":88,"name":"Jungle Bar"}}
{"id":4522,"title":"Warehouse Night","starts_at":"2026-09-19T21:00:00.000Z", ..., "venue":null}

La risposta viene scaricata come sway-events.ndjson, sway-artists.ndjson, sway-promoters.ndjson o sway-venues.ndjson (Content-Disposition: attachment). Errori: rate_limited (429, un'esportazione di questa risorsa è già stata eseguita per questa chiave negli ultimi cinque minuti).


Richieste di booking

POST /v1/booking-requests

Invia una richiesta di booking da un sito partner nella pipeline di booking della crew e notifica la crew. Ogni artista richiesto deve essere un artista del roster gestito.

Scope write:bookings (da attivare). Chiavi pubblicabili: no. Mai in cache. Accetta Idempotency-Key. Limitato più delle letture: 10 richieste al minuto per visitatore (client_ip), e 300 al giorno per crew.

CampoTipoObbligatorioNote
artist_idsnumber[]sìNon vuoto, al massimo 50 id, ciascuno un artista del roster gestito
contact_emailstringsìEmail valida, al massimo 320 caratteri
contact_namestringnoAl massimo 200 caratteri
contact_phonestringnoAl massimo 200 caratteri
event_namestringnoAl massimo 200 caratteri
event_datestringnoData ISO, YYYY-MM-DD
venuestringnoAl massimo 200 caratteri
citystringnoAl massimo 200 caratteri
fee_offerstringnoTesto libero, al massimo 200 caratteri
capacitystringnoTesto libero, al massimo 200 caratteri
ticket_pricestringnoTesto libero, al massimo 200 caratteri
organizationstringnoAl massimo 200 caratteri
{
  "artist_ids": [245],
  "contact_email": "[email protected]",
  "contact_name": "Jane Doe",
  "event_name": "Warehouse Night",
  "event_date": "2026-11-21",
  "venue": "Fuse",
  "city": "Brussels",
  "fee_offer": "800 EUR"
}

Risposta 201 Created, restituita direttamente:

{ "id": "9f2c1a7e-4d3b-4e21-8c7a-1b2c3d4e5f60", "status": "new", "created_at": "2026-07-03T14:00:00.000Z" }

Errori: validation_error (422, forma del corpo non valida, o artist_ids nomina un id fuori dal roster, elencato sotto errors[].invalid_ids), rate_limited (429, limite per visitatore o per crew al giorno).


Pubblico

Nessuno di questi endpoint accetta una chiave pubblicabile.

POST /v1/newsletter-signup

Aggiunge un'email alla newsletter di un promoter gestito come contatto CRM, con una voce di log di consenso GDPR.

Scope write:newsletter (da attivare). Mai in cache. Accetta Idempotency-Key. Limitato a 10 richieste al minuto per visitatore (client_ip), e 500 iscrizioni al giorno per promoter.

CampoTipoObbligatorioNote
emailstringsìEmail valida, al massimo 320 caratteri
promoter_idnumbersìUn promoter gestito dalla crew
first_name / last_namestringnoAl massimo 200 caratteri
localestringnoAl massimo 10 caratteri
countrystringnoAl massimo 2 caratteri
{ "email": "[email protected]", "promoter_id": 697, "first_name": "Jane" }

Risposta 201 Created: { "status": "subscribed" }.

Errori: validation_error (422, corpo non valido, o promoter_id non gestito dalla crew), rate_limited (429).

POST /v1/newsletter-unsubscribe

Toglie un'email dalla newsletter di un promoter gestito e dal suo fornitore di mailing list: stesso stato CRM che ogni sincronizzazione successiva rispetta.

Scope write:newsletter (da attivare). Mai in cache. Accetta Idempotency-Key. Limitato a 10 richieste al minuto per visitatore (client_ip), e 500 al giorno per promoter.

CampoTipoObbligatorioNote
emailstringsìEmail valida
promoter_idnumbersìUn promoter gestito dalla crew

Risposta 200 OK: sempre { "status": "unsubscribed" }, che l'indirizzo fosse o no nell'elenco: l'endpoint non deve mai dire a chi chiama chi è iscritto.

Errori: validation_error (422, corpo non valido, o promoter_id non gestito), rate_limited (429).

POST /v1/forms/{slug}/submit

Invia le risposte di un modulo che il sito partner ha renderizzato da GET /v1/forms/{slug}: convalidate contro la definizione memorizzata, archiviate nel CRM del proprietario quando il modulo cattura un contatto, con api:form:{slug} registrato come fonte del consenso.

Scope write:forms (da attivare). Mai in cache. Accetta Idempotency-Key. Limitato a 60 invii al minuto per chiave e modulo, 5 al minuto per indirizzo del visitatore (quando client_ip viene inoltrato), e 5000 al giorno per modulo.

CampoTipoObbligatorioNote
answersobjectsìIndicizzato per id di campo, come li chiama la definizione propria del modulo
localestringnoTag di lingua di due lettere, opzionalmente con una regione
honeypotstringnoUn campo nascosto del modulo proprio del partner: qualsiasi testo al suo interno e la risposta viene scartata silenziosamente
client_ipstringnoL'indirizzo del visitatore, usato solo per contare i suoi invii
{ "answers": { "email": "[email protected]", "s1_q1": "Yes" } }

Risposta 201 Created: { "status": "received", "redirect_url": null } (una risposta con l'honeypot compilato risponde esattamente allo stesso modo, ma non memorizza nulla).

Errori: not_found (404, slug non valido, modulo in bozza, o un modulo non posseduto dalla crew), validation_error (422, corpo malformato o risposte che falliscono la convalida propria del modulo, sotto errors[].param come answers.<campo>), conflict (409, not_taking_answers quando il modulo non è attualmente aperto, already_answered quando il modulo permette solo una risposta per indirizzo e questo ha già risposto), rate_limited (429).


Checkout

Nessuno di questi endpoint accetta una chiave pubblicabile.

POST /v1/checkout-sessions

Crea una sessione di checkout ospitato Stripe per uno storefront pubblico e restituisce la sua url. L'acquirente viene reindirizzato lì, poi torna alla pagina /success o /cancel propria di Sway, mai al sito di chi chiama; l'evasione (ordine, biglietti, email di conferma) è interamente di Sway.

Scope write:checkout (da attivare); un carrello il cui totale è esattamente zero richiede in più write:checkout:free. Mai in cache. Non accetta Idempotency-Key: questo endpoint tiene invece una propria protezione (idempotency_key nel corpo, sotto). Limitato a 60 richieste al minuto per acquirente (client_ip), 600 al minuto per chiave, 5000 checkout al giorno per crew, e 30 checkout a pagamento (o 10 gratuiti) al giorno per email dell'acquirente.

Tutto ciò che è autorevole su soldi e scorte si risolve lato server e non può mai essere fornito o sovrascritto da chi chiama: prezzo, valuta, l'account Stripe del beneficiario, la commissione di servizio e la prenotazione delle scorte. La crew della chiave deve gestire il promoter che riceve i fondi, altrimenti validation_error.

CampoTipoObbligatorioNote
event_idnumbersìDeve essere pubblicato e nel grafo della crew
line_itemsarraysìDa 1 a 20 voci { "tier_id": string, "quantity": number }; tier_id è un id di tipo da GET /v1/events/{id}/ticket-tiers, quantity da 1 a 50
buyer_emailstringsìEmail valida, al massimo 320 caratteri: dove vengono consegnati i biglietti
idempotency_keystringnoUn UUID per clic di acquisto; un nuovo tentativo con lo stesso valore restituisce la stessa sessione
discount_idstringnoUn id coupon per l'evento, da POST /v1/coupons/validate
access_codestringnoUn codice di prevendita o di accesso che sblocca un tipo nascosto
referencestringnoTesto libero, al massimo 64 caratteri di [A-Za-z0-9._:-], riportato solo nell'analytics
localestringnoLa lingua di chi compra (en, fr, nl...), usata per nominare la riga della commissione di servizio nella pagina di pagamento Stripe. Per default l'Accept-Language della richiesta, poi l'inglese
marketing_consentbooleannoIgnorato su questo percorso; un consenso dichiarato da una chiave API non è mai attendibile per l'iscrizione al CRM
consentobjectnoBooleani { analytics, marketing } più version/at, inoltrati solo alla registrazione di attribuzione pubblicitaria
client_ip / client_user_agent / event_source_urlstringnoSegnali lato acquirente per la corrispondenza di attribuzione pubblicitaria, e client_ip per contare i checkout dell'acquirente. Mai usati per altro

Campi rifiutati con validation_error se forniti, perché ciascuno si risolve lato server: amount / price / unit_amount, currency, qualsiasi beneficiario o stripe_account, application_fee_amount / fee, success_url / cancel_url / return_url, coupon / coupon_code / stripe_coupon_id, payment_method_types, user_id.

{
  "event_id": 4521,
  "line_items": [{ "tier_id": "8f14e45f-ceea-467a-9575-b2c45e4f2a11", "quantity": 2 }],
  "buyer_email": "[email protected]",
  "idempotency_key": "b1e2c3d4-5f60-4a1b-9c2d-3e4f5a6b7c8d"
}

Risposta 200 OK, restituita direttamente:

{ "url": "https://checkout.stripe.com/c/pay/cs_live_...", "checkout_id": "chk_9f2c1a7e..." }

checkout_id segue solo una sessione a pagamento; un ordine gratuito (in omaggio) restituisce solo { "url": ... }, dato che non c'è nessuna sessione Stripe da seguire in seguito.

Un checkout a pagamento trattiene i suoi biglietti, e un utilizzo del suo coupon e del suo codice di accesso, per i 30 minuti della sessione. Ciò che l'acquirente non paga torna disponibile alla scadenza della sessione; fino ad allora, un codice il cui ultimo utilizzo è trattenuto risponde validation_error.

Ordini gratuiti (zero euro). Un carrello il cui totale è zero, da un coupon del 100% o da un tipo di biglietto davvero gratuito, è una capacità distinta e a fiducia più alta: la chiave richiede in più write:checkout:free. Biglietti gratuiti e a pagamento non si possono combinare in un solo ordine.

Errori: missing_scope (403, nessun write:checkout, o un ordine gratuito senza write:checkout:free), not_found (404, evento non pubblicato o fuori dal grafo), validation_error (422, corpo non valido, tipo sconosciuto o non disponibile, quantità oltre max_per_order, righe gratuite e a pagamento miste, o beneficiario non gestito dalla crew), conflict (409, l'account di pagamento del promoter non è pronto), rate_limited (429, controlla Retry-After), upstream_error (502, Stripe non è riuscita a creare la sessione).

GET /v1/checkout-sessions/{id}

A che punto sta un checkout aperto da questa crew, indirizzato dal checkout_id restituito alla sua creazione, mai dall'id di sessione Stripe: aperto (con il suo link di pagamento), completo o scaduto.

Scope write:checkout. Mai in cache (no-store).

{
  "checkout_id": "chk_9f2c1a7e...",
  "status": "open",
  "payment_status": "unpaid",
  "amount_total": 4552,
  "currency": "eur",
  "expires_at": "2026-09-26T13:00:00.000Z",
  "reference": null,
  "url": "https://checkout.stripe.com/c/pay/cs_live_..."
}

url è presente solo mentre status è open. Errori: not_found (404, handle malformato, o un checkout sigillato per un'altra crew).

POST /v1/checkout-sessions/{id}/expire

Chiude un checkout aperto da questa crew e restituisce subito i suoi biglietti alle scorte, invece di aspettare il timer proprio di Stripe. Idempotente: chiamarlo di nuovo su un checkout già scaduto risponde semplicemente con il suo stato attuale.

Scope write:checkout. Mai in cache. Accetta Idempotency-Key.

Stessa forma di risposta di GET /v1/checkout-sessions/{id}. Errori: not_found (404, come sopra), conflict (409, il checkout era già stato pagato: un rimborso passa invece dall'organizzatore).

POST /v1/coupons/validate

Controlla un codice promozionale prima del checkout: il discount_id da passare, le sue condizioni, e i totali del carrello quando ne viene inviato uno, o perché non si applica. Risponde 200 in entrambi i casi: un codice rifiutato è una risposta, non un errore.

Scope write:checkout. Mai in cache. Non accetta Idempotency-Key (writeClass: check): gira ogni volta, fino a 300 controlli al minuto per chiave, oltre ai limiti di tentativi qui sotto.

CampoTipoObbligatorioNote
event_idnumbersìDeve essere un evento il cui beneficiario è gestito dalla crew
codestringsìDa 1 a 64 caratteri stampabili
line_itemsarraynoIl carrello del checkout, stessa forma di POST /v1/checkout-sessions
client_ipstringnoL'indirizzo dell'acquirente, usato solo per contare i suoi tentativi falliti
{ "event_id": 4521, "code": "EARLY10", "line_items": [{ "tier_id": "8f14e45f-ceea-467a-9575-b2c45e4f2a11", "quantity": 2 }] }
{
  "valid": true, "code": "EARLY10", "discount_id": "f4c8b1a2-...",
  "percentage": 10, "amount": null, "products_remaining": 4,
  "totals": { "currency": "EUR", "subtotal": 5000, "discount": 500, "total": 4500 }
}

Un codice appartenente a un altro evento, un altro organizzatore, o uno disattivato rispondono tutti { "valid": false, "code": "...", "reason": "not_found" }, in modo indistinguibile. Altri valori di reason: min_order (con min_order_amount), product_limit (con products_remaining), expired, usage_limit, not_open_here, not_applicable. L'id coupon Stripe non esce mai.

Errori: validation_error (422, corpo non valido), not_found (404, evento non pubblicato o fuori dal grafo), validation_error (422, beneficiario non gestito dalla crew), rate_limited (429, il tetto di controlli al minuto, o il limite di tentativi qui sotto).

POST /v1/access-codes/validate

Controlla un codice di prevendita o di accesso: i tipi di biglietto nascosti che sblocca (mai elencati da GET /v1/events/{id}/ticket-tiers), se l'organizzatore vuole mostrare solo quei tipi, e il tetto per ordine proprio del codice, o perché non funziona.

Scope write:checkout. Mai in cache. Non accetta Idempotency-Key (writeClass: check), stesso tetto di 300 controlli al minuto dei coupon.

CampoTipoObbligatorioNote
event_idnumbersìDeve essere un evento il cui beneficiario è gestito dalla crew
codestringsìDa 1 a 64 caratteri stampabili
client_ipstringnoL'indirizzo dell'acquirente, usato solo per contare i suoi tentativi falliti
{
  "valid": true, "code": "VIP", "name": "Members", "exclusive": true, "max_per_order": 2,
  "tiers": [{ "id": "8f14e45f-ceea-467a-9575-b2c45e4f2a11", "name": "Presale", "description": null, "price": 25.0, "currency": "EUR", "status": "on_sale", "sale_start": null, "sale_end": null, "max_per_order": 4, "checkout_url": "https://www.sway.events/event/4521?ref=api&code=VIP" }]
}

exclusive: true significa che l'organizzatore vuole mostrare solo questi tipi sbloccati una volta inserito il codice. Gli id interni propri del codice non escono mai. Altri valori di reason per il rifiuto: not_found, inactive, not_started, ended, usage_limit.

Tetti di tentativo (entrambi gli endpoint sopra). Oltre al tetto generale di controlli al minuto, un codice sconosciuto viene contato per chiave ed evento (300 tentativi falliti ogni 10 minuti) e, quando chi chiama inoltra client_ip, anche per indirizzo dell'acquirente (10 tentativi falliti ogni 15 minuti): oltre l'uno o l'altro limite la risposta è rate_limited (429) ancora prima che il codice venga cercato.

Errori: stessi di POST /v1/coupons/validate.


Shop

Le tre letture accettano una chiave pubblicabile; il preventivo e il checkout no.

Il merch venduto dalle pagine che la tua crew gestisce, per uno shop tuo: il catalogo di una pagina, un prodotto, ciò che vende un evento, il prezzo di un carrello e un checkout Stripe sull'account della pagina che vende. L'acquirente paga su Stripe e torna sulle pagine di Sway; Sway registra l'ordine, scrive all'acquirente e al venditore, e il venditore gestisce spedizione, recessi e rimborsi dalla schermata Shop della pagina.

Una pagina vende appena il suo shop è aperto; prima, il suo catalogo risulta vuoto. I prezzi includono l'IVA e sono in centesimi di euro. Nessuna route dà lo stock: una variante è available oppure no.

GET /v1/shop/products

Il merch pubblicato di una pagina della crew, con l'identità del venditore che l'acquirente deve vedere prima di pagare. Le esclusive di evento non ci sono: leggile sull'evento.

Scope read:shop. Chiavi pubblicabili: sì. In cache 30 s.

ParametroTipoObbligatorioNote
seller_typestringsìpromoter, artist o venue
seller_idnumbersìUna pagina che la crew gestisce
{
  "seller": {
    "type": "promoter", "id": 697, "name": "Insomnia Nights", "image_url": null,
    "legal_name": "Insomnia Nights SRL", "address": "Rue de la Loi 1, 1000 Brussels, BE",
    "company_number": "BE0123456789", "contact_email": "[email protected]",
    "vat_regime": "registered", "return_shipping_paid_by": "buyer"
  },
  "installments": { "provider": "stripe", "publishable_key": "pk_live_...", "stripe_account": "acct_1Abc...", "country": "BE", "methods": ["klarna"] },
  "data": [
    {
      "id": "3f2c5e1a-7b4d-4c1e-9a2b-5d6e7f8a9b0c", "slug": "tour-hoodie-2026", "title": "Tour Hoodie 2026",
      "description": "Heavyweight cotton.",
      "images": [{ "url": "https://assets.sway.events/shop/hoodie.webp", "alt": null, "width": 1200, "height": 1200 }],
      "options": ["Size"],
      "variants": [{ "id": "9a1b7c2d-3e4f-4a5b-8c6d-7e8f9a0b1c2d", "options": ["M"], "price_cents": 4500, "available": true, "max_per_order": 20 }],
      "from_price_cents": 4500, "currency": "EUR", "available": true,
      "fulfilment_modes": ["delivery", "pickup_event"], "country_rule": "all", "countries": [], "exclusive": false,
      "manufacturer": { "name": "Insomnia Nights SRL", "address": "Rue de la Loi 1, 1000 Brussels", "email": "[email protected]" },
      "eu_responsible": null, "safety_info": "Wash at 30 degrees.", "preparation_days": 2, "preorder": null,
      "seller": { "type": "promoter", "id": 697 }
    }
  ]
}

Un elenco piccolo: racchiuso in { "data": [...] }, senza pagination. Una pagina che la crew non gestisce, o il cui shop non è aperto, risponde { "seller": null, "data": [] }. fulfilment_modes elenca delivery, pickup_event e pickup_location; country_rule è all, only o except, applicato a countries. Errori: validation_error (422, seller_type o seller_id mancante o non valido).

preorder è null, oppure { "status": "open", "ships_on": "2026-11-15", "ends_on": null } per un prodotto venduto prima di essere pronto: viene spedito dal ships_on e accetta preordini fino a ends_on quando è impostato. status è closed quando il preordine si è chiuso prima del giorno di spedizione; le varianti riportano allora available: false. Nel giorno di spedizione il prodotto si vende come gli altri e preorder torna null.

installments è null, oppure l'offerta di pagamento a rate di Stripe sull'account della pagina che vende. Quando è presente, mostra il Payment Method Messaging Element di Stripe con publishable_key e stripe_account (Stripe.js), mai testi tuoi: la pubblicità del credito rateale è regolamentata. methods elenca ciò che il Checkout offre (klarna); country è quello del venditore, un valore predefinito finché l'acquirente non sceglie il paese di consegna. Il prodotto e lo shop di un evento portano lo stesso campo.

GET /v1/shop/products/{id}

Un prodotto per la sua pagina, restituito direttamente, con seller come identità completa del venditore. Con event_id, il prodotto come lo vende quell'evento, esclusive comprese.

Scope read:shop. Chiavi pubblicabili: sì. In cache 30 s.

ParametroTipoObbligatorioNote
event_idnumbernoUn evento del grafo della crew con lo shop aperto

Errori: not_found (404, prodotto sconosciuto o non pubblicato, una pagina che la crew non gestisce, un'esclusiva chiesta senza il suo evento, o un evento con lo shop chiuso).

GET /v1/events/{id}/shop

Ciò che vende un evento del grafo della crew: i prodotti del suo promoter incassante, tutti o una selezione, esclusive comprese, finché lo shop dell'evento è aperto; e se l'acquirente può ancora scegliere il ritiro all'evento.

Scope read:shop. Chiavi pubblicabili: sì. In cache 30 s.

{
  "event_id": 4521,
  "seller": { "type": "promoter", "id": 697, "name": "Insomnia Nights", "legal_name": "Insomnia Nights SRL" },
  "pickup_at_event": { "open": true, "cutoff_at": "2026-10-10T20:00:00.000Z", "instructions": "Merch stand, left of the bar, from doors" },
  "data": [{ "id": "3f2c5e1a-7b4d-4c1e-9a2b-5d6e7f8a9b0c", "title": "Tour Hoodie 2026", "exclusive": false }]
}

seller porta l'identità completa del venditore, come nell'elenco sopra. L'elenco è vuoto, con seller e pickup_at_event a null, finché lo shop dell'evento è chiuso, o quando il promoter incassante non è una pagina della crew, perché il checkout non potrebbe vendere per lui. Errori: not_found (404, evento non pubblicato o fuori dal grafo).

POST /v1/shop/quote

Calcola un carrello prima del checkout: le righe dopo il coupon, le opzioni di consegna per una modalità e un paese, le modalità e i paesi che il carrello consente, e il totale. Nulla viene riservato.

Scope read:shop. Chiavi pubblicabili: no. Mai in cache. Limitato a 120 richieste al minuto per acquirente (client_ip).

CampoTipoObbligatorioNote
linesarraysìDa 1 a 20 voci { "variant_id": string, "quantity": number }, quantità da 1 a 20
modestringsìdelivery, pickup_event o pickup_location
event_idnumbernoL'evento da cui viene il carrello: sblocca le sue esclusive e il ritiro all'evento
countrystringper deliveryCodice a due lettere del paese di consegna
coupon_codestringnoUn codice della pagina che vende
method_keystringnoL'opzione di consegna scelta, da shipping_options
client_ipstringnoL'indirizzo dell'acquirente, per contare le sue richieste
{
  "currency": "EUR",
  "lines": [{ "variant_id": "9a1b7c2d-3e4f-4a5b-8c6d-7e8f9a0b1c2d", "name": "Tour Hoodie 2026 · M", "quantity": 2, "unit_cents": 4500, "discount_cents": 900 }],
  "subtotal_cents": 9000,
  "discount_cents": 900,
  "shipping_options": [
    { "key": "b1c2d3e4-f5a6-4b7c-8d9e-0f1a2b3c4d5e", "kind": "delivery", "name": "bpost, 2 to 4 days", "carrier": "bpost", "amount_cents": 590, "min_days": 2, "max_days": 4, "pickup_address": null, "instructions": null }
  ],
  "selected_option": "b1c2d3e4-f5a6-4b7c-8d9e-0f1a2b3c4d5e",
  "shipping_cents": 590,
  "total_cents": 8690,
  "available_modes": ["delivery", "pickup_event"],
  "deliverable_countries": ["BE", "FR", "NL"],
  "coupon": { "code": "TOUR10", "discount_cents": 900 },
  "ships_on": null
}

ships_on è il giorno di spedizione dell'ordine quando il carrello contiene un preordine, il più tardivo; l'acquirente lo vede prima di pagare, anche sulla pagina Stripe. È null per un carrello disponibile. not_ready_for_event rifiuta il ritiro all'evento di un carrello spedito dopo; preorder_closed porta il variant_id il cui preordine è chiuso.

Errori: validation_error (422) con una reason: invalid_cart, invalid_mode, unavailable, out_of_stock, max_per_order, mixed_sellers, country_required, country_not_allowed, no_method, mode_not_allowed, pickup_closed, preorder_closed, not_ready_for_event, o un motivo che inizia con coupon_. max_per_order porta variant_id e max; un rifiuto di consegna porta available_modes e deliverable_countries. Poi conflict (409, motivo not_ready: la pagina non può ancora vendere), not_found (404, un evento fuori dal grafo) e rate_limited (429).

POST /v1/shop/cart-check

Indica quali righe del carrello che un acquirente conserva puntano a un prodotto uscito dallo shop: ritirato, archiviato o eliminato, o una variante disattivata o rimossa. Togli quelle righe dal carrello. Una variante esaurita, una pagina che non può ancora vendere o un evento con lo shop chiuso conserva la sua riga: può cambiare di nuovo, e il preventivo lo spiega. Anche una riga di una pagina che la tua crew non gestisce viene restituita.

Scope read:shop. Chiavi pubblicabili: no. Mai in cache. Limitato a 120 richieste al minuto per acquirente (client_ip).

CampoTipoObbligatorioNote
linesarraysìDa 1 a 20 voci { "variant_id": string, "product_id": string }
client_ipstringnoL'indirizzo dell'acquirente, per contare le sue richieste
{ "gone": ["9a1b7c2d-3e4f-4a5b-8c6d-7e8f9a0b1c2d"] }

Controlla il carrello quando l'acquirente torna sul sito e quando il carrello si apre.

Errori: validation_error (422, una riga mancante o malformata) e rate_limited (429).

POST /v1/shop/checkout-sessions

Apre un checkout Stripe ospitato per il merch di una pagina che la crew gestisce, sull'account Stripe di quella pagina, e restituisce la sua url. L'acquirente paga lì e arriva sulla pagina di ringraziamento di Sway e sulla pagina del suo ordine, oppure su /cancel, mai sul sito del chiamante. Sway registra l'ordine, scrive all'acquirente e al venditore, e trattiene la sua commissione dal venditore.

Scope write:shop:checkout (opt-in). Chiavi pubblicabili: no. Mai in cache. Non accetta Idempotency-Key: invia idempotency_key nel body. Limitato a 60 richieste al minuto per acquirente (client_ip), 5000 checkout al giorno per crew e 30 al giorno per email dell'acquirente.

Il body è quello del preventivo, più:

CampoTipoObbligatorioNote
buyer_emailstringsìDove vanno la conferma e il link dell'ordine
idempotency_keystringnoUn UUID per clic su Paga; un nuovo tentativo con lo stesso valore restituisce la stessa sessione
localestringnoLa lingua dell'acquirente per Stripe e le email: en, fr, de, es, it, nl o uk
referencestringnoLibero, al massimo 64 caratteri di [A-Za-z0-9._:-], conservato sull'ordine

Campi rifiutati con validation_error se inviati, perché ciascuno è risolto lato server: amount, price, unit_amount, unit_cents, price_cents, total_cents, shipping_cents, currency, seller_type, seller_id, payee, stripe_account, application_fee_amount, fee, commission, success_url, cancel_url, return_url, discount_id, stripe_coupon_id, user_id.

{
  "lines": [{ "variant_id": "9a1b7c2d-3e4f-4a5b-8c6d-7e8f9a0b1c2d", "quantity": 2 }],
  "mode": "delivery",
  "country": "BE",
  "coupon_code": "TOUR10",
  "buyer_email": "[email protected]",
  "idempotency_key": "b1e2c3d4-5f60-4a1b-9c2d-3e4f5a6b7c8d"
}

Risposta 200 OK: { "url": "https://checkout.stripe.com/c/pay/cs_live_..." }

Lo stock resta trattenuto per i 30 minuti della sessione; ciò che l'acquirente non paga torna disponibile quando la sessione scade. Un consenso marketing inviato dal chiamante viene ignorato: una chiave API non iscrive mai un acquirente in un CRM.

Errori: quelli del preventivo, più validation_error per un buyer_email o una reference non validi, o per prodotti di una pagina che la crew non gestisce; conflict (409, not_ready, o lo stock è cambiato sotto il carrello); rate_limited (429, vedi Retry-After); upstream_error (502, Stripe non è riuscito a creare la sessione).


Ambassador

Il programma di referral del promoter, mostrato sul sito proprio del partner invece che su sway.events. Nessuno di questi endpoint accetta una chiave pubblicabile: eseguili da un server, mai da un browser. Sono in gioco tre credenziali: la chiave API sway_sk_... (quale crew sta chiamando), una sessione membro amb_st_... che viaggia nell'header X-Ambassador-Session (quale ambassador è connesso, generata alla registrazione o all'accesso, 8 ore), e il cookie proprio di prima parte del partner che collega un visitatore a quella sessione.

La registrazione e l'accesso sono moduli propri del partner; Sway qui non mostra nessuna pagina e non vede nessun widget, quindi il partner gestisce la propria protezione anti-bot (Turnstile, hCaptcha, un honeypot). La protezione propria di Sway è strutturale: l'iscrizione è spenta finché il promoter non la attiva, consuma un invito storefront dedicato con la sua quota, ed ogni endpoint qui sotto è limitato per chiave, per visitatore e, dove conta, per indirizzo email.

GET /v1/ambassador-program

Tutto ciò che serve a una pagina "diventa ambassador": nome e descrizione del programma, lo sconto che offre, se sta accettando persone proprio ora, e le parole proprie del promoter per XP, livelli, missioni e badge. Solo dati pubblici, nessun membro e nessun conteggio dei membri.

Scope read:ambassadors. In cache 120 s.

ParametroValoriPredefinitoSignificato
owner_typepromoter, venueobbligatorioIl programma di chi
owner_idintero positivoobbligatorioL'id della pagina
{
  "ok": true,
  "program": { "name": "Insomnia Ambassadors", "description": "Bring your friends, earn rewards." },
  "owner": { "type": "promoter", "id": 697 },
  "discount": { "percentage": 15 },
  "signup": { "open": true, "seats_left": 42 }
}

La risposta ripete anche name, description, signup_open e gamification_enabled al primo livello, per i siti costruiti sulla prima versione del boilerplate Insomnia. Leggi i campi annidati: sono quelli che questa pagina descrive.

Un proprietario fuori dal grafo della crew risponde con lo stesso 404 di uno senza programma: questo endpoint non deve mai dire quali promoter che la chiave non gestisce ne hanno uno.

Errori: validation_error (422, owner_type / owner_id mancante o malformato: questo endpoint precede la regola del 400 per i parametri di query e mantiene la sua risposta), not_found (404).

GET /v1/ambassador-leaderboard

La classifica del programma, solo quando il promoter la pubblica: posizione, nome e iniziale del cognome, punteggio. Una classifica per fatturato dà l'ordine, mai gli importi.

Scope read:ambassadors. In cache 60 s.

ParametroValoriPredefinitoSignificato
owner_typepromoter, venueobbligatorioIl programma di chi
owner_idintero positivoobbligatorioL'id della pagina
limitintero, da 1 a 10025Quante posizioni
{
  "metric": "tickets",
  "data": [
    { "rank": 1, "name": "Melanie P.", "score": 42, "tier": { "name": "Gold", "color": "#f5c542" } },
    { "rank": 2, "name": "Tom D.", "score": 37, "tier": null }
  ]
}

metric è tickets, xp o revenue. Con revenue, score è null: l'ordine mostra chi ha venduto di più, mai quanto. tier è impostato solo mentre i livelli del programma sono attivi.

Errori: validation_error (400, owner_type / owner_id mancante o malformato), not_found (404, nessun programma, o il programma non pubblica la sua classifica).

GET /v1/ambassador-rewards

I premi su cui i membri spendono i loro crediti: costo, quanti ne restano, quando, e il livello di cui hanno bisogno. Vuoto mentre i crediti sono spenti.

Scope read:ambassadors. In cache 60 s.

ParametroValoriPredefinitoSignificato
owner_typepromoter, venueobbligatorioIl programma di chi
owner_idintero positivoobbligatorioL'id della pagina
{
  "credits_enabled": true,
  "label_singular": "credit",
  "label_plural": "credits",
  "data": [
    {
      "id": "7c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
      "name": "Backstage tour",
      "description": "Meet the crew before doors open.",
      "image_url": null,
      "cost": 50,
      "stock_left": 8,
      "max_per_member": 1,
      "available_from": null,
      "available_until": "2026-12-31T23:00:00+00:00",
      "available": true,
      "event": { "id": 4521, "title": "Insomnia Night: Autumn Opening" },
      "min_tier": { "id": "2b3c4d5e-6f70-4812-9a3b-4c5d6e7f8091", "name": "Gold", "min_xp": 500 },
      "claim_prompt": "Which night suits you?"
    }
  ]
}

stock_left è null quando il premio non ha un limite di scorte. available è false fuori dalle sue date o una volta esaurito. claim_prompt è la domanda a cui il membro risponde quando lo riscatta (l'answer di POST /v1/ambassador-claim).

Errori: validation_error (400, owner_type / owner_id), not_found (404, nessun programma).

GET /v1/ambassador-quests

Le missioni che i membri possono intraprendere ora: obiettivo, date e premi. Il progresso proprio di un membro connesso sta in GET /v1/ambassador-me, mai qui.

Scope read:ambassadors. In cache 60 s.

ParametroValoriPredefinitoSignificato
owner_typepromoter, venueobbligatorioIl programma di chi
owner_idintero positivoobbligatorioL'id della pagina
{
  "enabled": true,
  "quest_noun": "quest",
  "xp_label": "XP",
  "badge_noun": "badge",
  "data": [
    {
      "id": "3d4e5f60-7182-4a93-b4c5-d6e7f8091a2b",
      "name": "Bring five friends",
      "description": null,
      "image_url": null,
      "metric": "tickets",
      "scope": "event",
      "threshold": 5,
      "event": { "id": 4521, "title": "Insomnia Night: Autumn Opening" },
      "starts_at": null,
      "ends_at": "2026-10-03T20:00:00+00:00",
      "xp_reward": 100,
      "credit_reward": 20,
      "badge": { "id": "4e5f6071-8293-4a4b-85c6-e7f8091a2b3c", "name": "Recruiter", "image_url": null, "icon": "users", "rarity": "silver" }
    }
  ]
}

metric è ciò di cui il membro deve raggiungere threshold: orders, tickets, revenue_cents, credits_earned, reward_claimed, profile_complete o manual (lo spunta l'organizzatore). scope è lifetime (da quando il membro si è iscritto), event (su un evento, o su un singolo evento qualsiasi quando event è null) o window (tra starts_at e ends_at). data è vuoto mentre la gamification è spenta.

Errori: validation_error (400, owner_type / owner_id), not_found (404, nessun programma).

GET /v1/ambassadors

I membri del programma per gli strumenti propri del promoter, più recenti prima: nome, email, Instagram, codice, stato, vendite, crediti, XP e livello. Mai il telefono, il token di accesso o l'account Sway.

Scope read:ambassador-members (solo proprietario: solo il proprietario della crew può concederlo). Mai in cache (no-store).

ParametroValoriPredefinitoSignificato
owner_typepromoter, venueobbligatorioIl programma di chi
owner_idintero positivoobbligatorioL'id della pagina
qtestonessunoConfronta nome, email, Instagram o codice
statusactive, pending, suspendednessunoFiltra per stato
limitintero, da 1 a 10025Dimensione della pagina
cursorstringa opacanessunoDa una pagina precedente
{
  "data": [
    {
      "id": "5f0c6a0e-2b1d-4c3e-9f8a-7b6c5d4e3f21", "first_name": "Melanie", "last_name": "Pauwels",
      "email": "[email protected]", "instagram_handle": "mel", "locale": "nl", "status": "active", "code": "MELANPAU",
      "sales": { "orders": 4, "tickets": 9, "revenue_cents": 27000 }, "credit_balance": 12, "xp": 340, "tier": "Gold",
      "joined_at": "2026-09-01T00:00:00.000Z"
    }
  ],
  "pagination": { "next_cursor": null, "has_more": false, "limit": 25 }
}

Errori: validation_error (400, owner_type / owner_id / status), invalid_cursor (400), not_found (404, nessun programma per questo proprietario).

GET /v1/ambassadors/{id}/ledger

La storia dei crediti di un membro, più recente prima, con il suo saldo corrente: cosa ha aggiunto o tolto ogni vendita, riscatto, missione o assegnazione manuale.

Scope read:ambassador-members. Mai in cache.

ParametroValoriPredefinitoSignificato
limitintero, da 1 a 10025Dimensione della pagina
cursorstringa opacanessunoDa una pagina precedente
{
  "member": { "id": "5f0c6a0e-2b1d-4c3e-9f8a-7b6c5d4e3f21", "first_name": "Melanie", "last_name": "Pauwels", "status": "active" },
  "credits_enabled": true,
  "balance": 12,
  "data": [{ "id": "7a1b...", "amount": 3, "reason": "ticket_sale", "note": null, "created_at": "2026-09-20T18:00:00.000Z" }],
  "pagination": { "next_cursor": null, "has_more": false, "limit": 25 }
}

Il proprietario che la crew deve gestire viene letto dal programma proprio del membro, mai dalla richiesta: il membro di un'altra crew risponde con lo stesso 404 di un id sconosciuto. Errori: not_found (404, id malformato o membro fuori dai programmi della crew), invalid_cursor (400).

GET /v1/ambassador-stats

Come sta andando il programma: membri per stato e quota di quelli attivi che vendono, le vendite portate dai suoi codici (a vita, e tra from e to quando forniti), e i dieci migliori codici sullo stesso periodo.

Scope read:ambassador-members. Mai in cache.

ParametroValoriPredefinitoSignificato
owner_typepromoter, venueobbligatorioIl programma di chi
owner_idintero positivoobbligatorioL'id della pagina
from / todata o data-ora ISO 8601nessunoFinestra, al massimo 400 giorni di distanza, to dopo from
{
  "members": { "total": 12, "active": 8, "pending": 3, "suspended": 1, "selling": 3, "selling_rate": 0.375 },
  "lifetime": { "orders": 20, "tickets": 41, "revenue_cents": 90000 },
  "window": null,
  "top_codes": []
}

Errori: validation_error (400, owner_type / owner_id, o una finestra invertita o oltre 400 giorni), not_found (404, nessun programma).

POST /v1/ambassador-register

Qualcuno si iscrive al programma dal sito proprio del partner. Restituisce il suo codice e una sessione membro, così lo storefront può mostrargli subito la sua pagina.

Scope write:ambassadors (da attivare). Mai in cache. Accetta Idempotency-Key. Limitato a 30 registrazioni al minuto per visitatore (client_ip, dimensionato per il wifi condiviso di un locale), e 300 al giorno per programma.

CampoTipoObbligatorioNote
owner_typestringsìpromoter o venue
owner_idnumbersìL'id della pagina
first_name / last_namestringsìAl massimo 120 caratteri
emailstringsìEmail valida; l'unico modo per tornare a una pagina persa
phonestringsìUn numero di telefono reale; la chiave di deduplicazione per il programma
localestringnoAl massimo 8 caratteri
{ "owner_type": "promoter", "owner_id": 697, "first_name": "Jane", "last_name": "Doe", "email": "[email protected]", "phone": "+32470000000" }

Risposta: 201 per un membro genuinamente nuovo, 200 quando esisteva già:

{ "created": true, "member_id": "6b1d...", "code": "MELANPAU", "status": "active", "session": { "token": "amb_st_...", "expires_at": "2026-09-01T18:00:00.000Z" } }

Un numero di telefono già iscritto in questo programma viene rifiutato, non connesso: chi ha digitato il numero non è per forza il suo titolare.

Errori: validation_error (422, campi mancanti o non validi, o un'email usa e getta/bloccata), not_found (404, proprietario fuori dal grafo della crew), conflict (409, signup_closed a meno che il promoter abbia attivato l'iscrizione pubblica e un invito storefront attivo abbia ancora quota, oppure already_registered per un telefono già iscritto), rate_limited (429).

POST /v1/ambassador-login

Invia via email a un membro un link di accesso monouso che riporta al sito proprio del partner.

Scope write:ambassadors. Mai in cache. Accetta Idempotency-Key. Limitato a 8 al minuto per visitatore (client_ip), e 5 all'ora per indirizzo email per l'invio effettivo.

CampoTipoObbligatorioNote
owner_typestringsìpromoter o venue
owner_idnumbersìL'id della pagina
emailstringsìDove inviare il link
return_originstringsìUna delle origini registrate su questa chiave
return_pathstringnoUn percorso relativo su quell'origine, predefinito /ambassador

Risposta 202, sempre lo stesso corpo: { "status": "accepted" }. Un indirizzo sconosciuto, un membro sospeso, un programma chiuso e un invio genuino sono indistinguibili: un modulo di accesso che rispondesse diversamente a un indirizzo noto sarebbe un oracolo di appartenenza. return_origin e return_path vengono riconvalidati in SQL, quindi non c'è modo di costruire un redirect aperto.

Errori: validation_error (422, corpo non valido, o return_origin non tra le origini registrate sulla chiave), rate_limited (429, per visitatore o per indirizzo email).

POST /v1/ambassador-login-exchange

La pagina di arrivo del partner riceve ?amb_token=... e lo scambia, lato server, con una sessione membro.

Scope write:ambassadors. Mai in cache. Accetta Idempotency-Key. Limitato a 20 tentativi al minuto per visitatore (client_ip).

CampoTipoObbligatorioNote
tokenstringsìIl valore amb_lt_... dalla query string
{ "session": { "token": "amb_st_...", "expires_at": "2026-09-01T18:00:00.000Z" }, "return_url": "https://www.example.com/ambassador" }

Monouso: il token viene speso dallo stesso aggiornamento del database che lo legge, quindi due tocchi in un client di posta che precarica i link non possono avere entrambi successo.

Errori: invalid_key (401, token malformato, sconosciuto, scaduto o già usato, tutti identici), rate_limited (429).

GET /v1/ambassador-me

La pagina propria del membro connesso: codice, vendite, crediti e premi, più XP, livello, missioni e badge quando il promoter ha attivato la gamification.

Scope read:ambassadors, più un header X-Ambassador-Session. Mai in cache.

{
  "membership": {
    "member": { "id": "0b7c9d1e-2f3a-4b5c-8d6e-7f8091a2b3c4", "first_name": "Melanie", "last_name": "Paulus", "status": "active", "code": "MELANPAU" },
    "program": { "id": "5e6f7081-92a3-4b4c-95d6-e7f8091a2b3c", "name": "Insomnia Ambassadors", "description": null, "leaderboard_visible": true, "owner": { "id": 697, "type": "promoter", "name": "Insomnia", "image_url": null } },
    "owner": { "id": 697, "type": "promoter", "name": "Insomnia", "image_url": null },
    "stats": { "orders": 4, "tickets": 9, "revenue_cents": 27000 },
    "credits": { "enabled": true, "balance": 12, "label_singular": "credit", "label_plural": "credits" },
    "game": { "enabled": true, "xp_label": "XP", "lifetime_xp": 340, "tier": { "id": "2b3c4d5e-6f70-4812-9a3b-4c5d6e7f8091", "name": "Gold", "min_xp": 300, "color": "#f5c542" }, "next_tier": null, "badges_earned": 2, "quests_completed": 3 },
    "tiers": [],
    "quests": [],
    "badges": [],
    "xp_ledger": [],
    "rewards": [],
    "claims": [],
    "ledger": [],
    "events": [],
    "leaderboard": []
  },
  "session": { "expires_at": "2026-09-26T20:00:00+00:00" }
}

La stessa pagina membro che mostra sway.events, qui abbreviata: game porta anche le parole proprie del programma (tier_noun, quest_noun, badge_noun) e next_tier dice quanto XP resta per salire (null in cima). quests, badges, tiers e xp_ledger si riempiono solo mentre la gamification è attiva, rewards, claims e ledger (la storia dei crediti) solo mentre i crediti sono attivi. events elenca gli eventi su cui funziona il codice del membro, con il suo sconto, e leaderboard la classifica quando il promoter la pubblica, con revenue_cents su ogni posizione solo in una classifica per fatturato, come su sway.events.

access_token (la credenziale permanente del membro per la sua pagina su sway.events) e il suo numero di telefono non compaiono mai qui, qualunque cosa contenga la riga sottostante.

Errori: invalid_key (401, l'header manca o è malformato, o la sessione è scaduta o non valida).

POST /v1/ambassador-profile

Il membro connesso corregge il proprio nome, email o lingua.

Scope write:ambassadors, più sessione. Mai in cache. Accetta Idempotency-Key. Limitato a 20 al minuto per visitatore (client_ip).

CampoTipoObbligatorioNote
first_name / last_name / email / localestringnoQualsiasi sottoinsieme; almeno uno è richiesto

Il numero di telefono non si può cambiare qui (è la chiave di deduplicazione: cambiarlo sarebbe una fusione, non una modifica), né lo stato, il codice, i crediti o l'XP: il codice non segue un cambio di nome.

Errori: invalid_key (401, sessione), validation_error (422, email non valida, nessun campo fornito, o un'email usa e getta/bloccata), rate_limited (429).

POST /v1/ambassador-claim

Il membro connesso spende crediti su un premio. Chi chiama non nomina mai un prezzo: costo, saldo, scorte, il limite per membro e il livello richiesto vengono tutti letti e applicati dal registro sotto un blocco di riga.

Scope write:ambassadors, più sessione. Mai in cache. Accetta Idempotency-Key. Limitato a 20 al minuto per visitatore (client_ip).

CampoTipoObbligatorioNote
reward_idstringsìUn UUID
answerstringnoAl massimo 500 caratteri, quando il premio pone una domanda

Risposta 201 in caso di successo, con claim_id e status.

Errori: invalid_key (401, nessuna sessione), not_found (404, premio sconosciuto), conflict (409, inactive, not_started, ended, out_of_stock, max_per_member, insufficient_credits), validation_error (403, member_not_active o tier_locked; 422, answer_required o un corpo malformato).

Due rifiuti, member_not_active e tier_locked, rispondono 403 con il codice validation_error: leggi il campo reason per distinguerli.

POST /v1/ambassador-email-preference

Il membro connesso disattiva, o riattiva, le email del programma.

Scope write:ambassadors, più sessione. Mai in cache. Accetta Idempotency-Key. Limitato a 20 al minuto per visitatore (client_ip).

CampoTipoObbligatorioNote
opt_outbooleansì

Un interruttore, non la disiscrizione a senso unico del piè di pagina di un'email: il portale è l'unico posto in cui un membro può provare chi è e tornare sulla propria decisione.

Errori: invalid_key (401, sessione), validation_error (422, opt_out mancante o non booleano).

POST /v1/ambassador-logout

Revoca la sessione nominata in X-Ambassador-Session.

Scope write:ambassadors. Mai in cache. Accetta Idempotency-Key.

Risposta: sempre 200 { "ok": true }, che la sessione esistesse o no, e che l'header fosse stato inviato o no: uscire non è un posto dove imparare qualcosa.

Errori: nessuno oltre all'autenticazione.

GET /v1/ambassador-code

Risolve un ?amb=CODE su una pagina biglietti: se questo codice è attivo su questo evento, quanto vale, e di chi è. Un'anteprima, mai un prezzo: le condizioni vengono rilette dentro il checkout dalla stessa apertura.

Scope read:ambassadors. In cache 30 s. Limitato a 60 ricerche al minuto per chiave.

ParametroValoriPredefinitoSignificato
event_idintero positivoobbligatorioL'evento su cui viene controllato il codice
codetesto, al massimo 64 caratteriobbligatorioIl codice così com'è digitato
{ "code": "MELANPAU", "discount_id": "f4c8b1a2-...", "ambassador_first_name": "Melanie", "percentage": 15, "amount": null, "min_order_amount": 0 }

discount_id è ciò che uno storefront passa a POST /v1/checkout-sessions come discount_id: quell'endpoint rifiuta del tutto un codice coupon grezzo. Un codice che non esiste, non è su questo evento, appartiene a un membro sospeso, o appartiene a un altro tenant, rispondono tutti allo stesso 404: qualcosa di più specifico renderebbe questo un oracolo di enumerazione su ogni promoter della piattaforma.

Errori: validation_error (422, event_id / code mancante o malformato), not_found (404), rate_limited (429).


MCP e webhook

Altri due modi per raggiungere gli stessi dati, ciascuno documentato sulla propria pagina:

  • POST /v1/mcp è il server MCP di Sway: gli endpoint di lettura sopra, esposti come strumenti in sola lettura per un assistente IA su JSON-RPC (HTTP streamabile, stateless), dietro una chiave che ha lo scope mcp:access. Vedi Il server MCP di Sway.
  • I webhook, configurati in Admin della crew → API → Webhook, inviano eventi (ordini pagati e rimborsati, eventi pubblicati e aggiornati, richieste di booking, vendite ambassador, risposte ai moduli, pagamenti) al server proprio di un partner appena accadono, firmati alla maniera Standard Webhooks. Vedi Webhook.