Erreurs API Yasmine
Format RFC 7807 sur
tout /v1/*. Chaque classe d'erreur a un type stable ;
router dessus côté SDK, pas sur le code HTTP seul. Format URL :
https://docs.yasmine.akidly.com/errors/<slug> → redirige vers
la section ci-dessous.
invalid_api_key401
Ce que ça veut dire. Header Authorization absent,
mal formé (pas de Bearer), ou clé invalide/révoquée. Le header
WWW-Authenticate: Bearer est propagé.
Causes fréquentes
- Clé copiée avec espace/newline en fin (le serveur hash la clé entière).
- Clé révoquée via
DELETE /v1/me/api-keys/{id}. - Bearer prefix manquant :
Authorization: yk_...au lieu deBearer yk_....
Comment corriger
- Régénérer une clé via
POST /v1/me/api-keys. - Vérifier l'état de vos clés :
GET /v1/me/api-keys. - Consulter les events d'audit de la clé :
GET /v1/me/api-keys/{id}/events.
Exemple de réponse
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer
Content-Type: application/problem+json
{
"type": "https://docs.yasmine.akidly.com/errors/invalid_api_key",
"title": "Unauthorized",
"status": 401,
"detail": "Authorization: Bearer <reseller_api_key> requis",
"instance": "/v1/calls",
"request_id": "a1b2c3d4"
}
↑ Retour en haut
forbidden403
Ce que ça veut dire. Clé valide, mais sans droit sur cette
ressource. Aucun endpoint /v1/* ne l'émet aujourd'hui — slug réservé
pour une future séparation admin/reader.
Comment corriger
- Contacter le support si vous êtes admin et voyez ce code.
validation_error422
Ce que ça veut dire. Le body du POST ne respecte
pas le schéma Pydantic du endpoint. Inclut aussi les champs inconnus :
extra="forbid" sur CallCreate et CustomerInput
— un typo comme custommer au lieu de customer, ou
phonne_number au lieu de phone_number dans
customer, est refusé (P1-12). Le champ errors[]
liste chaque violation avec son chemin exact.
Causes fréquentes
- Typo sur le nom d'un champ (
errors[].type === "extra_forbidden"). - Type mauvais :
amountenvoyé en nombre au lieu de string. - Contrainte violée :
customer.namevide,amount ≤ 0. - Enum :
countryhorsMA/DZ/TN/FR.
Comment corriger
- Parser
errors[].loceterrors[].msgpour cibler le champ exact (loc peut avoir plusieurs segments pour les champs nested, ex.["body", "customer", "phone_number"]). - Consulter les schémas
CallCreateetCustomerInputdans la spec OpenAPI. - Pour un champ en trop : vérifier l'orthographe ou supprimer du body.
Exemple de réponse
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/problem+json
{
"type": "https://docs.yasmine.akidly.com/errors/validation_error",
"title": "Unprocessable Entity",
"status": 422,
"detail": "Un ou plusieurs champs du payload sont invalides.",
"instance": "/v1/calls",
"request_id": "a1b2c3d4",
"errors": [
{ "loc": ["body", "customer", "phonne_number"], "msg": "Extra inputs are not permitted", "type": "extra_forbidden" },
{ "loc": ["body", "amount"], "msg": "Decimal input should be greater than 0", "type": "greater_than" }
]
}
↑ Retour en haut
invalid_key_id400
Ce que ça veut dire. DELETE /v1/me/api-keys/{key_id}
ou GET /v1/me/api-keys/{key_id}/events avec UUID malformé (P1-4/P1-6).
Distingué du 404 pour aider à diagnostiquer un bug de construction d'URL côté client.
Comment corriger
key_iddoit être un UUID v4 strict (format8-4-4-4-12).- Relire la valeur retournée par
POST /v1/me/api-keys(id).
invalid_period_format400
Ce que ça veut dire. GET /v1/me/usage?period=...
avec format non-YYYY-MM ou mois hors 01-12 (P1-1). Exemples de valeurs
rejetées : ?period=abc, ?period=2026-13,
?period=2026-00.
Comment corriger
- Format strict
^\d{4}-(0[1-9]|1[0-2])$. - Omettre
periodpour obtenir le mois courant UTC (défaut).
invalid_status_filter400
Ce que ça veut dire.
GET /v1/me/webhooks/deliveries?status=... avec valeur hors
delivered/failed/pending (P1-2).
Comment corriger
- Utiliser exactement
delivered,failed, oupending. - Le
detailde la réponse liste les valeurs acceptées.
invalid_cursor400
Ce que ça veut dire. Curseur de pagination (P1-3) malformé,
avec signature tampered, ou CURSOR_SIGNING_KEY tournée côté serveur.
S'applique à tous les endpoints paginés :
GET /v1/calls, GET /v1/me/transactions,
GET /v1/me/webhooks/deliveries,
GET /v1/me/api-keys/{id}/events.
Comment corriger
- Refaire la 1re requête sans le paramètre
cursor. - Ne pas stocker de curseurs en durable (TTL 24 h + rotation possible de la clé de signature).
cursor_expired400
Ce que ça veut dire. Curseur plus vieux que 24 h (TTL fixe). Yasmine ne propose pas de curseur « temps-indépendant » pour limiter la surface en cas de fuite de clé.
Comment corriger
- Refaire la 1re requête sans
cursor. - Reprendre la pagination depuis le début avec la page 1 fraîche.
missing_idempotency_key400
Ce que ça veut dire. Header Idempotency-Key
absent sur POST /v1/calls. Obligatoire depuis P0-1 pour éviter un
double appel en cas de retry réseau.
Comment corriger
- Générer un UUID v4 par requête (
uuidgen,crypto.randomUUID(), etc.). - Même valeur dans un retry automatique → la réponse cachée est servie (pas de double appel).
- Fenêtre de dedup : 24 h.
idempotency_key_empty400
Ce que ça veut dire. Header Idempotency-Key
présent mais vide. Doit faire au moins 1 caractère.
idempotency_key_too_long400
Ce que ça veut dire. Header Idempotency-Key
> 255 caractères. Limite stricte serveur pour éviter un abus storage.
idempotency_key_conflict409
Ce que ça veut dire. La même Idempotency-Key
a déjà été utilisée dans la fenêtre 24 h avec un body différent.
Générer une nouvelle clé pour une nouvelle requête. Le body original n'est
jamais ré-exposé (anti-BOPLA).
Comment corriger
- Ne jamais réutiliser une
Idempotency-Keypour 2 requêtes sémantiquement différentes. - Un retry légitime (même body) renvoie la réponse d'origine, pas une erreur.
order_external_id_already_exists409
Ce que ça veut dire. Un POST /v1/calls
envoie une order.external_id déjà utilisée pour ce merchant.
Les commandes sont créées par INSERT strict (pas d'upsert) depuis Phase 2-final —
un second POST avec la même external_id est rejeté.
Causes fréquentes
- Retry d'une commande sans réutiliser la même
Idempotency-Key(qui aurait fait un replay). - Deux services du reseller qui partagent le même pool d'
external_idsans coordination. - Collision involontaire (formats courts type
CMD-001).
Comment corriger
- Retenter sur la commande existante : nouveau POST avec nouvelle
Idempotency-Key+order.previous_attemptsincrémenté. - Créer une commande distincte : générer une
external_idunique côté reseller. - Retry réseau pur : garder la même
Idempotency-Key→ replay bit-for-bit.
Exemple de réponse
HTTP/1.1 409 Conflict
Content-Type: application/problem+json
{
"type": "https://docs.yasmine.akidly.com/errors/order_external_id_already_exists",
"title": "Order external_id already exists",
"status": 409,
"detail": "An order with this external_id already exists for this merchant.",
"instance": "/v1/calls",
"request_id": "a1b2c3d4",
"hint": "To retry reaching the customer on this existing order, submit a new POST with a fresh Idempotency-Key and increment order.previous_attempts. To create a distinct order, use a unique external_id."
}
↑ Retour en haut
insufficient_balance402
Ce que ça veut dire. Solde strictement inférieur au minimum facturable (10 secondes) pour initier un appel. L'appel n'a pas été lancé.
Causes fréquentes
- Recharge automatique non configurée ou échouée.
- Consommation sur la période supérieure à l'attendu.
- Clé API partagée dev/prod qui consomme le même crédit.
Comment corriger
- Lire le solde :
GET /v1/me/balance. - Vérifier la consommation :
GET /v1/me/usage?period=YYYY-MM. - Recharger via le portail ou l'API facturation.
Exemple de réponse
HTTP/1.1 402 Payment Required
Content-Type: application/problem+json
{
"type": "https://docs.yasmine.akidly.com/errors/insufficient_balance",
"title": "Payment Required",
"status": 402,
"detail": "Solde disponible 3s < minimum facturable 10s.",
"instance": "/v1/calls",
"request_id": "a1b2c3d4",
"balance_seconds": 3,
"required_seconds": 10
}
↑ Retour en haut
rate_limited429
Ce que ça veut dire. Slug fallback générique émis si un
429 est levé hors du handler slowapi (rare). En pratique vous verrez plutôt
rate_limit_exceeded.
rate_limit_exceeded429
Ce que ça veut dire. Rate-limit par clé API dépassé (M3.6 C7). Le bucket est remis à zéro en glissant à la fin de la fenêtre.
Seuils actuels
POST /v1/calls: 60 / min / clé.POST /v1/calls/{id}/cancel: 120 / min.GET /v1/*(lectures) : 600 / min.POST /v1/me/webhooks*: 10 / min.POST /v1/me/api-keys: 5 / min.
Comment corriger
- Respecter le header
Retry-After(secondes) avant de retenter. - Exploiter
X-RateLimit-Limit/-Remaining/-Resetsur les 2xx pour prévenir la saturation. - Chaque 429 est enregistré comme event
rate_limitedsur la clé (P1-6) — visible dansGET /v1/me/api-keys/{id}/events.
Exemple de réponse
HTTP/1.1 429 Too Many Requests
Retry-After: 38
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1745229338
Content-Type: application/problem+json
{
"type": "https://docs.yasmine.akidly.com/errors/rate_limit_exceeded",
"title": "Too Many Requests",
"status": 429,
"detail": "Limite de frequence depassee (60 per 1 minute). Reessayer dans 38 seconde(s).",
"instance": "/v1/calls",
"request_id": "a1b2c3d4",
"retry_after": 38
}
↑ Retour en haut
not_found404
Ce que ça veut dire. Ressource absente ou non accessible au
reseller courant. Slug générique — certains endpoints utilisent un slug dédié
(call_not_found,
api_key_not_found,
webhook_not_configured).
call_not_found404
Ce que ça veut dire. GET /v1/calls/{id},
GET /v1/calls/{id}/recording ou
POST /v1/calls/{id}/cancel sur un call_id inexistant
OU appartenant à un autre reseller (M3.6 C8, pattern anti-énum P0-2).
Émis aussi par l'endpoint recording quand l'audio n'a pas
(encore) été produit (call jamais finalisé). Body byte-identique
dans tous les cas — pas de leak d'existence.
recording_gone410
Ce que ça veut dire. GET /v1/calls/{id}/recording
sur un appel dont l'enregistrement audio a été purgé après la
fenêtre de rétention (30 jours).
Pourquoi distinct de call_not_found
Pour permettre au reseller de différencier « jamais existé »
(404) vs « a existé mais purgé après rétention » (410). Si vous
stockez les recording_url côté reseller pour permettre
aux opérateurs d'écouter les appels passés, traitez le 410 comme
le signal de mettre à jour votre indexation locale (l'audio n'est
plus disponible).
Correctif
Aucun retry possible — l'audio est définitivement supprimé. Si vous avez besoin d'archiver les enregistrements au-delà de 30 jours, téléchargez-les côté reseller et stockez-les chez vous.
↑ Retour en hautapi_key_not_found404
Ce que ça veut dire. DELETE /v1/me/api-keys/{id}
ou GET /v1/me/api-keys/{id}/events sur une clé inexistante OU
appartenant à un autre reseller (P1-4, P1-6). Body byte-identique — anti-énum P0-2.
conflict409
Ce que ça veut dire. Slug générique 409. Les cas concrets
utilisent un slug dédié :
idempotency_key_conflict,
webhook_already_configured.
webhook_url_rejected400
Ce que ça veut dire. POST /v1/me/webhooks avec
URL refusée par le guard SSRF. Le body contient un champ reason
explicite.
Valeurs possibles de reason
invalid_url— URL non parseable.scheme_not_allowed— pashttps(ouhttpen dev).localhost_rejected—localhost/127.0.0.1interdits.dns_resolution_failed— le hostname ne résout pas.private_ip_rejected— IP privée (RFC 1918) ou link-local interdite.
webhook_already_configured409
Ce que ça veut dire. POST /v1/me/webhooks sur
un reseller qui a déjà un webhook actif. 1 webhook max par reseller.
Comment corriger
DELETE /v1/me/webhooksd'abord pour rotate.- Le nouveau POST retournera un secret HMAC frais.
webhook_not_configured404
Ce que ça veut dire.
GET /v1/me/webhooks ou DELETE /v1/me/webhooks sans
configuration active. Pas de webhook à lire/supprimer.
webhook_url_not_configured400
Ce que ça veut dire.
POST /v1/me/webhooks/test sans webhook actif (P1-2). Impossible de
tester une URL qui n'existe pas.
Comment corriger
- Configurer un webhook via
POST /v1/me/webhooksavant de tester.
payload_too_large413
Ce que ça veut dire. Émis sur POST /webhooks/whatsapp
quand le body dépasse 256 KiB (P0-6). Vérification avant HMAC
(défense en profondeur contre DoS et payloads falsifiés).
Contexte
- Meta envoie ~50 KB en pratique — marge 5×.
- Pas applicable aux endpoints
/v1/*(leur validation Pydantic coupe plus tôt). - Si vu côté Meta retry storm : contacter le support avec
request_id.
bad_request400
Ce que ça veut dire. Slug fallback 400 générique. Les cas
concrets utilisent un slug dédié (idempotency, cursor, period, key_id, webhook,
validation). Un bad_request brut sans contexte indique un chemin
d'erreur rare — contacter le support avec request_id.
internal_error500
Ce que ça veut dire. Erreur inattendue côté Yasmine (bug).
La stack trace est loguée côté serveur avec le request_id —
l'envoyer au support pour qu'il retrouve la trace exacte.
Exemple de réponse
HTTP/1.1 500 Internal Server Error
Content-Type: application/problem+json
{
"type": "https://docs.yasmine.akidly.com/errors/internal_error",
"title": "Internal Server Error",
"status": 500,
"detail": "Une erreur interne est survenue. Contacter le support avec la valeur de request_id.",
"instance": "/v1/calls",
"request_id": "a1b2c3d4"
}
↑ Retour en haut
language_not_supported_for_country422
Ce que ça veut dire. POST /v1/calls avec une combinaison
(country, language) non disponible. Aujourd'hui, seule la combinaison
country=FR + language=ar est rejetée. Les autres combinaisons
(MA/DZ/TN avec ar ou fr,
FR avec fr) sont valides.
Exemple de réponse
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/problem+json
{
"type": "https://docs.yasmine.akidly.com/errors/language_not_supported_for_country",
"title": "Unprocessable Entity",
"status": 422,
"detail": "La langue 'ar' n'est pas disponible pour le pays 'FR'. Combinaisons supportees : MA/DZ/TN avec `ar` ou `fr`, FR uniquement avec `fr`.",
"instance": "/v1/calls",
"request_id": "a1b2c3d4"
}
Remédiation
- Soit retirer le champ
languagedu payload — la langue locale du pays sera appliquée par défaut (MA/DZ/TN→ar,FR→fr). - Soit fournir une combinaison valide (
FR+fr, ou un pays maghrébin avecar/fr).
http_error4xx/5xx
Ce que ça veut dire. Slug fallback générique pour les codes HTTP
non mappés explicitement (cas marginal). Le code HTTP réel est toujours dans le champ
status du body. Ne pas dépendre de ce slug dans votre
code — un code HTTP donné finira toujours par avoir un slug stable dédié.
Remédiation
- Logger le
request_idet contacter le support si vous voyez ce slug. - Côté SDK reseller : router sur le code HTTP en fallback quand
typetermine par/http_error.