Conventions#
Ces règles valent pour toute la surface. Les lire une fois évite de les redécouvrir ressource par ressource.
Versionnage#
La version est dans le chemin : /api/public/v1/.
Les changements additifs — nouveaux champs, nouveaux points de terminaison, nouveaux paramètres optionnels, nouvelles valeurs d’énumération — sont livrés à l’intérieur de v1. Ton client doit les tolérer, ce qui demande deux habitudes : ignorer les champs de réponse que tu ne reconnais pas, et ne pas échouer face à une valeur d’énumération inconnue.
Les suppressions, renommages, changements de type et tout ce qui durcit la validation font monter la version, avec une fenêtre de recouvrement pendant laquelle les deux sont actives.
Réponses de liste#
Les listes sont paginées par curseur et reviennent toujours dans la même enveloppe :
{
"object": "list",
"data": [ { "object": "shift", "id": "0194d1c0-…", "…": "…" } ],
"has_more": true,
"next": "https://app.shiftavo.com/api/public/v1/shifts/?cursor=…"
}
nextest une URL opaque. Suis-la telle quelle ; ne construis ni n’analyse de curseurs.Arrête-toi quand
has_morevautfalse.page_sizecontrôle la longueur de la page.
Le discriminant object#
Chaque ressource porte un champ "object" qui nomme son type — "shift", "location", "employee". Branche dessus plutôt que sur la forme de la charge utile, en particulier dans les gestionnaires de webhooks et les collections mixtes.
Erreurs#
Une seule enveloppe, partout :
{
"error": {
"type": "conflict_error",
"code": "not_cancellable",
"message": "…",
"details": [],
"param": "start_date"
}
}
detailsest toujours présent — une liste d’échecs au niveau des champs, vide quand l’erreur n’a pas de détail par champ.paramn’apparaît que lorsqu’un seul champ est en cause.
Il existe sept types. Chacun correspond à un statut, sauf invalid_request_error, le fourre-tout des 4xx non tabulés, où c’est le code qui porte la distinction :
|
Statut |
Notes |
|---|---|---|
|
400 · 405 · 406 · 415 |
|
|
400 |
|
|
401 |
jeton manquant / invalide / expiré |
|
403 |
|
|
404 |
renvoyé aussi à la place d’un 403 pour les enregistrements hors de ton entreprise — aucune fuite d’existence |
|
409 |
conflits d’état : |
|
429 |
porte |
Branche sur type pour la classe de traitement, sur code pour le cas précis. Les deux sont stables ; message ne l’est pas — traite-le comme lisible par un humain uniquement.
Idempotence#
Envoie un en-tête Idempotency-Key sur les créations. Une requête réessayée rejoue alors le premier résultat au lieu de créer un second enregistrement.
Réutiliser une clé avec un corps différent est un 400 idempotency_key_reused — la clé identifie une requête, pas un point de terminaison.
Traçage des requêtes#
Chaque réponse, succès comme erreur, porte un en-tête X-Request-Id. Journalise-le. Envoie ton propre X-Request-Id et il t’est renvoyé, de sorte qu’un id de trace peut voyager de bout en bout. Cite-le dans toute demande de support.
Transitions de cycle de vie#
Une transition se déclenche par un POST vers un sous-chemin d’action : POST …/<id>/publish/, …/cancel/, …/revoke/.
Une transition qui ne peut pas s’appliquer — annuler un brouillon, publier quelque chose de déjà publié — est un 409 avec un code spécifique (not_cancellable, not_publishable, not_unpublishable). Ce n’est jamais un 200 qui n’a silencieusement rien fait.
Suppression#
DELETE renvoie un stub, pas un 204 :
{ "id": "0194d1c0-…", "object": "shift", "deleted": true }
Un DELETE ne prend jamais de corps. Tout ce dont une suppression a besoin se trouve dans le chemin ou la chaîne de requête, si bien qu’un client généré peut les exprimer toutes. (HTTP laisse indéfinie la sémantique d’un corps de DELETE et OpenAPI 3.0 dit aux consommateurs de l’ignorer ; un champ de corps ici disparaîtrait donc silencieusement de ton client et reviendrait sous forme d’un 400 que le schéma ne saurait expliquer.)
Supprimer n’est pas le verbe doux#
Là où une ressource offre les deux, ce ne sont pas des synonymes, et le mauvais choix n’est pas rattrapable :
Ressource |
|
Le verbe doux |
|---|---|---|
|
supprime le shift ; les personnes assignées à un shift publié sont notifiées et toute offre active sur la bourse aux shifts est retirée au préalable |
|
|
suppression terminale : accès révoqué, la personne quitte toutes les collections et un |
|
|
uniquement tant qu’elle est en attente ou refusée — une demande confirmée ou révoquée porte une écriture au grand livre, c’est donc un |
|
Enregistrements récurrents#
Supprimer une occurrence d’un enregistrement shifts ou availability récurrent nécessite une portée explicite :
DELETE …/<id>/?scope=this|following|all # shifts
DELETE …/<id>/?scope=single|following|all # availability
Omets-la sur une série et tu obtiens un 409 delete_scope_required plutôt qu’une supposition sur ton intention.
Limites de débit#
Un 429 porte un en-tête Retry-After. Respecte-le, et applique un back-off exponentiel en cas de répétition.