Autenticazione
Ogni richiesta porta una chiave API come token Bearer:
curl https://www.sway.events/api/v1/artists \
-H "Authorization: Bearer sway_sk_YOUR_KEY"
La chiave identifica la tua crew. Non esiste alcun parametro di tenant da nessuna parte nell'API: cosa può vedere una chiave lo decide la crew a cui appartiene, mai la richiesta.
Due tipi di chiave
| Chiave segreta | Chiave pubblicabile | |
|---|---|---|
| Prefisso | sway_sk_ | sway_pk_ |
| Gira su | Il tuo server | Il browser di un visitatore |
| Scope | Qualsiasi | Solo le letture pubbliche: read:profile, read:artists, read:promoters, read:events, read:venues, read:content, read:shop |
| Dove risponde | Ovunque | Solo sui websites elencati sulla chiave (verificati contro l'Origin del browser) |
| Inviata come | Authorization: Bearer ... | Authorization: Bearer ..., oppure ?key= quando un header non è praticabile |
Usa una chiave segreta per tutto quello che fa il tuo server, e per qualsiasi cosa scriva, venda biglietti o tocchi i membri. Non spedirla mai a un browser o a un'app: chiunque possa leggere le tue pagine può leggere la chiave.
Usa una chiave pubblicabile quando un browser deve chiamare l'API direttamente, per esempio un sito statico senza server. Legge solo quello che Sway mostra già pubblicamente, risponde solo sui propri siti, e riceve una quota più piccola per indirizzo di visitatore, oltre a quella della chiave.
?key= viene rifiutata con 400 key_in_query, perché gli URL finiscono nei log e nelle cronologie del browser. Se succede, la chiave va trattata come trapelata: ruotala.Ciclo di vita di una chiave
Le chiavi si gestiscono in Admin della crew → API (piani Studio e Roster, permesso Gestire API).
| Azione | Cosa succede |
|---|---|
| Crea | Nome, tipo, scope, websites o indirizzi storefront, e una scadenza (mai, 30, 90 o 365 giorni). La chiave completa viene mostrata una volta; dopo si vedono solo il suo inizio e le ultime quattro cifre |
| Modifica | Cambia gli scope e gli elenchi di indirizzi; si applica subito |
| Sospendi | L'interruttore d'emergenza. Ogni richiesta risponde 403 key_suspended finché qualcuno non clicca Riattiva. Niente viene eliminato |
| Ruota | POST /v1/keys/rotate con una chiave che ha write:keys restituisce la sua sostituta: stesso nome, tipo, scope e limiti. La vecchia chiave continua a funzionare per grace_hours (da 1 a 168, predefinito 24), poi scade. Una chiave ruota una volta, al massimo una volta all'ora, e il proprietario della crew riceve un'email ogni volta |
| Revoca | Definitiva. Le richieste rispondono 401 invalid_key. Quando la chiave era stata ruotata, Revoca anche le chiavi che l'hanno sostituita revoca tutta la catena: usala quando la chiave è trapelata, dato che chiunque possieda una chiave trapelata con write:keys potrebbe averla ruotata |
| Scade | Dopo expires_at, le richieste rispondono 401 invalid_key |
Una chiave revocata, scaduta o sconosciuta risponde tutte allo stesso modo con 401 invalid_key: l'API non dice mai a chi chiama se una chiave che non conosce sia mai esistita. Il pannello Attività su ogni chiave mostra quanto ha usato dei suoi limiti e le sue ultime chiamate.
Se l'abbonamento della crew scade, ogni chiave risponde 403 subscription_required finché non torna attivo.
Scope
Gli scope si scelgono alla creazione della chiave e si possono modificare in seguito. Una richiesta senza lo scope richiesto dal suo endpoint risponde 403 missing_scope.
| Scope | Apre | Predefinito |
|---|---|---|
read:profile | Il profilo della crew stessa: /v1/crew, /v1/crew/pages | Sì |
read:artists | Il roster, ogni artista, i suoi eventi, calendari e press kit | Sì |
read:promoters | I promoter che gestisci, i loro eventi, locali e artisti | Sì |
read:events | Eventi, line-up, giorni, orari, tipi di biglietto, disponibilità, commissioni, galleria, prevendite | Sì |
read:venues | Locali, i loro eventi e la capienza | Sì |
read:content | Notizie, partner, moduli e press kit | Sì |
read:ambassadors | La pagina pubblica del programma ambassador, la classifica, i premi e le missioni, e la pagina personale di un membro connesso | No |
read:ambassador-members | I membri, i loro estratti conto e le statistiche del programma. Dati personali: solo il proprietario della crew | No |
read:export | L'intero catalogo come download NDJSON | No |
read:shop | Il merch delle pagine che gestisci: catalogo, prodotto, ciò che vende un evento; il calcolo e il controllo di un carrello | No |
write:bookings | Invia richieste di booking (POST /v1/booking-requests) | No |
write:newsletter | Aggiunge iscrizioni alla newsletter nel CRM di un promoter, e le toglie | No |
write:forms | Invia risposte ai tuoi moduli pubblici | No |
write:checkout | Apre checkout di biglietti a pagamento, e verifica coupon e codici di accesso | No |
write:checkout:free | Emette anche ordini di biglietti gratuiti e con sconto del 100%. Solo il proprietario della crew | No |
write:shop:checkout | Aprire checkout di merch per una pagina che gestisci | No |
write:ambassadors | Iscriversi, accedere e uscire, riscattare un premio, modificare i propri dati | No |
write:keys | Lascia che la chiave ruoti se stessa (POST /v1/keys/rotate) | No |
mcp:access | Usa la chiave con il server MCP | No |
GET /v1/me, /v1/usage, /v1/audit-log e /v1/genres non richiedono alcuno scope: qualsiasi chiave valida può chiamarli.
Gli scope "solo il proprietario della crew" possono essere concessi solo dal proprietario della crew; un membro con il permesso Gestire API può toglierli ma non aggiungerli. GET /v1/scopes restituisce questo elenco come JSON.
Siti web e indirizzi storefront
Due elenchi di indirizzi vivono su una chiave, entrambi impostati in Admin della crew → API:
- Siti web (chiavi pubblicabili): le uniche origini browser a cui la chiave risponde. Una richiesta da un altro sito risponde
403 origin_not_allowed. - Indirizzi storefront (chiavi con uno scope ambassador): dove Sway può rimandare un membro dopo un link di accesso via email. Il tuo sito passa l'indirizzo di ritorno con la richiesta; Sway lo accetta solo se la sua origine è nell'elenco, così il link non può mai essere indirizzato altrove. Una chiave con uno scope ambassador e un elenco vuoto continua a servire le pagine, ma non potrà mai far rientrare un membro.
Sono accettate solo origini https, più http su localhost per lo sviluppo. I percorsi vengono scartati: si salva e si confronta solo l'origine.
Limiti di richieste
L'API è costruita perché un sito partner molto frequentato continui a mostrare i suoi dati. Due idee lo permettono:
- Una risposta in cache è gratis. Le letture sono in cache per crew. Ripetere una richiesta che la cache ha già costa zero, e un
304 Not Modifiedcosta zero. - Si conta il lavoro, non le richieste. Solo una richiesta che deve leggere il database spende il budget di lavoro della chiave, e lo spende in proporzione a quanto legge.
| Piano | Lavoro al minuto | Lavoro al giorno |
|---|---|---|
| Studio | 120 | 20.000 |
| Roster | 300 | 100.000 |
Quanto costa una richiesta non in cache:
| Richiesta | Unità di lavoro |
|---|---|
Un dettaglio (/v1/events/{id}) | 1 |
| Una pagina di elenco | 1 per ogni blocco iniziato di 25 elementi (limit=100 costa 4) |
Ogni valore di expand | +1 |
Una ricerca in blocco (ids=) | 1 per ogni blocco iniziato di 10 id |
Quando il budget finisce, una lettura che la cache possiede riceve comunque risposta, dall'ultima copia, con Cache-Status: sway; hit; ...; detail=rate-limited. Solo una lettura senza alcuna copia risponde 429 rate_limited, con Retry-After.
Altre quote, per chiave salvo indicazione diversa:
| Quota | Valore |
|---|---|
| Richieste, in cache o no | 2.400 al minuto su Studio, 6.000 su Roster (o 20 volte il lavoro al minuto della chiave, il valore più alto) |
| Dati di risposta | 5 GB al giorno su Studio, 20 GB su Roster |
| Tutte le chiavi della crew insieme | 600 di lavoro al minuto e 120.000 al giorno su Studio; 1.500 e 500.000 su Roster. Altrettante scritture |
Scritture (POST), checkout a parte | Quante il lavoro al minuto della chiave, mai meno di 60 |
| Sessioni di checkout | 600 al minuto, per la chiave e per tutte le chiavi della crew insieme, a parte le altre scritture |
| Verifiche di coupon e codici di accesso | 300 al minuto, a parte le scritture |
| Messaggi MCP | 120 al minuto |
| Chiave pubblicabile, per indirizzo di visitatore | 60 di lavoro e 600 richieste al minuto |
Ogni risposta autenticata riporta il budget di lavoro della chiave:
RateLimit-Policy: "burst";q=120;w=60, "daily";q=20000;w=86400
RateLimit: "burst";r=118;t=42, "daily";r=19873;t=51230
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 118
X-RateLimit-Reset: 1759525489
RateLimit-Policy e RateLimit seguono la bozza IETF; gli header X-RateLimit-* mostrano la più stretta delle due finestre e restano per i client esistenti. Su un 429, aspetta Retry-After secondi; una chiave che continua a superare il limite riceve attese più lunghe. Una chiave che scorre gli id con un pattern insolito viene limitata alle sole risposte in cache per un po'.
If-None-Match e la cache, un sito partner che serve migliaia di visitatori al giorno resta ben sotto queste cifre. La guida all'integrazione mostra il pattern. Se ti serve di più, contattaci: i limiti si impostano per chiave.Cache
Ogni lettura in cache porta la sua durata e un ETag:
Cache-Control: private, max-age=60, stale-while-revalidate=120, stale-if-error=86400
ETag: "4f9a1c7e2b8d5f3a"
Cache-Status: sway; hit; ttl=42
Rimanda indietro l'ETag come If-None-Match: quando niente è cambiato, la risposta è 304 Not Modified, senza corpo e senza costo. Cache-Status dice da dove viene una risposta: hit (la cache), fwd=miss (lettura fresca), fwd=stale (aggiornata dopo che la sua durata è scaduta). Le durate vanno da 30 secondi (tipi di biglietto, disponibilità) a un'ora (generi); il riferimento degli endpoint le indica una per una. Le risposte che portano dati a livello di chiave o di membro non sono mai in cache (Cache-Control: private, no-store).
Per saltare la copia di Sway, invia Cache-Control: no-cache con una chiave segreta: la richiesta viene letta di nuovo alla fonte (Cache-Status: sway; fwd=request; stored) e consuma lavoro come qualsiasi lettura fuori cache. È la richiesta da fare quando un webhook annuncia un cambiamento, perché la copia può essere precedente. Quando il tuo budget di lavoro è esaurito, ricevi comunque la copia, con detail=rate-limited. Il Cache-Control di una chiave pubblicabile viene ignorato.
Errori
Ogni errore è un corpo RFC 9457 application/problem+json con un code stabile su cui basare la logica, e l'id della richiesta:
{
"type": "https://www.sway.events/en/docs/api/errors#missing_scope",
"title": "Missing Scope",
"status": 403,
"detail": "This API key does not have the required scope.",
"instance": "/api/v1/events/4521",
"code": "missing_scope",
"request_id": "0b7c2e4a-9f1d-4a3b-8c6e-5d2f1a9b7e3c"
}
code | Stato | Significato |
|---|---|---|
invalid_key | 401 | La chiave è malformata, sconosciuta, revocata o scaduta |
key_in_query | 400 | Una chiave segreta è stata inviata come ?key=. Ruotala |
key_suspended | 403 | La chiave è sospesa; un admin della crew può riattivarla |
subscription_required | 403 | Il piano della crew non include più l'API |
missing_scope | 403 | Alla chiave manca lo scope dell'endpoint, oppure una chiave pubblicabile ha chiamato un endpoint che richiede una chiave segreta |
origin_not_allowed | 403 | Una chiave pubblicabile è stata usata da un sito non nel suo elenco, oppure un browser ha chiamato il server MCP |
not_found | 404 | La risorsa non esiste o è fuori dalle pagine della tua crew |
conflict | 409 | Lo stesso Idempotency-Key è ancora in elaborazione, oppure l'azione è già stata fatta (una chiave ruota una volta) |
validation_error | 400 / 422 | Un parametro di query (400) o il corpo della richiesta (422) non è valido |
invalid_cursor | 400 | Il cursore di paginazione è malformato o scaduto |
idempotency_mismatch | 422 | Questo Idempotency-Key è stato usato con un altro corpo |
password_required | 401 | Un press kit protetto richiede Presskit-Password |
rate_limited | 429 | È stato raggiunto un limite; vedi Retry-After |
internal_error | 500 | Qualcosa è andato storto da parte nostra; puoi riprovare senza rischi |
upstream_error | 502 | Un servizio da cui Sway dipende (pagamenti, email) ha fallito; puoi riprovare senza rischi |
validation_error porta anche un array errors di { "param", "message" }. Gli endpoint aggiungono propri codici dove servono (un tipo di biglietto esaurito, un codice che non si applica); il riferimento degli endpoint li elenca.
404 not_found vale sia per "mancante" sia per "non tuo": l'API non conferma mai che esistano dati fuori dalla tua crew.
Ogni risposta, di successo o di errore, porta X-Request-Id. Citalo quando ci contatti: trova la richiesta nei nostri log. Puoi inviare il tuo X-Request-Id (da 8 a 128 tra lettere, cifre, ., _, : o -) per collegare i nostri log ai tuoi; qualsiasi altra cosa viene sostituita con un id nuovo.
Idempotenza
Un tentativo di rete ripetuto non deve mai prenotare, iscrivere o rimborsare due volte. Invia un header Idempotency-Key su un POST, un valore unico per operazione (un UUID è l'ideale, da 1 a 255 caratteri ASCII visibili):
curl -X POST https://www.sway.events/api/v1/booking-requests \
-H "Authorization: Bearer sway_sk_YOUR_KEY" \
-H "Idempotency-Key: 5b8f3c1e-2a7d-4e9b-8c6f-1d3a5e7b9c2f" \
-H "Content-Type: application/json" \
-d '{ "artist_ids": [501], "event_name": "Warehouse Series #4", "contact_email": "[email protected]" }'
- La stessa chiave con lo stesso corpo entro 24 ore riceve indietro la prima risposta, senza rifare il lavoro.
- La stessa chiave con un altro corpo risponde
422 idempotency_mismatch. - Un tentativo che arriva mentre la prima richiesta è ancora in corso risponde
409 conflict: aspetta e riprova.
Funziona su ogni POST tranne l'apertura di un checkout, che si protegge da sola dai duplicati, e le verifiche di coupon e codici di accesso, che non cambiano nulla. Una risposta che porta un segreto (una chiave ruotata) non viene mai conservata per un replay.