Erreurs
Format des erreurs 221 Pay, erreurs par champ, limite de requêtes, codes stables et conduite à tenir pour chacun.
Toute erreur de l’API 221 (221 Pay, routes de données, routes de projet,
404 et 405 compris) a la même enveloppe : un code stable à tester dans le
code, un message lisible, un request_id à transmettre au support, fields
quand des champs sont refusés, et des membres propres au code (limit,
reset_at, retry_after_s…), au même niveau.
{
"code": "INVALID_REQUEST",
"message": "Certains champs sont invalides.",
"request_id": "2f6c9a0e-5b1d-4e7f-8c3a-9d0b1e2f3a4c",
"fields": {
"customer_phone": "Numéro mobile invalide pour le pays de ce moyen de paiement.",
"offer_code": "offer_code doit contenir de 4 à 16 lettres ou chiffres."
}
}- Tester
code, jamaismessageni le statut HTTP : le texte peut changer, et un même code peut porter un statut différent selon la route. fieldsregroupe un message par champ refusé, pour afficher chaque erreur à côté de son champ.messageest en français ; en anglais avecAccept-Language: enou le paramètre?lang=en.request_idest aussi renvoyé dans l’en-têteX-Request-Idde chaque réponse.
Limite de requêtes
600 requêtes par minute et par projet. Au-delà : 429 RATE_LIMITED, avec
l’en-tête Retry-After, le délai d’attente en secondes.
HTTP/1.1 429 Too Many Requests
Retry-After: 12C’est la seule limite des routes 221 Pay : le quota quotidien
(429 QUOTA_EXCEEDED) ne concerne que les routes de données. Voir
Authentification, section « Quota quotidien ».
Méthode non autorisée
Une route de /v1 appelée avec une autre méthode HTTP répond 405 avec
l’en-tête Allow, la liste des méthodes acceptées, et la même enveloppe que
les autres erreurs, avec le code METHOD_NOT_ALLOWED. Le message suit
Accept-Language (français par défaut, anglais si la valeur commence par en).
HTTP/1.1 405 Method Not Allowed
Allow: GET, POST
Content-Type: application/json
{"code": "METHOD_NOT_ALLOWED", "message": "Méthode non autorisée. Méthodes acceptées : GET, POST.", "request_id": "2f6c9a0e-5b1d-4e7f-8c3a-9d0b1e2f3a4c"}Codes
| HTTP | code | Cause | Conduite |
|---|---|---|---|
| 400 | INVALID_REQUEST | Corps, paramètre ou en-tête invalide. fields donne un message par champ refusé, y compris un offer_code mal formé, un champ inconnu du corps de POST /v1/payments (fields.<nom du champ>), un Idempotency-Key absent sur POST /v1/payments, /v1/refunds ou /v1/payouts (clé Idempotency-Key), ou un remboursement supérieur au reste remboursable (fields.amount). | Corriger les champs indiqués. |
| 400 | OFFER_INVALID | Code promo inconnu, expiré ou hors des conditions de la commande. | Retirer le code promo ou en utiliser un autre. |
| 400 | PROJECT_REQUIRED | Session du tableau de bord sans projet désigné. | Envoyer l’en-tête X-221-External-Ref, ou appeler avec la clé du projet. |
| 400 | BELOW_MINIMUM | Virement bancaire sous le minimum. Champs min_amount et max_amount, et fields.amount. | Envoyer au moins min_amount. Voir Retraits. |
| 400 | ABOVE_MAXIMUM | Virement bancaire au-dessus du maximum par retrait. Champs min_amount et max_amount, et fields.amount. | Réduire le montant ou faire plusieurs retraits. |
| 400 | INVALID_CODE | Code de vérification d’un compte de retrait incorrect. Champ attempts_left. | Saisir le code reçu par e-mail. |
| 400 | MISSING_CAPTURES | Vérification d’identité envoyée sans toutes les photos (/v1/kyc/...). Champ missing, la liste des photos à envoyer. | Envoyer les photos manquantes, puis renvoyer la vérification. |
| 401 | INVALID_API_KEY | Clé mal formée, inconnue ou révoquée. | Vérifier la clé. Voir Authentification. |
| 401 | UNAUTHENTICATED | Ni clé ni session, ou clé d’un autre projet. | Envoyer la clé du projet. |
| 403 | MISSING_SCOPE | Clé sans la portée exigée par la route (payments, data) ou par l’événement webhook. Membre scope. | Créer une clé avec cette portée. |
| 403 | LIVE_NOT_ENABLED | Appel en mode live (clé sk_221_pay_live_…) sur un projet dont le mode live n’est pas activé. | Faire activer le mode live par un administrateur du projet (voir Authentification), ou appeler avec une clé de test. |
| 403 | FORBIDDEN | Rôle insuffisant pour l’action. | Agir avec un compte administrateur ou une clé API. |
| 403 | KYC_REQUIRED | Retrait ou numéro de retrait avant vérification d’identité. Champ kyc_status. | Terminer la vérification. Voir Retraits. |
| 403 | MERCHANT_SUSPENDED | Compte marchand suspendu ou fermé. Champ merchant_status. | Contacter 221. Les lectures restent possibles. |
| 403 | MERCHANT_LIMIT_EXCEEDED | Plafond journalier ou mensuel atteint. Champs limit_type (daily ou monthly) et limit. | Attendre la période suivante ou demander un relèvement. |
| 403 | WITHDRAWAL_LIMIT_EXCEEDED | Plafond de retrait atteint. Champs limit_type (per_withdrawal, daily_count, daily_amount ou monthly_amount) et limit. | Réduire le montant, ou attendre la remise à zéro (minuit, heure de Dakar). |
| 403 | MONTHLY_LIMIT_REACHED | Total mensuel des virements bancaires atteint. Champ remaining, le montant encore possible ce mois-ci. | Réduire le montant à remaining au plus, ou attendre le 1er du mois suivant. |
| 404 | NOT_FOUND | Objet inexistant ou d’un autre projet. | Vérifier l’identifiant. |
| 404 | FEATURE_DISABLED | Fonction désactivée par 221 (codes promo, par exemple). | Appeler sans cette fonction. |
| 409 | DUPLICATE_MERCHANT_REFERENCE | merchant_reference déjà utilisée dans le projet. | Rejouer avec l’Idempotency-Key d’origine pour retrouver le paiement, ou choisir une nouvelle référence pour une nouvelle commande. |
| 409 | IDEMPOTENCY_CONFLICT | Idempotency-Key déjà utilisée avec un autre corps. | Rejouer le corps d’origine. |
| 409 | QUOTE_EXPIRED | Devis de remboursement ou de retrait expiré (2 minutes). fields.quote_hash porte le message. | Demander un nouveau devis. |
| 409 | QUOTE_INVALID | quote_hash inconnu, déjà utilisé, d’un autre compte marchand, ou qui ne correspond pas à la requête (autre montant, autre fee_payer). fields.quote_hash porte le message. | Demander un nouveau devis. |
| 409 | DESTINATION_NOT_VERIFIED | Devis ou retrait vers un numéro ou un compte bancaire dont le code de vérification n’a pas été saisi. Le message dit « numéro » ou « compte bancaire » selon la destination. | Saisir le code, puis réessayer. Voir Retraits. |
| 409 | INSUFFICIENT_FUNDS | Solde disponible insuffisant. | Attendre de nouveaux encaissements ou réduire le montant. |
| 409 | INVALID_STATE | L’objet n’est pas dans un état qui permet l’action : litige déjà ouvert sur le paiement, ou litige qui ne peut plus être accepté, contesté ou complété ; numéro de retrait déjà vérifié (/resend). Le message nomme l’état actuel. | Relire l’objet et agir selon son état. |
| 409 | KYC_ALREADY_SUBMITTED | Vérification d’identité déjà envoyée à l’examen (/v1/kyc/...). | Ne pas la renvoyer : suivre GET /v1/kyc/status. |
| 409 | OFFER_CODE_TAKEN | POST /v1/offers avec un code promo qui existe déjà sur le projet. | Choisir un autre code. |
| 410 | CODE_EXPIRED | Code de vérification d’un compte de retrait expiré (10 minutes). | Demander un nouveau code avec POST /v1/payout-destinations/{id}/resend. |
| 413 | REQUEST_TOO_LARGE | Corps de requête de plus de 1 Mio sur une route 221 Pay (l’envoi d’une pièce de litige a sa propre limite), de plus de 64 Kio sur une route de données. | Réduire le corps. |
| 429 | TOO_MANY_ATTEMPTS | 5 essais incorrects sur le code de vérification. | Demander un nouveau code. |
| 429 | RATE_LIMITED | Limite de requêtes atteinte. Les routes de données ajoutent le membre retry_after_s. | Attendre Retry-After secondes. |
| 500 | INTERNAL_ERROR | Erreur interne imprévue de 221 Pay. | Rejouer avec la même Idempotency-Key et le même corps ; si elle persiste, transmettre le request_id à 221. |
| 503 | CHANNEL_UNAVAILABLE | Moyen de paiement non activé sur le projet ou momentanément fermé. Pour un retrait, les fonds réservés sont restitués avant la réponse. | Proposer un autre moyen de paiement ou réessayer plus tard. |
| 503 | PAYMENTS_UNAVAILABLE | 221 Pay momentanément indisponible sur ce serveur. | Réessayer plus tard, avec la même Idempotency-Key. |
| 503 | NOT_CONFIGURED | Fonction non installée sur ce serveur : vérification d’identité (/v1/kyc/...) ou adresse publique des liens de paiement (POST /v1/payment-links). Différent de CHANNEL_UNAVAILABLE : ici rien n’est à réessayer. | Contacter 221 avec le request_id. |
| 507 | STORAGE_FULL | Stockage des photos de vérification d’identité momentanément plein (/v1/kyc/...). | Réessayer plus tard. |
Codes des routes de données et de projet
Ces codes, écrits en majuscules comme les autres, viennent des routes de
données, des clés, des webhooks et de l’équipe d’un projet. Les membres propres
au code sont au premier niveau du corps, à côté de message. Le statut HTTP
peut varier selon la route : tester code.
| HTTP | code | Cause | Membres |
|---|---|---|---|
| 400 | BAD_REQUEST | Corps JSON illisible ou absent sur une route de données ou de projet (aucun fields). Un champ de type incorrect donne plutôt INVALID_REQUEST avec fields. | |
| 401 | API_KEY_EXPIRED | Clé expirée. | expires_at |
| 401 | API_KEY_REVOKED | Clé révoquée. | revoked_at |
| 403 | API_KEY_DISABLED | Clé désactivée par 221 pour usage abusif. | disabled_at |
| 403 | FORBIDDEN_ROLE | Rôle du projet insuffisant pour l’action. | role, required |
| 429 | QUOTA_EXCEEDED | Quota quotidien des routes de données atteint. | limit, reset_at |
| 400 | INVALID_URL | URL de webhook refusée (https vers un hôte public). | fields.url |
| 409 | SUBSCRIPTION_EXISTS | Cette URL a déjà un point de terminaison. | url |
| 409 | SUBSCRIPTION_LIMIT | 5 points de terminaison par projet atteints. | limit |
| 409 | KEY_LIMIT_REACHED | 5 clés actives par projet atteintes. | limit |
| 409 | PROJECT_LIMIT_REACHED | Limite de projets atteinte. | limit |
| 404 | PROJECT_NOT_FOUND | Projet inexistant ou qui n’est pas à l’appelant. | |
| 409 | LAST_ADMIN | Le projet doit garder un administrateur. | |
| 403 | OWNER_PROTECTED | Seul le titulaire change son rôle ou quitte le projet. | |
| 409 | OWNER_MUST_STAY | Le titulaire reste administrateur. | |
| 409 | ALREADY_MEMBER, ALREADY_INVITED | Personne déjà membre, ou invitation déjà en attente. | email |
| 404 | INVITATION_NOT_FOUND | Invitation inconnue ou retirée. | |
| 410 | INVITATION_EXPIRED | Invitation expirée. | expires_at |
| 410 | INVITATION_USED | Invitation déjà utilisée. | |
| 403 | INVITATION_EMAIL_MISMATCH | Invitation adressée à une autre adresse. | |
| 409 | EMAIL_NOT_VERIFIED | Adresse e-mail non confirmée. | |
| 403 | REAUTHENTICATION_REQUIRED | Reconnexion de moins de 10 minutes exigée. | |
| 403 | INVALID_PASSWORD | Mot de passe incorrect. | |
| 409 | BALANCE_NOT_EMPTY | Suppression du projet impossible tant que des fonds ou un retrait sont en cours. | reason |
| 503 | DATASET_UNAVAILABLE | Jeu de données pas encore publié. | dataset |
| 503 | SERVER_BUSY | Service occupé : réessayer dans quelques secondes. |
Les routes /auth (connexion, mot de passe) répondent aussi avec une forme
plate code, message et request_id (aussi dans l’en-tête X-Request-Id).
Leurs codes sont ceux du service de connexion, par exemple INVALID_EMAIL,
INVALID_EMAIL_OR_PASSWORD ou VALIDATION_ERROR (corps illisible, avec un seul
message générique). TOO_MANY_REQUESTS ajoute retry_after_s et l’en-tête
X-Retry-After.
Réessayer sans risque
| Situation | Réessai |
|---|---|
400, 401, 403, 404, 409 | Non : corriger d’abord la requête. |
429 | Oui, après Retry-After secondes. |
500, 503, délai dépassé, coupure réseau | Oui, avec la même Idempotency-Key et le même corps. |
5xx sur un devis | Oui : un devis ne déplace pas d’argent. |
POST /v1/payments, POST /v1/refunds et POST /v1/payouts exigent
Idempotency-Key : un réessai ne crée jamais une seconde opération. Voir
Paiements, section « Issue inconnue ».
Signaler un incident
Transmettre à 221 le request_id, l’heure de l’appel et la route appelée.
Jamais la clé API ni le secret de webhook.