Authentification

Clés API, scopes, limites de débit, cache, erreurs et idempotence dans l'API publique Sway.

Chaque requête porte une clé API en tant que jeton Bearer :

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

La clé identifie votre crew. Il n'existe aucun paramètre de tenant dans l'API : ce qu'une clé peut voir est décidé par le crew auquel elle appartient, jamais par la requête.


Deux types de clé

Clé secrèteClé publique
Préfixesway_sk_sway_pk_
Tourne côtéVotre serveurLe navigateur d'un visiteur
ScopesTousLes lectures publiques uniquement : read:profile, read:artists, read:promoters, read:events, read:venues, read:content, read:shop
RépondPartoutSeulement sur les sites web listés sur la clé (vérifiés contre l'Origin du navigateur)
Envoyée commeAuthorization: Bearer ...Authorization: Bearer ..., ou ?key= quand un en-tête n'est pas possible

Utilisez une clé secrète pour tout ce que fait votre serveur, et pour tout ce qui écrit, vend des billets ou touche des membres. Ne l'envoyez jamais à un navigateur ou une application : quiconque peut lire vos pages peut lire la clé.

Utilisez une clé publique quand un navigateur doit appeler l'API directement, par exemple un site statique sans serveur. Elle ne lit que ce que Sway affiche déjà publiquement, ne répond que sur ses propres sites web, et reçoit une allocation plus petite par adresse de visiteur, en plus de celle de la clé.

Une clé secrète envoyée en ?key= est refusée avec 400 key_in_query, car les URL finissent dans des logs et des historiques de navigateur. Si cela arrive, traitez la clé comme fuitée : faites-la tourner.

Cycle de vie d'une clé

Les clés se gèrent dans Admin crew → API (formules Studio et Roster, permission Gérer l'API).

ActionCe qui se passe
CréerNom, type, scopes, sites web ou adresses du site, et une expiration (jamais, 30, 90 ou 365 jours). La clé complète s'affiche une fois ; ensuite seuls son début et ses quatre derniers caractères sont visibles
Modifier les scopesChange les scopes et les listes d'adresses ; s'applique aussitôt
SuspendreL'interrupteur d'urgence. Chaque requête répond 403 key_suspended jusqu'à ce que quelqu'un clique sur Réactiver. Rien n'est supprimé
Faire tournerPOST /v1/keys/rotate avec une clé détenant write:keys renvoie sa remplaçante : même nom, type, scopes et limites. L'ancienne clé continue de fonctionner pendant grace_hours (1 à 168, 24 par défaut), puis expire. Une clé tourne une fois, au plus une fois par heure, et le propriétaire du crew reçoit un e-mail à chaque fois
RévoquerDéfinitif. Les requêtes répondent 401 invalid_key. Quand la clé a tourné, Révoquer aussi les clés qui l'ont remplacée révoque toute la chaîne : utilisez-la quand la clé a fuité, puisque quiconque détient une clé fuitée avec write:keys aurait pu la faire tourner
ExpirerUne fois expires_at dépassé, les requêtes répondent 401 invalid_key

Clé révoquée, expirée ou inconnue : la réponse est toujours la même, 401 invalid_key. L'API ne dit jamais à qui l'appelle si une clé qu'elle ne connaît pas a un jour existé. Le panneau Activité de chaque clé montre ce qu'elle a consommé de ses limites et ses derniers appels.

Si l'abonnement du crew expire, chaque clé répond 403 subscription_required jusqu'à sa réactivation.


Scopes

Les scopes sont choisis à la création de la clé et peuvent être modifiés ensuite. Une requête sans le scope requis par son endpoint répond 403 missing_scope.

ScopeOuvre l'accès àPar défaut
read:profileLe profil du crew lui-même : /v1/crew, /v1/crew/pagesOui
read:artistsLe roster, chaque artiste, leurs événements, calendriers et press kitsOui
read:promotersLes promoteurs que vous gérez, leurs événements, salles et artistesOui
read:eventsÉvénements, lineups, jours, timetables, catégories de billets, disponibilité, frais, galerie, préventesOui
read:venuesSalles, leurs événements et leur capacitéOui
read:contentActus, partenaires, formulaires et press kitsOui
read:ambassadorsLa page publique du programme ambassadeurs, son classement, ses récompenses et ses quêtes, et la page d'un membre connectéNon
read:ambassador-membersLes membres, leurs registres et les statistiques du programme. Données personnelles : propriétaire du crew uniquementNon
read:exportTout le catalogue en téléchargements NDJSONNon
read:shopLe merch des pages que vous gérez : catalogue, produit, ce que vend un événement ; le chiffrage et la vérification d'un panierNon
write:bookingsEnvoyer des demandes de booking (POST /v1/booking-requests)Non
write:newsletterAjouter des inscriptions newsletter au CRM d'un promoteur, et en retirer une adresseNon
write:formsEnvoyer des réponses à vos formulaires publicsNon
write:checkoutOuvrir des paiements de billets, et vérifier des coupons et codes d'accèsNon
write:checkout:freeÉmettre aussi des commandes de billets gratuits ou à 100 % de réduction. Propriétaire du crew uniquementNon
write:shop:checkoutOuvrir des paiements de merch pour une page que vous gérezNon
write:ambassadorsRejoindre, se connecter et se déconnecter, réclamer une récompense, modifier ses propres informationsNon
write:keysLaisser la clé se faire tourner elle-même (POST /v1/keys/rotate)Non
mcp:accessUtiliser la clé avec le serveur MCPNon

GET /v1/me, /v1/usage, /v1/audit-log et /v1/genres ne demandent aucun scope : toute clé valide peut les appeler.

Les scopes "propriétaire du crew uniquement" ne peuvent être accordés que par le propriétaire du crew ; un membre détenant la permission Gérer l'API peut les retirer mais pas les ajouter. GET /v1/scopes renvoie cette liste en JSON.

Sites web et adresses du site

Deux listes d'adresses vivent sur une clé, toutes deux réglées dans Admin crew → API :

  • Sites web (clés publiques) : les seules origines de navigateur auxquelles la clé répond. Une requête venant d'un autre site répond 403 origin_not_allowed.
  • Adresses du site (clés avec un scope ambassadeur) : où Sway peut renvoyer un membre après un lien de connexion envoyé par e-mail. Votre site transmet l'adresse de retour avec la requête ; Sway ne l'accepte que si son origine figure sur la liste, si bien que le lien ne peut jamais pointer ailleurs. Une clé avec un scope ambassadeur et une liste vide sert quand même les pages, mais ne peut jamais reconnecter un membre.

Seules les origines https sont acceptées, plus http sur localhost pour le développement. Les chemins sont supprimés : seule l'origine est stockée et comparée.


Limites de débit

L'API est construite pour qu'un site partenaire à fort trafic continue d'afficher ses données. Deux idées portent cela :

  • Une réponse en cache est gratuite. Les lectures sont mises en cache par crew. Répéter une requête que le cache détient déjà ne coûte rien, et un 304 Not Modified ne coûte rien.
  • C'est le travail qui est compté, pas les requêtes. Seule une requête qui doit lire la base de données dépense le budget de travail de la clé, et elle le dépense en proportion de ce qu'elle lit.
FormuleTravail par minuteTravail par jour
Studio12020 000
Roster300100 000

Ce que coûte une requête non mise en cache :

RequêteUnités de travail
Un détail (/v1/events/{id})1
Une page de liste1 par tranche entamée de 25 éléments (limit=100 coûte 4)
Chaque valeur d'expand+1
Une recherche par lot (ids=)1 par tranche entamée de 10 ids

Quand le budget est épuisé, une lecture que le cache détient reste servie, depuis la dernière copie, avec Cache-Status: sway; hit; ...; detail=rate-limited. Seule une lecture sans aucune copie répond 429 rate_limited, avec Retry-After.

Autres allocations, par clé sauf mention contraire :

AllocationValeur
Requêtes, en cache ou non2 400 par minute sur Studio, 6 000 sur Roster (ou 20 fois le travail par minute de la clé, selon le plus élevé)
Données de réponse5 Go par jour sur Studio, 20 Go sur Roster
Toutes les clés du crew ensemble600 de travail par minute et 120 000 par jour sur Studio ; 1 500 et 500 000 sur Roster. Autant d'écritures en plus
Écritures (POST), hors checkoutAutant que le travail par minute de la clé, jamais moins de 60
Sessions de checkout600 par minute, pour la clé et pour toutes les clés du crew ensemble, en plus des autres écritures
Vérifications de coupons et codes d'accès300 par minute, en plus des écritures
Messages MCP120 par minute
Clé publique, par adresse de visiteur60 de travail et 600 requêtes par minute

Chaque réponse authentifiée indique le budget de travail de la clé :

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 et RateLimit suivent le brouillon IETF ; les en-têtes X-RateLimit-* montrent la plus stricte des deux fenêtres et restent pour les clients existants. Sur un 429, attendez le nombre de secondes donné par Retry-After ; une clé qui continue de heurter la limite reçoit des attentes plus longues. Une clé qui parcourt des ids selon un motif inhabituel est limitée aux réponses en cache pendant un moment.

Avec If-None-Match et le cache, un site partenaire qui sert des milliers de visiteurs par jour reste très en dessous de ces chiffres. Le guide d'intégration montre le modèle à suivre. Si vous avez besoin de plus, contactez-nous : les limites se règlent par clé.

Mise en cache

Chaque lecture pouvant être mise en cache porte sa durée de vie et un ETag :

Cache-Control: private, max-age=60, stale-while-revalidate=120, stale-if-error=86400
ETag: "4f9a1c7e2b8d5f3a"
Cache-Status: sway; hit; ttl=42

Renvoyez l'ETag en If-None-Match : quand rien n'a changé, la réponse est 304 Not Modified, sans corps et sans coût. Cache-Status dit d'où vient une réponse : hit (le cache), fwd=miss (lecture fraîche), fwd=stale (rafraîchie après la fin de sa durée de vie). Les durées de vie vont de 30 secondes (catégories de billets, disponibilité) à une heure (genres) ; la référence des endpoints donne chacune d'elles. Les réponses qui portent des données propres à la clé ou à un membre ne sont jamais mises en cache (Cache-Control: private, no-store).

Pour passer outre la copie de Sway, envoyez Cache-Control: no-cache avec une clé secrète : la requête est relue à la source (Cache-Status: sway; fwd=request; stored) et dépense du travail comme toute lecture hors cache. C'est la requête à faire quand un webhook annonce un changement, car la copie peut dater d'avant. Quand votre budget de travail est épuisé, vous recevez quand même la copie, avec detail=rate-limited. Le Cache-Control d'une clé publique est ignoré.


Erreurs

Chaque erreur est un corps RFC 9457 application/problem+json, avec un code stable sur lequel brancher votre logique, et l'id de la requête :

{
  "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"
}
codeStatutSignification
invalid_key401La clé est malformée, inconnue, révoquée ou expirée
key_in_query400Une clé secrète a été envoyée en ?key=. Faites-la tourner
key_suspended403La clé est suspendue ; un admin du crew peut la réactiver
subscription_required403L'offre du crew n'inclut plus l'API
missing_scope403La clé n'a pas le scope requis par l'endpoint, ou une clé publique a appelé un endpoint qui demande une clé secrète
origin_not_allowed403Une clé publique a été utilisée depuis un site absent de sa liste, ou un navigateur a appelé le serveur MCP
not_found404La ressource n'existe pas ou est en dehors des pages de votre crew
conflict409La même Idempotency-Key est encore en cours de traitement, ou l'action a déjà été faite (une clé ne tourne qu'une fois)
validation_error400 / 422Un paramètre de requête (400) ou le corps de la requête (422) est invalide
invalid_cursor400Le curseur de pagination est malformé ou périmé
idempotency_mismatch422Cette Idempotency-Key a été utilisée avec un autre corps
password_required401Un press kit protégé demande Presskit-Password
rate_limited429Une limite a été atteinte ; voir Retry-After
internal_error500Un problème est survenu de notre côté ; nouvelle tentative possible
upstream_error502Un service dont Sway dépend (paiements, e-mail) a échoué ; nouvelle tentative possible

validation_error porte aussi un tableau errors d'objets { "param", "message" }. Les endpoints ajoutent leurs propres codes là où il en faut (une catégorie épuisée, un code qui ne s'applique pas) ; la référence des endpoints les liste.

404 not_found vaut à la fois pour "n'existe pas" et "n'est pas à vous" : l'API ne confirme jamais l'existence de données en dehors de votre crew.

Chaque réponse, succès ou erreur, porte X-Request-Id. Citez-le quand vous nous contactez : il retrouve la requête dans nos logs. Vous pouvez envoyer votre propre X-Request-Id (8 à 128 lettres, chiffres, ., _, : ou -) pour relier nos logs aux vôtres ; toute autre valeur est remplacée par un nouvel id.


Idempotence

Une nouvelle tentative réseau ne doit jamais réserver, inscrire ou rembourser deux fois. Envoyez un en-tête Idempotency-Key sur un POST, une valeur unique par opération (un UUID est idéal, 1 à 255 caractères ASCII visibles) :

curl -X POST https://www.sway.events/api/v1/booking-requests \
  -H "Authorization: Bearer sway_sk_VOTRE_CLE" \
  -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 même clé avec le même corps dans les 24 heures reçoit la première réponse en retour, sans refaire le travail.
  • La même clé avec un autre corps répond 422 idempotency_mismatch.
  • Une nouvelle tentative qui arrive pendant que la première requête tourne encore répond 409 conflict : attendez et réessayez.

Cela fonctionne sur chaque POST sauf l'ouverture d'un paiement, qui se protège elle-même des doublons, et les vérifications de coupons et codes d'accès, qui ne changent rien. Une réponse qui porte un secret (une clé qui vient de tourner) n'est jamais conservée pour être rejouée.