Authentifizierung

API-Schlüssel, Scopes, Rate Limits, Caching, Fehler und Idempotenz in der öffentlichen Sway-API.

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üsselVeröffentlichbarer Schlüssel
Präfixsway_sk_sway_pk_
Läuft aufDeinem ServerDem Browser eines Besuchers
ScopesAlleNur die öffentlichen Lese-Scopes: read:profile, read:artists, read:promoters, read:events, read:venues, read:content, read:shop
Antwortet woÜberallNur auf den am Schlüssel hinterlegten Websites (geprüft gegen den Origin-Header des Browsers)
Gesendet alsAuthorization: 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.

Ein geheimer Schlüssel, der als ?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).

AktionWas passiert
AnlegenName, 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
SperrenDer Notausschalter. Jede Anfrage antwortet mit 403 key_suspended, bis jemand auf Reaktivieren klickt. Nichts wird gelöscht
RotierenPOST /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
WiderrufenEndgü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
AblaufenNach 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ÖffnetStandard
read:profileDas eigene Profil der Crew: /v1/crew, /v1/crew/pagesJa
read:artistsDas Roster, jeder Künstler, seine Veranstaltungen, Kalender und PresskitsJa
read:promotersDie Veranstalter, die du verwaltest, ihre Veranstaltungen, Veranstaltungsorte und KünstlerJa
read:eventsVeranstaltungen, Lineups, Tage, Timetables, Tickettypen, Verfügbarkeit, Gebühren, Galerie, VorverkäufeJa
read:venuesVeranstaltungsorte, ihre Veranstaltungen und KapazitätJa
read:contentNews, Partner, Formulare und PresskitsJa
read:ambassadorsDie öffentliche Seite des Ambassador-Programms, Ranking, Belohnungen und Quests, sowie die eigene Seite eines angemeldeten MitgliedsNein
read:ambassador-membersDie Mitglieder, ihre Bewegungen und die Programmstatistiken. Personenbezogene Daten: nur Crew-EigentümerNein
read:exportDer ganze Katalog als NDJSON-DownloadsNein
read:shopDas Merch der Seiten, die du verwaltest: Katalog, Produkt, was ein Event verkauft; das Berechnen und Prüfen eines WarenkorbsNein
write:bookingsBooking-Anfragen senden (POST /v1/booking-requests)Nein
write:newsletterNewsletter-Anmeldungen zum CRM eines Veranstalters hinzufügen und eine Adresse wieder austragenNein
write:formsAntworten auf deine öffentlichen Formulare einreichenNein
write:checkoutBezahlte Ticket-Checkouts öffnen, und Gutscheine und Zugangscodes prüfenNein
write:checkout:freeAuch kostenlose und 100%-rabattierte Ticketbestellungen ausstellen. Nur Crew-EigentümerNein
write:shop:checkoutMerch-Checkouts für eine Seite öffnen, die du verwaltestNein
write:ambassadorsBeitreten, an- und abmelden, eine Belohnung einlösen, eigene Angaben bearbeitenNein
write:keysDen Schlüssel sich selbst rotieren lassen (POST /v1/keys/rotate)Nein
mcp:accessDen Schlüssel mit dem MCP-Server nutzenNein

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 Modified kostet 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.
PlanArbeit pro MinuteArbeit pro Tag
Studio12020.000
Roster300100.000

Was eine ungecachte Anfrage kostet:

AnfrageArbeitseinheiten
Ein Detail (/v1/events/{id})1
Eine Listenseite1 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:

KontingentWert
Anfragen, gecacht oder nicht2.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)
Antwortdaten5 GB pro Tag bei Studio, 20 GB bei Roster
Alle Schlüssel der Crew zusammen600 Arbeit pro Minute und 120.000 pro Tag bei Studio; 1.500 und 500.000 bei Roster. Ebenso viele Schreibzugriffe dazu
Schreibzugriffe (POST), ohne CheckoutSo viele wie die Arbeit pro Minute des Schlüssels, nie unter 60
Checkout-Sessions600 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üfungen300 pro Minute, zusätzlich zu den Schreibzugriffen
MCP-Nachrichten120 pro Minute
Veröffentlichbarer Schlüssel, pro Besucheradresse60 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.

Mit 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"
}
codeStatusBedeutung
invalid_key401Der Schlüssel ist fehlerhaft, unbekannt, widerrufen oder abgelaufen
key_in_query400Ein geheimer Schlüssel wurde als ?key= gesendet. Rotier ihn
key_suspended403Der Schlüssel ist gesperrt; ein Crew-Admin kann ihn reaktivieren
subscription_required403Der Plan der Crew umfasst die API nicht mehr
missing_scope403Dem 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_allowed403Ein veröffentlichbarer Schlüssel wurde von einer Website benutzt, die nicht auf seiner Liste steht, oder ein Browser hat den MCP-Server aufgerufen
not_found404Die Ressource existiert nicht, oder liegt außerhalb der Seiten deiner Crew
conflict409Derselbe Idempotency-Key wird noch verarbeitet, oder die Aktion wurde schon ausgeführt (ein Schlüssel rotiert nur einmal)
validation_error400 / 422Ein Query-Parameter (400) oder der Request-Body (422) ist ungültig
invalid_cursor400Der Paginierungs-Cursor ist fehlerhaft oder veraltet
idempotency_mismatch422Dieser Idempotency-Key wurde mit einem anderen Body verwendet
password_required401Ein geschütztes Presskit braucht Presskit-Password
rate_limited429Ein Limit wurde erreicht; siehe Retry-After
internal_error500Etwas ist auf unserer Seite fehlgeschlagen; ein erneuter Versuch ist sicher
upstream_error502Ein 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-Key mit demselben Body bekommt innerhalb von 24 Stunden die erste Antwort zurück, ohne die Arbeit erneut zu tun.
  • Derselbe Idempotency-Key mit einem anderen Body antwortet mit 422 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.