# Codes d'erreur

> Tous les codes d'erreur de l'API, leur signification, et s'il est utile de réessayer.

Toute erreur a la forme `{ "error": { "code", "message", "details?", "requestId" } }`. **Basez votre code sur `code`**, jamais sur le texte de `message`. Conservez le `requestId` : c'est ce qui permet de retrouver l'appel.

## Table des codes

| Code | HTTP | Signification | Réessayer ? |
|---|---|---|---|
| `validation_error` | 400 | Un champ est invalide ou manquant ; `details` le liste. | Non, corrigez d'abord. |
| `unauthorized` | 401 | Clé ou session absente, invalide, expirée ou révoquée. | Non, vérifiez le jeton. |
| `quota_exceeded` | 402 | Le quota mensuel de votre plan est atteint (production). | Non, changez de plan ou attendez le mois suivant. |
| `plan_limit_reached` | 402 | Le plan n'autorise pas plus d'entreprises connectées, ni plus de numéros pour cette entreprise. | Non : changez de plan. |
| `forbidden` | 403 | La clé n'a pas la portée requise ; le message la nomme. | Non, créez une clé avec la bonne portée. |
| `sandbox_only` | 403 | Action réservée aux clés de bac à sable. | Non. |
| `not_found` | 404 | La ressource n'existe pas, **ou appartient à un autre compte ou environnement**. | Non. |
| `conflict` | 409 | Doublon (nom, référence externe, compte WhatsApp déjà connecté…) ou action impossible dans l'état actuel (livraison en cours, destination désactivée). | Selon le cas. |
| `invalid_message` | 422 | Message invalide : numéro mal formé, texte vide, média ambigu, lien non `https`… | Non. |
| `outside_service_window` | 422 | Message libre hors de la fenêtre de 24 heures. | Non : envoyez un template, ou attendez que le client écrive. |
| `template_not_approved` | 422 | Le template n'est pas approuvé par Meta. | Non, attendez l'approbation. |
| `idempotency_conflict` | 422 | Même `Idempotency-Key` pour une requête différente. | Non : utilisez une autre clé. |
| `meta_error` | 502 | Meta a refusé la requête ou ne répond pas ; le message reprend sa réponse. | **Oui** si c'est une panne, non si c'est un refus. |
| `internal_error` | 500 | Erreur de notre côté. | **Oui**, puis contactez le support avec le `requestId`. |

## Exemples réels

### 400 — `validation_error`

<ErrorExample name="validation_error" />

`details` indique le champ et la règle enfreinte.

### 401 — `unauthorized`

<ErrorExample name="unauthorized" />

### 403 — `forbidden`

<ErrorExample name="forbidden" />

### 404 — `not_found`

<ErrorExample name="not_found" />

<Callout type="note" title="Un autre compte répond aussi 404">
Pour ne pas révéler l'existence de ressources qui ne sont pas les vôtres, un identifiant valide mais appartenant à un autre compte (ou à l'autre environnement) répond `404`, exactement comme un identifiant inexistant.
</Callout>

### 409 — `conflict`

<ErrorExample name="conflict" />

### 422 — `outside_service_window`

<ErrorExample name="outside_service_window" />

### 422 — `invalid_message`

<ErrorExample name="invalid_message" />

### 422 — `idempotency_conflict`

<ErrorExample name="idempotency_conflict" />

## Erreurs de livraison d'un message

Une fois le message **accepté** (`202`), les échecs ne se voient plus dans une réponse HTTP : le message passe en `failed`, avec un `error` qui reprend le code et le message de Meta, et un événement `message.failed` est envoyé à vos webhooks.

| Situation | Reprise automatique ? |
|---|---|
| Panne de Meta, délai dépassé, limite de débit | Oui, jusqu'à 6 tentatives avec attente croissante |
| Refus définitif (template introuvable, destinataire non joignable, contenu refusé) | Non : `failed` immédiatement |

Les codes numériques qui apparaissent dans `error.code` sont ceux de **Meta** : consultez sa documentation pour leur sens exact.
