# Erreurs

> Un refus porte un `code` stable, un `message` en français et, quand c'est possible, une piste (`hint`) qui dit quoi faire. Le code ne change pas : c'est lui qu'un agent teste. La piste se suit, elle ne se contourne pas.

## La forme d'un refus

Par MCP, un refus est un résultat d'outil marqué `isError`, avec un texte lisible : le message, le code entre parenthèses, puis la piste après une flèche.

```text
Doublon : un message de agent@exemple.fr à nadia.morel@exemple.com est déjà parti (créé lundi 12 octobre à 10 h 14). Règle de l’espace : un seul premier message par destinataire et par boîte en 30 jours. (code duplicate_recipient)
→ Pour relancer, répondez dans le fil thr_b81f4c6a2d9e47035c1a8e6f0b4d2c97 avec reply_to_thread (une réponse dans un fil n’est pas un doublon).
```

En HTTP (`POST /v1/{opération}`), le même refus est un statut HTTP et un corps JSON :

```json
{
  "error": {
    "code": "duplicate_recipient",
    "message": "Doublon : un message de agent@exemple.fr à nadia.morel@exemple.com est déjà parti (créé lundi 12 octobre à 10 h 14). Règle de l’espace : un seul premier message par destinataire et par boîte en 30 jours.",
    "hint": "Pour relancer, répondez dans le fil thr_b81f4c6a2d9e47035c1a8e6f0b4d2c97 avec reply_to_thread (une réponse dans un fil n’est pas un doublon)."
  }
}
```

## Ce qui n'est pas une erreur

Un message retenu n'est pas refusé. La réponse est un succès, et elle dit ce qu'il attend.

| Situation | Ce que rend le serveur | Que faire |
|---|---|---|
| Quota du jour de la boîte atteint | Le message est en file (`queued`), avec le départ au plus tôt. | Rien. Ne le renvoyez pas. |
| Envoi hors des heures ouvrées | Le message est en file, avec le départ au plus tôt. | Rien. |
| Validation humaine demandée | Le message est un brouillon (`draft`). | Attendre un humain : `approve_message` demande la permission `approve`. |
| Même `idempotency_key`, même contenu | Le message déjà créé, avec `replayed: true`. | Rien : rien de nouveau n'a été créé. |

## Règles d'envoi

| Code | HTTP | Ce qui s'est passé | Que faire |
|---|---|---|---|
| `space_unverified` | 403 | L'espace n'est pas vérifié : ses boîtes reçoivent, mais n'envoient pas. | Demander à l'humain le code à 6 chiffres, puis appeler `verify`. S'il n'a rien reçu : `attach_human`. |
| `trial_limit` | 429 | La limite d'essai du domaine commun est atteinte. | Attendre le palier suivant, donné par la piste. Réécrire à un destinataire déjà compté reste possible. Pour sortir des limites : `add_domain`. |
| `recipient_suppressed` | 422 | Le destinataire est bloqué : désinscription, plainte, adresse morte ou blocage manuel. | Ne plus écrire à cette adresse, depuis aucune boîte. |
| `duplicate_recipient` | 409 | Cette boîte a déjà écrit un premier message à ce destinataire dans les 30 derniers jours. | Répondre dans le fil donné par la piste, avec `reply_to_thread`. |
| `key_daily_cap` | 429 | Le plafond de messages par jour de la clé est atteint. | Reprendre le lendemain, à partir de 0 h, heure de Paris. |
| `inbox_suspended` | 409 | Le coupe-circuit a suspendu la boîte. La raison est dans le message. | Vérifier la liste de destinataires. La reprise est manuelle, par un administrateur de Subagent. |
| `unresolved_placeholder` | 422 | Un champ entre doubles accolades est resté dans le sujet ou le texte. | Remplacer chaque champ par sa valeur. Seul `{{unsubscribe_url}}` est accepté. |
| `idempotency_conflict` | 409 | La même `idempotency_key` a déjà servi pour un contenu différent. | Même message : réutiliser la clé telle quelle. Nouveau message : en prendre une nouvelle. |
| `test_recipient_not_allowed` | 422 | Un envoi de test (`test: true`) vise une adresse hors de l'espace. | Envoyer le test à l'e-mail de l'humain de l'espace ou à une autre de vos boîtes. Pour écrire à ce destinataire : retirer `test`. |
| `queue_full` | 429 | La file de la boîte est pleine : trop de brouillons et de messages en attente (dix fois son quota maximal du jour). | Laisser partir les messages en attente, ou annuler ceux qui ne servent plus avec `cancel_message`. |

## Inscription et vérification

| Code | HTTP | Ce qui s'est passé | Que faire |
|---|---|---|---|
| `signups_closed` | 403 | Les inscriptions sont fermées sur ce serveur. | Demander une clé ou une invitation à `contact@subagent.fr`. |
| `invitation_required` | 403 | Bêta sur invitation : l'e-mail de l'humain n'est pas invité. | Demander une invitation pour cette adresse. En attendant, `sign_up` sans `human_email` ouvre une boîte qui reçoit. |
| `signup_rate_limited` | 429 | Trop d'inscriptions en 24 heures, depuis la même adresse ou sur tout le service, ou trop de codes envoyés au même e-mail. | Réessayer plus tard. Une clé déjà obtenue se réutilise. |
| `username_taken` | 409 | Ce nom de boîte est déjà pris. | Choisir un autre `username`. |
| `reserved_username` | 422 | Ce nom est réservé : adresse de service, ou nom réservé sur le domaine commun. | Choisir le nom de l'agent, d'une personne ou d'une fonction. |
| `human_email_not_allowed` | 422 | L'adresse donnée n'est pas celle d'une personne : boîte Subagent, ou domaine d'exemple. | Donner l'adresse personnelle ou professionnelle de l'humain. |
| `no_pending_code` | 409 | Aucun code n'attend pour cet espace. | Appeler `attach_human` avec l'e-mail de l'humain. |
| `invalid_code` | 422 | Le code est incorrect. Le message donne le nombre d'essais. | Redemander le code du dernier e-mail reçu : un code plus ancien ne vaut plus. |
| `code_expired` | 409 | Le code a plus de 24 heures. | Appeler `attach_human` avec le même e-mail. |
| `too_many_attempts` | 429 | Dix essais ont échoué : le code ne vaut plus. | Appeler `attach_human` pour recevoir un nouveau code. |
| `code_too_soon` | 429 | Un code vient d'être envoyé. | Attendre le nombre de secondes donné par la piste. |
| `code_limit` | 429 | Cinq codes ont déjà été envoyés pour cet espace. | Donner à `verify` un code encore valable. Sinon, se réinscrire sous un autre nom. |
| `code_not_sent` | 502 | L'e-mail du code n'a pas pu partir. | Rien n'a changé : rappeler `attach_human` dans un instant. |
| `human_change_limit` | 409 | L'e-mail de l'humain a déjà été remplacé deux fois. | Rappeler `attach_human` avec l'adresse déjà rattachée. |
| `already_verified` | 409 | L'espace est déjà vérifié. | Rien : l'envoi est ouvert. |
| `not_signed_up` | 409 | L'espace n'a pas été ouvert par `sign_up` : il n'a pas de code. | Rien à vérifier. |

## Boîtes

| Code | HTTP | Ce qui s'est passé | Que faire |
|---|---|---|---|
| `address_taken` | 409 | L'adresse est déjà une boîte. | Choisir un autre `username`. |
| `inbox_limit` | 409 | Le plafond de boîtes de l'espace est atteint. | Supprimer une boîte inutile avec `delete_inbox`. |
| `inbox_creation_limit` | 429 | Trop de boîtes créées en 7 jours pour l'espace. Les boîtes supprimées comptent. | Garder ses boîtes plutôt que les supprimer et les recréer. Pour un besoin réel, écrire à `contact@subagent.fr`. |
| `domain_not_allowed` | 422 | Le domaine demandé n'est pas un domaine d'envoi de l'espace. Ou, pour `add_domain`, c'est un domaine de Subagent. | Prendre un des domaines donnés par la piste, ou enregistrer un domaine dont vous gérez le DNS. |
| `no_sending_domain` | 409 | L'espace n'a aucun domaine d'envoi. | Prévenir un administrateur de Subagent. |
| `provisioning_failed` | 502 | Le serveur de messagerie n'a pas créé la boîte, ou n'a pas pris le domaine. | Réessayer dans un instant. Rien n'est perdu. |

## Domaines

| Code | HTTP | Ce qui s'est passé | Que faire |
|---|---|---|---|
| `domain_exists` | 409 | Le domaine est déjà enregistré pour cet espace. | Appeler `list_domains` : il donne ses enregistrements DNS et son état. |
| `domain_taken` | 409 | Le domaine est déjà enregistré par un autre espace. | Enregistrer un sous-domaine, ou demander à un administrateur de Subagent de le libérer. |
| `domain_limit` | 409 | Le plafond de cinq domaines par espace est atteint. | Retirer un domaine inutile avec `remove_domain`. |
| `domain_in_use` | 409 | Des boîtes de l'espace vivent encore sur le domaine à retirer. | Supprimer d'abord ces boîtes avec `delete_inbox`. |
| `domain_not_verified` | 409 | Le domaine de la boîte n'est pas encore vérifié. | Publier ses enregistrements DNS, puis appeler `verify_domain`. |
| `domain_suspended` | 409 | Un enregistrement DNS exigé a disparu : le domaine est suspendu, ses boîtes n'envoient plus. | Remettre les enregistrements en place (`list_domains` les donne), puis appeler `verify_domain`. |
| `dns_unavailable` | 503 | La résolution DNS a échoué : rien n'a pu être contrôlé. | Réessayer dans un instant. L'état du domaine n'a pas changé. |

## Brouillons et messages

| Code | HTTP | Ce qui s'est passé | Que faire |
|---|---|---|---|
| `not_draft` | 409 | Le message n'est pas, ou n'est plus, un brouillon. | Relire son statut avec `get_message`. |
| `draft_changed` | 409 | Le brouillon a été modifié depuis sa lecture : la `revision` donnée n'est plus la sienne. | Relire le brouillon avec `list_drafts`, puis valider la version lue. |
| `not_cancellable` | 409 | Le message ne s'annule plus : il est parti, ou déjà annulé. | Rien : un message parti ne se rappelle pas. |

## Liste de blocage

| Code | HTTP | Ce qui s'est passé | Que faire |
|---|---|---|---|
| `public_mail_domain` | 422 | Une messagerie publique (`gmail.com`…) ne se bloque pas en entier, et ne s'enregistre pas comme domaine. | Bloquer les adresses une à une. Enregistrer un domaine dont vous gérez le DNS. |
| `suppression_protected` | 409 | Une désinscription, une plainte ou une adresse morte ne se lèvent pas par l'outil. | Rien. Seule la personne peut demander, par écrit, la levée d'une désinscription. |

## Accès et requêtes

| Code | HTTP | Ce qui s'est passé | Que faire |
|---|---|---|---|
| `unauthorized` | 401 | La clé est absente, inconnue ou révoquée. | Envoyer l'en-tête `Authorization: Bearer sak_…`. Pas encore de clé : `sign_up`. |
| `origin_refused` | 403 | L'appel vient de la page web d'un autre site (en-tête `Origin`). | Appeler l'API depuis votre agent ou votre serveur, sans en-tête `Origin`. |
| `forbidden` | 403 | La clé ne porte pas la permission que l'outil demande. | Pour `approve` : la validation revient à un humain. Sinon, demander une clé qui porte la permission. |
| `not_found` | 404 | L'objet n'existe pas pour cette clé. | Vérifier l'identifiant : il vient de `list_inboxes`, `list_threads`, `get_thread` ou `list_drafts`. |
| `invalid_argument` | 400 | Un paramètre manque, est inconnu ou mal formé. | Lire le message : il nomme le paramètre. La piste liste les paramètres acceptés. |
| `unknown_operation` | 404 | L'opération demandée n'existe pas. | Lire la liste des outils (`tools/list`, `/llms.txt` ou `/openapi.json`). |
| `payload_too_large` | 413 | Le corps de la requête dépasse 1 Mio. | Raccourcir le message. |
| `rate_limited` | 429 | Trop de requêtes en une minute depuis la même adresse (60 sans clé, 600 avec une clé), ou plus de six `verify_domain` par minute pour un espace. | Attendre le délai donné par l'en-tête `Retry-After`, puis espacer les appels. |
| `internal` | 500 | Erreur interne du serveur. | Réessayer dans un instant, avec la même `idempotency_key`. |
| `encryption_unavailable` | 503 | Le serveur n'est pas entièrement configuré. | Prévenir un administrateur de Subagent. |

## Les codes portés par un message

Un message qui n'est pas parti garde la raison dans `status_code` et `status_detail`. `get_message` les rend, avec le journal.

| `status` | `status_code` | Sens |
|---|---|---|
| `rejected` | `recipient_suppressed` | Le destinataire a été bloqué entre la création et l'envoi. |
| `rejected` | `duplicate_recipient` | Un autre premier message vers ce destinataire est parti entre-temps. |
| `cancelled` | `cancelled_by_key` | Annulé par `cancel_message`. |
| `cancelled` | `recipient_unsubscribed` | Annulé parce que le destinataire s'est désinscrit. |
| `cancelled` | `recipient_suppressed` | Annulé parce que le destinataire a été bloqué avec `add_suppression`. |
| `cancelled` | `inbox_deleted` | Annulé parce que la boîte a été supprimée. |
| `failed` | `smtp_rejected` | Le serveur de messagerie a refusé le message. |
| `failed` | `policy_rejected` | Le serveur du destinataire a refusé le message pour une raison de politique. |
| `failed` | `interrupted` | L'envoi a été interrompu. Il n'est pas renvoyé automatiquement, pour ne pas risquer un doublon. |
