Erreurs
La forme
Une erreur porte toujours le champ error, et rien d’autre dans la plupart des
cas :
{ "error": "description lisible" }Ce champ est un texte, pas un code. Il est écrit pour être lu par une
personne, et son libellé peut changer sans préavis. N’écrivez jamais de
condition dessus : branchez sur le code HTTP, et sur le champ code
lorsqu’il est présent.
Deux familles d’erreurs portent, elles, des champs supplémentaires exploitables par un programme : le refus pour module non activé et le refus pour maintenance. Les deux sont détaillés plus bas.
Les codes
| Code | Ce qu’il signifie ici | Ce qu’il faut faire |
|---|---|---|
400 | requête mal formée, ou organisation non résolue | corriger l’appel |
401 | identité absente, invalide, ou session terminée | se réauthentifier |
403 | identité valide, mais l’accès est refusé | lire la section suivante |
404 | introuvable, ou hors de votre périmètre | vérifier l’identifiant |
409 | demande légitime que l’état en place interdit | lire le message |
422 | données refusées par une règle métier | corriger les données |
423 | maintenance en cours, écritures suspendues | réessayer plus tard |
429 | limite de débit atteinte | ralentir, puis réessayer |
500 | défaillance de notre côté | réessayer, puis signaler |
Les deux refus en 403, et pourquoi ils ne se résolvent pas pareil
C’est la distinction la plus importante de cette page, et celle qui coûte le
plus de temps quand on la rate. Deux situations très différentes répondent
403, et le même message « c’est refusé » mène à deux démarches opposées.
Ce droit vous manque
{ "error": "insufficient permissions: procurement.read required" }Le module dont relève la route est actif pour votre organisation. C’est
votre compte qui ne détient pas la permission que la route réclame — ici
procurement.read. Le message la nomme explicitement.
Comment cela se résout : auprès de l’administrateur de votre organisation, qui attribue les rôles et leurs permissions. C’est une démarche interne, sans délai ni contrat, et le changement prend effet à la requête suivante — les droits sont relus à chaque appel, pas à la connexion.
Un cas particulier porte le même code mais un autre message :
{ "error": "you are not a member of this organization" }Là, ce n’est pas une permission qui manque : l’en-tête X-Organization-Id
désigne une organisation à laquelle votre compte n’appartient pas. Vérifiez
l’en-tête avant de demander un droit.
L’ordre des contrôles compte. Sur une route relevant d’un module, la
souscription est vérifiée avant la permission. Un compte qui n’a ni le
module ni le droit reçoit donc module_disabled, et obtenir le droit ne
changerait rien. Traitez la souscription d’abord.
Enfin, un rappel qui n’est pas une subtilité d’API mais une règle de construction : un droit refusé dans l’interface l’est aussi par l’API. Le grisage et les cadenas que vous voyez dans le menu sont un confort d’affichage ; l’autorité est côté serveur, et un appel direct ne contourne rien.
404 : introuvable ou hors périmètre
Le 404 est délibérément ambigu entre « cette ressource n’existe pas » et
« cette ressource existe, mais appartient à une autre organisation ».
Distinguer les deux permettrait de deviner l’existence d’une ressource en
essayant des identifiants, et donc d’apprendre ce que contient l’espace d’une
autre collectivité sans y avoir accès.
Pratiquement : si un identifiant que vous savez valide répond 404, vérifiez
d’abord votre en-tête X-Organization-Id. C’est l’explication neuf fois sur
dix.
422 : la donnée est refusée, pas la requête
Un 422 signifie que l’appel était bien formé et autorisé, mais que son contenu
ne passe pas une règle métier — un fichier d’import sans ligne d’en-tête, une
correspondance de colonnes absente, un champ de configuration manquant. Le
message décrit la règle en français.
Un 422 ne liste pas les champs fautifs dans un champ structuré : il n’y a
pas de tableau d’erreurs par attribut. Le message est la seule indication.
Lorsqu’un appel en renvoie un, affichez le message tel quel à l’utilisateur
plutôt que de le reformuler.
Un 400, lui, dit que la requête n’était pas exploitable : corps illisible,
identifiant qui n’est pas un UUID, paramètre obligatoire absent, valeur hors
d’une liste fermée.
409 : l’état en place s’y oppose
La demande est légitime, mais l’état actuel l’interdit : ajouter un membre qui
l’est déjà, supprimer le dernier rôle par lequel une organisation peut encore
être administrée, signer un document qui ne l’est pas encore. Le message dit
laquelle de ces situations s’applique. Un 409 ne se résout pas en réessayant.
423 : maintenance
Pendant une opération de maintenance, l’API protège les données en refusant
les écritures et continue de servir les lectures. Un POST, PUT, PATCH
ou DELETE reçoit alors 423, avec un corps exploitable :
{
"error": "maintenance_read_only",
"mode": "…",
"message": "…",
"ends_at": "2026-10-02T12:00:00Z"
}Le champ error prend la valeur maintenance_read_only lorsque toute
l’application est en lecture seule, et maintenance_section lorsque seule une
partie l’est — le champ section nomme alors laquelle. ends_at donne la fin
annoncée, reprise dans l’en-tête Retry-After lorsqu’elle est connue.
Deux choses ne sont jamais bloquées, et c’est volontaire : la connexion, et l’ouverture des documents déjà produits.
429 : limite de débit
Deux origines possibles, deux messages distincts : le plafond des routes de
connexion, compté par adresse IP, et celui des appels par clé d’API. Dans les
deux cas, respectez Retry-After lorsqu’il est présent et n’enchaînez pas les
tentatives. Voir
Authentification, section sur les limites de débit.
Les documents répondent en HTML, pas en JSON
Les rapports et exports s’ouvrent dans un onglet de navigateur. Quand leur lien
n’est plus valable, l’API ne renvoie donc pas du JSON mais une page lisible,
sous le code 401, qui dit de rouvrir le document depuis l’application.
Le motif exact — lien expiré, lien déjà consommé, paramètres modifiés — n’est
jamais distingué dans la réponse. Un client qui appelle ces routes doit
donc traiter le 401 comme « demandez un nouveau lien », sans chercher à
analyser le corps. Voir
Référence, section sur les documents.
Limites connues
- Un seul code lisible par un programme à ce jour :
module_disabled. Tous les autres refus ne se distinguent que par leur code HTTP et un texte. - Les messages mélangent le français et l’anglais selon leur ancienneté. Les afficher tels quels à un utilisateur final n’est donc pas toujours satisfaisant, et les traduire par correspondance de texte est fragile.
- Pas d’identifiant de requête renvoyé avec une erreur
500: pour un signalement au support, notez l’heure, la route et l’organisation.