Authentifizierung
Jede Anfrage trägt einen API-Schlüssel als Bearer-Token:
curl https://www.sway.events/api/v1/artists \
-H "Authorization: Bearer sway_sk_YOUR_KEY"
Der Schlüssel identifiziert deine Crew. Einen Mandanten-Parameter gibt es in der API nirgends: Was ein Schlüssel sehen kann, entscheidet die Crew, zu der er gehört, nie die Anfrage.
Zwei Schlüsselarten
| Geheimer Schlüssel | Veröffentlichbarer Schlüssel | |
|---|---|---|
| Präfix | sway_sk_ | sway_pk_ |
| Läuft auf | Deinem Server | Dem Browser eines Besuchers |
| Scopes | Alle | Nur die öffentlichen Lese-Scopes: read:profile, read:artists, read:promoters, read:events, read:venues, read:content, read:shop |
| Antwortet wo | Überall | Nur auf den am Schlüssel hinterlegten Websites (geprüft gegen den Origin-Header des Browsers) |
| Gesendet als | Authorization: Bearer ... | Authorization: Bearer ..., oder ?key=, wenn ein Header unpraktisch ist |
Nutz einen geheimen Schlüssel für alles, was dein Server tut, und für alles, was schreibt, Tickets verkauft oder Mitglieder anfasst. Gib ihn nie an einen Browser oder eine App weiter: Wer deine Seiten lesen kann, kann auch den Schlüssel auslesen.
Nutz einen veröffentlichbaren Schlüssel, wenn ein Browser die API direkt aufrufen muss, zum Beispiel eine statische Site ohne Server. Er liest nur, was Sway schon öffentlich zeigt, antwortet nur auf seinen eigenen Websites, und bekommt zusätzlich zum Kontingent des Schlüssels ein kleineres Kontingent pro Besucheradresse.
?key= gesendet wird, wird mit 400 key_in_query abgelehnt, weil URLs in Logs und Browser-Verläufen landen. Passiert das doch, gilt der Schlüssel als geleakt: Rotier ihn.Lebenszyklus eines Schlüssels
Schlüssel verwaltest du in Crew-Admin → API (Pläne Studio und Roster, Berechtigung API verwalten).
| Aktion | Was passiert |
|---|---|
| Anlegen | Name, Typ, Scopes, Websites oder Shop-Adressen und ein Ablauf (nie, oder nach 30, 90 oder 365 Tagen). Der volle Schlüssel wird einmal gezeigt; danach nur noch sein Anfang und die letzten vier Zeichen |
| Bearbeiten | Ändert die Scopes und Adresslisten; gilt sofort |
| Sperren | Der Notausschalter. Jede Anfrage antwortet mit 403 key_suspended, bis jemand auf Reaktivieren klickt. Nichts wird gelöscht |
| Rotieren | POST /v1/keys/rotate liefert, mit einem Schlüssel, der write:keys hält, seinen Ersatz: gleicher Name, Typ, gleiche Scopes und Limits. Der alte Schlüssel funktioniert noch grace_hours lang (1 bis 168, Standard 24), dann läuft er ab. Ein Schlüssel rotiert einmal, höchstens einmal die Stunde, und der Crew-Eigentümer bekommt jedes Mal eine E-Mail |
| Widerrufen | Endgültig. Anfragen antworten mit 401 invalid_key. Wurde der Schlüssel rotiert, widerruft Auch die Schlüssel widerrufen, die ihn ersetzt haben die ganze Kette: nutz das, wenn der Schlüssel geleakt ist, denn wer einen geleakten Schlüssel mit write:keys hält, könnte ihn rotiert haben |
| Ablaufen | Nach expires_at antworten Anfragen mit 401 invalid_key |
Ein widerrufener, abgelaufener oder unbekannter Schlüssel antworten alle mit demselben 401 invalid_key: Die API sagt einem Aufrufer nie, ob ein ihr unbekannter Schlüssel je existiert hat. Das Panel Aktivität an jedem Schlüssel zeigt, was er von seinen Limits verbraucht hat, und seine letzten Aufrufe.
Läuft das Abo der Crew aus, antwortet jeder Schlüssel mit 403 subscription_required, bis es wieder aktiv ist.
Scopes
Scopes wählst du beim Anlegen des Schlüssels und kannst sie später bearbeiten. Eine Anfrage ohne den Scope, den ihr Endpunkt braucht, antwortet mit 403 missing_scope.
| Scope | Öffnet | Standard |
|---|---|---|
read:profile | Das eigene Profil der Crew: /v1/crew, /v1/crew/pages | Ja |
read:artists | Das Roster, jeder Künstler, seine Veranstaltungen, Kalender und Presskits | Ja |
read:promoters | Die Veranstalter, die du verwaltest, ihre Veranstaltungen, Veranstaltungsorte und Künstler | Ja |
read:events | Veranstaltungen, Lineups, Tage, Timetables, Tickettypen, Verfügbarkeit, Gebühren, Galerie, Vorverkäufe | Ja |
read:venues | Veranstaltungsorte, ihre Veranstaltungen und Kapazität | Ja |
read:content | News, Partner, Formulare und Presskits | Ja |
read:ambassadors | Die öffentliche Seite des Ambassador-Programms, Ranking, Belohnungen und Quests, sowie die eigene Seite eines angemeldeten Mitglieds | Nein |
read:ambassador-members | Die Mitglieder, ihre Bewegungen und die Programmstatistiken. Personenbezogene Daten: nur Crew-Eigentümer | Nein |
read:export | Der ganze Katalog als NDJSON-Downloads | Nein |
read:shop | Das Merch der Seiten, die du verwaltest: Katalog, Produkt, was ein Event verkauft; das Berechnen und Prüfen eines Warenkorbs | Nein |
write:bookings | Booking-Anfragen senden (POST /v1/booking-requests) | Nein |
write:newsletter | Newsletter-Anmeldungen zum CRM eines Veranstalters hinzufügen und eine Adresse wieder austragen | Nein |
write:forms | Antworten auf deine öffentlichen Formulare einreichen | Nein |
write:checkout | Bezahlte Ticket-Checkouts öffnen, und Gutscheine und Zugangscodes prüfen | Nein |
write:checkout:free | Auch kostenlose und 100%-rabattierte Ticketbestellungen ausstellen. Nur Crew-Eigentümer | Nein |
write:shop:checkout | Merch-Checkouts für eine Seite öffnen, die du verwaltest | Nein |
write:ambassadors | Beitreten, an- und abmelden, eine Belohnung einlösen, eigene Angaben bearbeiten | Nein |
write:keys | Den Schlüssel sich selbst rotieren lassen (POST /v1/keys/rotate) | Nein |
mcp:access | Den Schlüssel mit dem MCP-Server nutzen | Nein |
GET /v1/me, /v1/usage, /v1/audit-log und /v1/genres brauchen keinen Scope: Jeder gültige Schlüssel darf sie aufrufen.
Die mit nur Crew-Eigentümer gekennzeichneten Scopes kann nur der Crew-Eigentümer vergeben; ein Mitglied mit der Berechtigung API verwalten kann sie entfernen, aber nicht hinzufügen. GET /v1/scopes liefert diese Liste als JSON.
Websites und Shop-Adressen
Zwei Adresslisten leben an einem Schlüssel, beide gesetzt in Crew-Admin → API:
- Websites (veröffentlichbare Schlüssel): die einzigen Browser-Origins, auf die der Schlüssel antwortet. Eine Anfrage von einer anderen Site antwortet mit
403 origin_not_allowed. - Shop-Adressen (Schlüssel mit einem Ambassador-Scope): wohin Sway ein Mitglied nach einem per E-Mail gesendeten Anmeldelink zurückschicken darf. Deine Site gibt die Rückkehradresse mit der Anfrage mit; Sway akzeptiert sie nur, wenn ihr Origin auf der Liste steht, sodass der Link nie woanders hinzeigen kann. Ein Schlüssel mit einem Ambassador-Scope und einer leeren Liste liefert weiterhin Seiten aus, kann aber nie ein Mitglied wieder anmelden.
Nur https-Origins werden akzeptiert, dazu http auf localhost für die Entwicklung. Pfade werden verworfen: Gespeichert und verglichen wird nur der Origin.
Rate Limits
Die API ist so gebaut, dass eine stark besuchte Partner-Site ihre Daten immer weiter zeigt. Zwei Ideen tragen das:
- Eine gecachte Antwort ist gratis. Lesezugriffe werden pro Crew gecacht. Eine Anfrage zu wiederholen, die der Cache schon hält, kostet nichts, und ein
304 Not Modifiedkostet nichts. - Gezählt wird Arbeit, nicht Anfragen. Nur eine Anfrage, die die Datenbank lesen muss, verbraucht das Arbeitsbudget des Schlüssels, und zwar im Verhältnis zu dem, was sie liest.
| Plan | Arbeit pro Minute | Arbeit pro Tag |
|---|---|---|
| Studio | 120 | 20.000 |
| Roster | 300 | 100.000 |
Was eine ungecachte Anfrage kostet:
| Anfrage | Arbeitseinheiten |
|---|---|
Ein Detail (/v1/events/{id}) | 1 |
| Eine Listenseite | 1 pro begonnenem Block von 25 Einträgen (limit=100 kostet 4) |
Jeder expand-Wert | +1 |
Eine Batch-Abfrage (ids=) | 1 pro begonnenem Block von 10 IDs |
Ist das Budget aufgebraucht, wird ein Lesezugriff, den der Cache hält, trotzdem beantwortet, aus der letzten Kopie, mit Cache-Status: sway; hit; ...; detail=rate-limited. Nur ein Lesezugriff ganz ohne Kopie antwortet mit 429 rate_limited, mit Retry-After.
Andere Kontingente, pro Schlüssel, außer wo anders angegeben:
| Kontingent | Wert |
|---|---|
| Anfragen, gecacht oder nicht | 2.400 pro Minute bei Studio, 6.000 bei Roster (oder das 20-Fache der Arbeit des Schlüssels pro Minute, je nachdem, was höher ist) |
| Antwortdaten | 5 GB pro Tag bei Studio, 20 GB bei Roster |
| Alle Schlüssel der Crew zusammen | 600 Arbeit pro Minute und 120.000 pro Tag bei Studio; 1.500 und 500.000 bei Roster. Ebenso viele Schreibzugriffe dazu |
Schreibzugriffe (POST), ohne Checkout | So viele wie die Arbeit pro Minute des Schlüssels, nie unter 60 |
| Checkout-Sessions | 600 pro Minute, für den Schlüssel und für alle Schlüssel der Crew zusammen, zusätzlich zu den anderen Schreibzugriffen |
| Gutschein- und Zugangscode-Prüfungen | 300 pro Minute, zusätzlich zu den Schreibzugriffen |
| MCP-Nachrichten | 120 pro Minute |
| Veröffentlichbarer Schlüssel, pro Besucheradresse | 60 Arbeit und 600 Anfragen pro Minute |
Jede authentifizierte Antwort meldet das Arbeitsbudget des Schlüssels:
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 und RateLimit folgen dem IETF-Entwurf; die X-RateLimit-*-Header zeigen das engere der beiden Fenster und bleiben für bestehende Clients erhalten. Bei einem 429 wart Retry-After Sekunden; ein Schlüssel, der das Limit immer wieder reißt, bekommt längere Wartezeiten. Ein Schlüssel, der IDs in einem ungewöhnlichen Muster durchkämmt, ist eine Weile auf gecachte Antworten beschränkt.
If-None-Match und dem Cache bleibt eine Partner-Site, die täglich Tausende Besucher bedient, weit unter diesen Werten. Der Integrationsleitfaden zeigt das Muster. Brauchst du mehr, meld dich bei uns: Limits werden pro Schlüssel gesetzt.Caching
Jeder cachebare Lesezugriff trägt seine Lebensdauer und ein ETag:
Cache-Control: private, max-age=60, stale-while-revalidate=120, stale-if-error=86400
ETag: "4f9a1c7e2b8d5f3a"
Cache-Status: sway; hit; ttl=42
Schick das ETag als If-None-Match zurück: Hat sich nichts geändert, ist die Antwort 304 Not Modified, ohne Body und ohne Kosten. Cache-Status sagt, woher eine Antwort kommt: hit (der Cache), fwd=miss (frisch gelesen), fwd=stale (aufgefrischt, nachdem die Lebensdauer abgelaufen ist). Lebensdauern reichen von 30 Sekunden (Tickettypen, Verfügbarkeit) bis zu einer Stunde (Genres); die Endpunkt-Referenz nennt sie einzeln. Antworten mit Daten auf Schlüssel- oder Mitgliedsebene werden nie gecacht (Cache-Control: private, no-store).
Um Sways Kopie zu überspringen, schick mit einem geheimen Schlüssel Cache-Control: no-cache: Die Anfrage wird frisch gelesen (Cache-Status: sway; fwd=request; stored) und verbraucht Arbeit wie jeder ungecachte Lesezugriff. Das ist die Anfrage, wenn ein Webhook eine Änderung meldet, denn die Kopie kann älter sein als die Änderung. Ist dein Arbeitsbudget aufgebraucht, bekommst du die Kopie trotzdem, mit detail=rate-limited. Das Cache-Control eines veröffentlichbaren Schlüssels wird ignoriert.
Fehler
Jeder Fehler ist ein RFC-9457-application/problem+json-Body mit einem stabilen code zum Verzweigen und der ID der Anfrage:
{
"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 | Status | Bedeutung |
|---|---|---|
invalid_key | 401 | Der Schlüssel ist fehlerhaft, unbekannt, widerrufen oder abgelaufen |
key_in_query | 400 | Ein geheimer Schlüssel wurde als ?key= gesendet. Rotier ihn |
key_suspended | 403 | Der Schlüssel ist gesperrt; ein Crew-Admin kann ihn reaktivieren |
subscription_required | 403 | Der Plan der Crew umfasst die API nicht mehr |
missing_scope | 403 | Dem Schlüssel fehlt der Scope des Endpunkts, oder ein veröffentlichbarer Schlüssel hat einen Endpunkt aufgerufen, der einen geheimen Schlüssel braucht |
origin_not_allowed | 403 | Ein veröffentlichbarer Schlüssel wurde von einer Website benutzt, die nicht auf seiner Liste steht, oder ein Browser hat den MCP-Server aufgerufen |
not_found | 404 | Die Ressource existiert nicht, oder liegt außerhalb der Seiten deiner Crew |
conflict | 409 | Derselbe Idempotency-Key wird noch verarbeitet, oder die Aktion wurde schon ausgeführt (ein Schlüssel rotiert nur einmal) |
validation_error | 400 / 422 | Ein Query-Parameter (400) oder der Request-Body (422) ist ungültig |
invalid_cursor | 400 | Der Paginierungs-Cursor ist fehlerhaft oder veraltet |
idempotency_mismatch | 422 | Dieser Idempotency-Key wurde mit einem anderen Body verwendet |
password_required | 401 | Ein geschütztes Presskit braucht Presskit-Password |
rate_limited | 429 | Ein Limit wurde erreicht; siehe Retry-After |
internal_error | 500 | Etwas ist auf unserer Seite fehlgeschlagen; ein erneuter Versuch ist sicher |
upstream_error | 502 | Ein Dienst, von dem Sway abhängt (Zahlungen, E-Mail), ist fehlgeschlagen; ein erneuter Versuch ist sicher |
validation_error trägt zusätzlich ein Array errors aus { "param", "message" }. Endpunkte fügen eigene Codes hinzu, wo sie sie brauchen (ein ausverkaufter Tickettyp, ein Code, der nicht passt); die Endpunkt-Referenz listet sie auf.
404 not_found steht sowohl für "fehlt" als auch für "gehört dir nicht": Die API bestätigt nie, dass Daten außerhalb deiner Crew existieren.
Jede Antwort, ob Erfolg oder Fehler, trägt X-Request-Id. Gib sie an, wenn du uns kontaktierst: Sie findet die Anfrage in unseren Logs. Du kannst deine eigene X-Request-Id (8 bis 128 Buchstaben, Ziffern, ., _, : oder -) mitschicken, um unsere Logs mit deinen zu verknüpfen; alles andere wird durch eine neue ID ersetzt.
Idempotenz
Eine Netzwerk-Wiederholung darf nie doppelt buchen, anmelden oder erstatten. Schick bei einem POST einen Header Idempotency-Key, einen pro Vorgang eindeutigen Wert (eine UUID ist ideal, 1 bis 255 sichtbare ASCII-Zeichen):
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]" }'
- Derselbe
Idempotency-Keymit demselben Body bekommt innerhalb von 24 Stunden die erste Antwort zurück, ohne die Arbeit erneut zu tun. - Derselbe
Idempotency-Keymit einem anderen Body antwortet mit422 idempotency_mismatch. - Eine Wiederholung, die eintrifft, während die erste Anfrage noch läuft, antwortet mit
409 conflict: warten und erneut versuchen.
Das funktioniert bei jedem POST, außer beim Öffnen eines Checkouts, der sich selbst gegen Duplikate schützt, und bei den Gutschein- und Zugangscode-Prüfungen, die nichts ändern. Eine Antwort, die ein Geheimnis trägt (ein rotierter Schlüssel), wird nie für eine Wiederholung gespeichert.