# Les erreurs

Un statut, et un corps stable — le même pour toutes les ressources.
Un statut HTTP, et un corps stable — le même pour toutes les ressources. Le `code` est le contrat : votre programme s’y fie ; le `message` est pour un humain, en français ou en anglais selon `Accept-Language`.

## Le corps

```json
{
  "error": {
    "type": "invalid_request_error",
    "code": "operator_required",
    "message": "Choisissez votre opérateur.",
    "details": {
      "operators": [
        {
          "code": "mtn",
          "name": "MTN"
        },
        {
          "code": "moov",
          "name": "Moov"
        }
      ]
    },
    "request_id": "req_01j9x3t8m2k4c7vd9qhs"
  }
}
```

| Champ | Type | Requis | Ce que c'est |
| --- | --- | --- | --- |
| `type` | « authentication_error » · « invalid_request_error » · « rate_limit_error » · « api_error » | oui | La famille : qui doit corriger. |
| `code` | « missing_api_key » · « invalid_api_key » · « insufficient_permissions » · « parameter_missing » · « parameter_invalid » · « resource_missing » · « payload_too_large » · « idempotency_key_reuse » · « rate_limited » · « merchant_inactive » · « live_mode_not_enabled » · « currency_unavailable » · « limit_exceeded » · « balance_insufficient » · « payment_closed » · « attempt_in_progress » · « phone_invalid » · « operator_required » · « operator_unavailable » · « country_unavailable » · « exchange_rate_unavailable » · « amount_out_of_range » · « idempotency_key_required » · « payment_declined » · « direct_not_enabled » · « too_many_attempts » · « payment_disputed » · « refund_amount_too_high » · « refund_reason_invalid » · « payment_not_refundable » · « recipient_not_ready » · « payout_unavailable » · « conflict » · « not_permitted » · « setting_missing » · « service_unavailable » · « internal_error » | oui | Le cas précis, stable, fait pour être testé. La liste : la page « Les erreurs ». |
| `message` | texte | oui | Une phrase déjà écrite pour être montrée, en français ou en anglais selon Accept-Language. |
| `param` | texte | — | Le champ fautif, quand il y en a un. |
| `details` | objet | — | Ce qui aide à corriger, selon le code : par exemple « remaining » pour un remboursement trop gros. |
| `request_id` | texte | — | L’identifiant de l’appel, aussi dans l’en-tête Request-Id : à citer quand vous nous écrivez. |

Un code ne se retire jamais sans passer par les [Changements](https://docs.payiz.app/changements). Citez `request_id` au support : il retrouve l’appel dans vos journaux.

## Les codes

| Statut | code | Quand | Que faire |
| --- | --- | --- | --- |
| `400` | `parameter_missing` | Un champ requis manque ; « param » le nomme. | Ajoutez le champ. |
| `400` | `parameter_invalid` | Un champ ne se lit pas ou sort de ses bornes ; « param » le nomme quand il y en a un. | Corrigez la valeur d’après le message. |
| `400` | `currency_unavailable` | La monnaie n’est pas ouverte, ou pas à cet espace. | Choisissez une monnaie de votre espace. |
| `400` | `limit_exceeded` | Le montant dépasse un plafond du palier, en réel. | Un montant plus petit, ou un palier plus haut. |
| `400` | `balance_insufficient` | Le solde ne couvre pas le remboursement ou le retrait, frais compris. | Attendez des encaissements, ou demandez moins. |
| `400` | `phone_invalid` | Le numéro du payeur ne se lit pas : trop court, trop long, ou d’un format inconnu. | Envoyez-le au format international (+229…), ou précisez « country ». |
| `400` | `operator_required` | Le numéro peut appartenir à plusieurs opérateurs de son pays. | Demandez au payeur son opérateur, et passez « operator » avec un des codes de « details.operators ». |
| `400` | `operator_unavailable` | Aucun opérateur ouvert ne correspond à ce numéro, ou l’opérateur ne prend pas ce montant pour le moment. | Proposez un autre numéro ; GET /v1/payment_methods dit ce qui est ouvert. |
| `400` | `country_unavailable` | Le pays du numéro, ou sa monnaie, n’est pas ouvert. | Proposez un numéro d’un pays ouvert : GET /v1/payment_methods les liste. |
| `400` | `exchange_rate_unavailable` | Le payeur paie dans une autre monnaie que le paiement, et le taux du jour manque. | Réessayez un peu plus tard, ou proposez un numéro du pays de votre monnaie. |
| `400` | `amount_out_of_range` | Le montant sort des bornes de l’opérateur, dans la monnaie du payeur. | Lisez « details.min_amount » et « details.max_amount », dans « details.currency ». |
| `400` | `idempotency_key_required` | Un appel qui envoie une demande sur un téléphone, sans en-tête Idempotency-Key. | Tirez la clé de votre commande (cmd-42) : un second clic donnera la même. |
| `400` | `refund_amount_too_high` | Le montant dépasse ce qui reste à rendre sur le paiement. | Demandez au plus « details.remaining ». |
| `400` | `refund_reason_invalid` | Le motif n’est pas un de ceux que Payiz accepte. | Prenez-le dans GET /v1/refund_reasons. |
| `401` | `missing_api_key` | Aucune clé dans l’en-tête Authorization. | Envoyez « Authorization: Bearer sk_… ». |
| `401` | `invalid_api_key` | Clé inconnue, révoquée, expirée, d’un autre mode, ou appel depuis une adresse non autorisée. | Vérifiez la clé et ses adresses autorisées dans votre espace. |
| `402` | `payment_declined` | Payiz refuse cette demande. La raison n’est pas donnée. | Ne relancez pas : proposez un autre moyen au payeur. |
| `403` | `insufficient_permissions` | La clé n’a pas le geste que l’appel demande. | Donnez ce geste à la clé, ou servez-vous d’une autre. |
| `403` | `merchant_inactive` | L’espace est suspendu ou fermé. | Écrivez au support depuis votre espace. |
| `403` | `live_mode_not_enabled` | Une clé réelle, alors que le palier de l’espace n’ouvre pas le réel. | Terminez la vérification du compte dans votre espace. |
| `403` | `direct_not_enabled` | Une demande lancée par l’API en réel, alors que le paiement direct n’est pas ouvert à l’espace. | Envoyez « payment_url » au payeur, ou demandez l’ouverture du direct. |
| `403` | `not_permitted` | Le geste est refusé pour cet espace. | Lisez le message ; écrivez au support si besoin. |
| `404` | `resource_missing` | L’objet n’existe pas, ou pas pour cette clé et ce mode. | Vérifiez l’identifiant et le mode de la clé. |
| `409` | `idempotency_key_reuse` | La même clé d’idempotence revient avec un autre corps, ou pendant que la première demande s’exécute. | Une clé par demande ; rejouez la même demande à l’identique. |
| `409` | `payment_closed` | Le paiement est payé, annulé ou expiré. | Lisez son état dans « details.status ». |
| `409` | `attempt_in_progress` | Une demande attend déjà la validation du payeur. | Attendez son issue, par webhook. |
| `409` | `payment_disputed` | Le paiement est contesté : la somme est déjà retenue. | Attendez l’issue du litige. |
| `409` | `payment_not_refundable` | Le paiement ne peut plus être remboursé (déjà rendu, ou en cours de remboursement). | Lisez le paiement et ses remboursements. |
| `409` | `recipient_not_ready` | Par sécurité, un bénéficiaire neuf ne reçoit qu’après un délai. | Réessayez après « details.usable_at ». |
| `409` | `conflict` | L’objet n’est pas dans un état qui permet ce geste. | Relisez l’objet, puis décidez. |
| `409` | `setting_missing` | Un réglage de la plateforme manque : c’est à Payiz de le poser. | Écrivez au support avec le « request_id ». |
| `413` | `payload_too_large` | Le corps dépasse la taille permise. | Allégez la demande. |
| `429` | `rate_limited` | Trop d’appels pour cette clé dans la minute. | Attendez le délai de l’en-tête Retry-After, puis reprenez. |
| `429` | `too_many_attempts` | Trop de demandes vers ce numéro en peu de temps. | Attendez avant de relancer : on ne harcèle pas un téléphone. |
| `500` | `internal_error` | Une erreur chez Payiz. | Réessayez avec la même clé d’idempotence ; citez le « request_id » au support. |
| `503` | `payout_unavailable` | Aucune voie ne peut envoyer ce retrait pour le moment. | Réessayez plus tard. |
| `503` | `service_unavailable` | Un service dont l’appel dépend ne répond pas. | Réessayez avec la même clé d’idempotence. |

Un paiement qui échoue chez le payeur n’est pas une erreur d’appel : la demande le dit, avec son `failure_code`. Voir [Le paiement direct](https://docs.payiz.app/paiement-direct).

## Où regarder

Chaque appel, sa réponse et sa durée s’inscrivent dans Développeurs › Journaux, gardés 30 jours : c’est là qu’on comprend une intégration qui coince.

