Authentification
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ète | Clé publique | |
|---|---|---|
| Préfixe | sway_sk_ | sway_pk_ |
| Tourne côté | Votre serveur | Le navigateur d'un visiteur |
| Scopes | Tous | Les lectures publiques uniquement : read:profile, read:artists, read:promoters, read:events, read:venues, read:content, read:shop |
| Répond | Partout | Seulement sur les sites web listés sur la clé (vérifiés contre l'Origin du navigateur) |
| Envoyée comme | Authorization: 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é.
?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).
| Action | Ce qui se passe |
|---|---|
| Créer | Nom, 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 scopes | Change les scopes et les listes d'adresses ; s'applique aussitôt |
| Suspendre | L'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 tourner | POST /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évoquer | Dé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 |
| Expirer | Une 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.
| Scope | Ouvre l'accès à | Par défaut |
|---|---|---|
read:profile | Le profil du crew lui-même : /v1/crew, /v1/crew/pages | Oui |
read:artists | Le roster, chaque artiste, leurs événements, calendriers et press kits | Oui |
read:promoters | Les promoteurs que vous gérez, leurs événements, salles et artistes | Oui |
read:events | Événements, lineups, jours, timetables, catégories de billets, disponibilité, frais, galerie, préventes | Oui |
read:venues | Salles, leurs événements et leur capacité | Oui |
read:content | Actus, partenaires, formulaires et press kits | Oui |
read:ambassadors | La page publique du programme ambassadeurs, son classement, ses récompenses et ses quêtes, et la page d'un membre connecté | Non |
read:ambassador-members | Les membres, leurs registres et les statistiques du programme. Données personnelles : propriétaire du crew uniquement | Non |
read:export | Tout le catalogue en téléchargements NDJSON | Non |
read:shop | Le merch des pages que vous gérez : catalogue, produit, ce que vend un événement ; le chiffrage et la vérification d'un panier | Non |
write:bookings | Envoyer des demandes de booking (POST /v1/booking-requests) | Non |
write:newsletter | Ajouter des inscriptions newsletter au CRM d'un promoteur, et en retirer une adresse | Non |
write:forms | Envoyer des réponses à vos formulaires publics | Non |
write:checkout | Ouvrir des paiements de billets, et vérifier des coupons et codes d'accès | Non |
write:checkout:free | Émettre aussi des commandes de billets gratuits ou à 100 % de réduction. Propriétaire du crew uniquement | Non |
write:shop:checkout | Ouvrir des paiements de merch pour une page que vous gérez | Non |
write:ambassadors | Rejoindre, se connecter et se déconnecter, réclamer une récompense, modifier ses propres informations | Non |
write:keys | Laisser la clé se faire tourner elle-même (POST /v1/keys/rotate) | Non |
mcp:access | Utiliser la clé avec le serveur MCP | Non |
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 Modifiedne 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.
| Formule | Travail par minute | Travail par jour |
|---|---|---|
| Studio | 120 | 20 000 |
| Roster | 300 | 100 000 |
Ce que coûte une requête non mise en cache :
| Requête | Unités de travail |
|---|---|
Un détail (/v1/events/{id}) | 1 |
| Une page de liste | 1 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 :
| Allocation | Valeur |
|---|---|
| Requêtes, en cache ou non | 2 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éponse | 5 Go par jour sur Studio, 20 Go sur Roster |
| Toutes les clés du crew ensemble | 600 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 checkout | Autant que le travail par minute de la clé, jamais moins de 60 |
| Sessions de checkout | 600 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ès | 300 par minute, en plus des écritures |
| Messages MCP | 120 par minute |
| Clé publique, par adresse de visiteur | 60 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.
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"
}
code | Statut | Signification |
|---|---|---|
invalid_key | 401 | La clé est malformée, inconnue, révoquée ou expirée |
key_in_query | 400 | Une clé secrète a été envoyée en ?key=. Faites-la tourner |
key_suspended | 403 | La clé est suspendue ; un admin du crew peut la réactiver |
subscription_required | 403 | L'offre du crew n'inclut plus l'API |
missing_scope | 403 | La 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_allowed | 403 | Une clé publique a été utilisée depuis un site absent de sa liste, ou un navigateur a appelé le serveur MCP |
not_found | 404 | La ressource n'existe pas ou est en dehors des pages de votre crew |
conflict | 409 | La 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_error | 400 / 422 | Un paramètre de requête (400) ou le corps de la requête (422) est invalide |
invalid_cursor | 400 | Le curseur de pagination est malformé ou périmé |
idempotency_mismatch | 422 | Cette Idempotency-Key a été utilisée avec un autre corps |
password_required | 401 | Un press kit protégé demande Presskit-Password |
rate_limited | 429 | Une limite a été atteinte ; voir Retry-After |
internal_error | 500 | Un problème est survenu de notre côté ; nouvelle tentative possible |
upstream_error | 502 | Un 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.