Apparence
Codes d'erreur
Le code HTTP porte le sens, le corps porte le message.
json
{
"error": "Le nom est requis"
}Codes retournés
| Code | Signification | Réaction attendue |
|---|---|---|
200 | Succès | — |
201 | Ressource créée | Lire l'id dans la réponse |
400 | Paramètres invalides | Corriger le corps de la requête ; ne pas réessayer tel quel |
401 | Clé absente ou invalide | Vérifier la clé ; ne pas réessayer |
403 | La ressource ne vous appartient pas | Vérifier le workspace de la clé |
404 | Ressource introuvable | Vérifier l'identifiant |
409 | Conflit — siège déjà pris, slug déjà utilisé | Rafraîchir l'état et proposer une alternative |
500 | Erreur serveur | Réessayer avec temporisation ; nous signaler si ça persiste |
Le conflit mérite une attention particulière
C'est l'erreur normale d'une billetterie sous charge : deux acheteurs visent le même siège, un seul l'obtient.
Sur les endpoints de réservation, le message est le même quelle que soit la place fautive :
json
{
"error": "One or more seats are not available"
}Il ne nomme pas les sièges en conflit : pour savoir lesquels, relisez l'état de l'événement avec GET /api/events/{eventId}/seats et comparez avec ce que vous demandiez.
Traitez ce code comme un cas fonctionnel, pas comme un incident : rafraîchissez le plan et invitez l'utilisateur à choisir une autre place.
ts
import { ApiException } from '@mitoera/sdk';
try {
await client.holds.book(eventId, seatKeys, holdToken);
} catch (e) {
if (e instanceof ApiException && e.statusCode === 409) {
return afficherMessage('Un siège vient d\'être pris, merci d\'en choisir un autre.');
}
throw e;
}php
use Mitoera\Sdk\Exception\ApiException;
try {
$client->holds->book($eventId, $seatKeys, $holdToken);
} catch (ApiException $e) {
if ($e->statusCode === 409) {
return $this->afficherMessage('Un siège vient d\'être pris, merci d\'en choisir un autre.');
}
throw $e;
}Diagnostiquer un 401
Un refus d'authentification nomme sa cause. Le corps porte un error — code machine, stable — plus un message lisible et un hint qui dit quoi faire :
json
{
"error": "invalid_key",
"message": "Unknown API key.",
"hint": "Check the keyId. Sandbox keys start with pk_test_, production keys with pk_live_ — a key issued for one instance is unknown to the other.",
"keyId": "pk_live_inexistante"
}error | Cause | Correctif |
|---|---|---|
malformed_credentials | Des identifiants ont été envoyés mais restent illisibles | Format attendu : Authorization: Basic base64(keyId:secret), ou la paire X-Api-Key-Id / X-Api-Key-Secret — les deux moitiés sont requises |
invalid_key | keyId inconnu de cette instance | Vérifier le préfixe : une clé pk_test_ n'existe pas en production, et inversement |
invalid_secret | Le secret ne correspond pas à ce keyId | Recopier les deux valeurs depuis la même paire, sans mélanger deux environnements |
key_revoked | Identifiants corrects, mais clé désactivée | Créer une nouvelle clé dans le back-office et remplacer keyId et secret |
subscription_required | Identifiants valides, mais le workspace n'a plus de plan actif | Activer un plan depuis le back-office (Abonnement), ou basculer sur une clé de test pk_test_ |
keyId n'est renvoyé que lorsqu'il est connu de l'appelant — le secret ne l'est jamais.
subscription_required n'est pas un problème d'identifiants
Votre clé est bonne : c'est l'abonnement du workspace qui ne l'est plus — résilié, ou impayé après les relances. Une clé suspendue ainsi redevient active d'elle-même dès que le paiement reprend ; inutile d'en créer une nouvelle.
Un prélèvement en cours de relance (past_due) ne coupe pas l'accès.
Un ordre de vérification qui protège vos clés
invalid_secret est renvoyé avant key_revoked. Quelqu'un qui possède un keyId sans le secret n'apprend donc jamais si la clé est encore active : seul un appelant ayant déjà prouvé la possession du secret voit key_revoked.
Les requêtes que le mécanisme de clés ne reconnaît pas
Avant de produire l'un des codes ci-dessus, l'API doit identifier votre requête comme une tentative d'authentification par clé. Elle ne le fait que dans trois cas : un en-tête Authorization: ApiKey …, un Authorization: Basic … dont le contenu décodé contient un :, ou la paire complète X-Api-Key-Id + X-Api-Key-Secret.
Tout le reste tombe sur le pare-feu JWT, et la réponse change de forme — ni error, ni hint, et code y désigne le statut HTTP, pas une cause :
json
{ "code": 401, "message": "JWT Token not found" }Formes concernées, toutes mesurées :
| Requête | Réponse |
|---|---|
| Aucun en-tête d'authentification | pare-feu JWT |
Authorization: Basic vide | pare-feu JWT |
Authorization: Basic dont le décodage ne contient pas de : | pare-feu JWT |
X-Api-Key-Id sans X-Api-Key-Secret — ou l'inverse | pare-feu JWT |
Authorization: Basic base64(":") | malformed_credentials |
Authorization: ApiKey vide ou illisible | malformed_credentials |
La frontière n'est donc pas « avec ou sans identifiants » mais « reconnaissable ou non comme authentification par clé ». Un client qui teste uniquement error doit prévoir la seconde forme — typiquement quand un en-tête est oublié en production après avoir été injecté par un proxy en développement.
/auth/embed-token : même information, champs permutés
Cet endpoint sert l'éditeur embarqué, dont le widget déployé lit le champ error pour l'afficher à l'utilisateur. error y porte donc la phrase lisible, et le code machine passe dans code :
json
{
"error": "Unknown API key.",
"code": "invalid_key",
"hint": "Check the keyId. Sandbox keys start with pk_test_…"
}C'est un choix de compatibilité, pas une incohérence : les deux mêmes informations sont présentes, sous des clés inversées. Un keyId ou un secret absent du corps donne un simple {"error": "keyId et secret requis"}.
Créer une clé de production sans plan
Le même contrôle s'applique en amont, à la création : POST /api/api-keys avec un scope de production refuse un workspace sans abonnement actif, en 403 cette fois — vous êtes bien identifié, c'est l'opération qui est interdite.
json
{
"error": "subscription_required",
"message": "Un plan actif est nécessaire pour créer une clé de production.",
"hint": "Activez un plan depuis le back-office (Abonnement). Les clés de test (pk_test_) restent disponibles sans abonnement."
}Les clés de sandbox ne sont jamais concernées : elles se créent et s'utilisent sans abonnement.
401 ou 403 ?
401 | 403 | |
|---|---|---|
| Sens | Je ne sais pas qui vous êtes | Je sais qui vous êtes, mais pas pour cette ressource |
| Causes | Header absent, clé mal formée, clé révoquée, workspace sans plan actif | Ressource d'un autre workspace, clé publique sur un endpoint d'écriture, création d'une clé de production sans plan |
| Réessayer ? | Non — corriger la clé | Non — corriger la cible ou la clé |
Exceptions côté SDK
| Classe | Correspond à |
|---|---|
AuthException | 401 et 403, plus les clés manquantes détectées avant l'appel |
ApiException | Toutes les autres erreurs HTTP — porte statusCode et le message serveur |
MitoeraException | Racine de la hiérarchie |
En PHP (SDK ≥ v1.0.1), la chaîne est AuthException → ApiException → MitoeraException → RuntimeException. Attraper ApiException capture donc aussi les erreurs d'authentification : placez le catch (AuthException) en premier si vous voulez les distinguer. statusCode vaut 0 quand l'erreur est détectée avant tout appel réseau (keyId ou secret manquant).
Stratégie de réessai
| Code | Réessayer ? |
|---|---|
400, 401, 403, 404 | Non — la requête est fautive, la rejouer donnera le même résultat |
409 | Non automatiquement — l'état a changé, il faut une décision utilisateur |
500 | Oui, avec temporisation exponentielle, deux ou trois fois |
| Erreur réseau / timeout | Oui — mais pour book, vérifiez d'abord l'état des sièges avec listSeats() avant de rejouer, pour ne pas doubler une réservation |

