RÉFÉRENCE API

Erreurs

L'API HTTP renvoie des codes HTTP standards. Toute erreur porte un corps JSON de la même forme, produite par le framework NestJS. Le relais SMTP a sa propre table de codes à trois chiffres, documentée séparément.

Forme du corps d’erreur

statusCode reprend le code HTTP et error son libellé standard. message est toujours une chaîne : la phrase à afficher telle quelle. errors est toujours un tableau de chaînes non vide : le détail, un élément par champ invalide quand plusieurs champs sont en cause. Les deux sont liés par un invariant : message === errors.join(' ') - message n’est que la concaténation de errors, séparée par un espace.

400 Bad Request - un champ invalide
{
  "message": "L'adresse du destinataire est invalide : indiquez une seule adresse comme « client@exemple.gn ».",
  "error": "Bad Request",
  "statusCode": 400,
  "errors": [
    "L'adresse du destinataire est invalide : indiquez une seule adresse comme « client@exemple.gn »."
  ]
}
400 Bad Request - plusieurs champs invalides
{
  "message": "L'adresse d'expédition est invalide : utilisez « adresse@domaine » ou « Nom <adresse@domaine> ». L'adresse du destinataire est invalide : indiquez une seule adresse comme « client@exemple.gn ». Le sujet est obligatoire et ne doit pas dépasser 300 caractères.",
  "error": "Bad Request",
  "statusCode": 400,
  "errors": [
    "L'adresse d'expédition est invalide : utilisez « adresse@domaine » ou « Nom <adresse@domaine> ».",
    "L'adresse du destinataire est invalide : indiquez une seule adresse comme « client@exemple.gn ».",
    "Le sujet est obligatoire et ne doit pas dépasser 300 caractères."
  ]
}

Migration : message n'est plus jamais un tableau

Avant ce correctif, message changeait de forme selon le chemin d’échec : une chaîne pour une erreur levée à la main, un tableau pour les erreurs de validation agrégées par NestJS. Un code qui lisait message[0] pour afficher la phrase recevait donc, sans erreur visible, soit la phrase entière soit sa première lettre - selon le chemin emprunté. Remplacez message[0] par errors[0] : message est désormais toujours la chaîne complète à afficher, et errors porte le détail champ par champ.

Codes HTTP

400Bad RequestRequête mal formée
  • -Paramètre manquant, mal typé ou hors bornes (POST /v1/emails : from, to, subject, html/text).
  • -Nom de domaine syntaxiquement invalide sur POST /v1/domains.
  • -Nom de clé API vide ou trop long sur POST /v1/api-keys.
  • -Domaine sélectionné inexistant, non vérifié ou en double sur POST /v1/api-keys ou POST /v1/smtp-credentials (portée par domaine). Plus de 50 domaines : « Une portée ne peut pas contenir plus de 50 domaines ».
  • -Pack de recharge inconnu, méthode de paiement invalide, ou numéro de téléphone mal formé sur POST /v1/billing/topup-requests.
  • -Paramètre de pagination (page, limit) non numérique ou hors bornes.
  • -Champ non déclaré dans le corps JSON, par exemple une faute de frappe dans un nom de paramètre : désormais refusé par un 400 (« Le champ « X » n'est pas accepté. »), là où il était auparavant ignoré silencieusement.
401UnauthorizedAuthentification manquante ou invalide
  • -En-tête Authorization absent, mal formé, ou clé API inconnue/révoquée (routes API key).
  • -Session absente ou expirée (routes tableau de bord, authentifiées par cookie).
402Payment RequiredSolde de crédits insuffisant
  • -POST /v1/emails alors que le solde de crédits du compte est inférieur à 1.
403ForbiddenAction refusée malgré une authentification valide
  • -Domaine d'envoi non vérifié sur POST /v1/emails.
  • -Clé API limitée à certains domaines, utilisée avec une adresse from d'un autre domaine sur POST /v1/emails (code CREDENTIAL_DOMAIN_OUT_OF_SCOPE).
  • -Envoi depuis l'adresse de test (mode bac à sable) vers un destinataire autre que l'adresse email de votre compte sur POST /v1/emails.
  • -Clé API créée sans la permission de lecture, utilisée sur GET /v1/emails, GET /v1/emails/:publicId, GET /v1/domains ou GET /v1/domains/:id : « Cette clé API n'a pas l'autorisation de lecture : créez une clé avec la lecture autorisée. »
  • -Compte suspendu (réputation d'envoi dégradée) - sur toute route authentifiée, API key ou session.
404Not FoundRessource introuvable
  • -Domaine, clé API ou email référencé par :id qui n'existe pas - ou qui appartient à un autre compte : Zendou renvoie volontairement la même 404 dans les deux cas, pour ne jamais révéler l'existence de la ressource d'un tiers.
409ConflictLa requête entre en conflit avec l'état actuel des données
  • -Nom de domaine déjà enregistré (par vous ou par un autre compte) sur POST /v1/domains.
  • -Une demande de recharge est déjà en attente avec la même référence de transaction sur POST /v1/billing/topup-requests.
429Too Many RequestsLimite journalière atteinte, rafale d'envoi trop rapide, ou trop d'échecs d'authentification
  • -Quota journalier : POST /v1/emails alors que le nombre d'emails envoyés depuis minuit UTC a atteint la limite journalière du compte (200 par défaut, relevée automatiquement avec l'ancienneté et le volume - voir Facturation). Message : « Limite journalière atteinte : réessayez demain ou demandez une augmentation de quota. »
  • -Rafale : plus de 60 requêtes par minute sur POST /v1/emails, indépendamment du quota journalier et du solde de crédits. Message : « Trop de requêtes. Réessayez dans quelques instants. »
  • -Trop d'échecs d'authentification depuis une même adresse IP sur POST /v1/emails (clé API inconnue ou révoquée présentée à répétition). Utile à savoir en intégration : tester avec une mauvaise clé peut donc, après plusieurs essais, renvoyer un 429 plutôt qu'un 401. Une clé valide n'est jamais concernée, même si l'IP est bloquée.

En-têtes de limitation de débit

Toute réponse d’une route soumise à une limite porte trois en-têtes : X-RateLimit-Limit (budget de la fenêtre), X-RateLimit-Remaining (requêtes encore acceptées dans cette fenêtre) et X-RateLimit-Reset (secondes avant remise à zéro). Un 429 porte en plus Retry-After (secondes avant de pouvoir réessayer), désormais aussi bien sur le 429 de rafale que sur celui de quota journalier atteint. Quand plusieurs compteurs s’appliquent à une même requête, ce sont les valeurs du compteur le plus contraignant, celui qui bloquera le client en premier, qui sont publiées.

Code d’erreur machine (POST /v1/emails)

Toute réponse d’erreur 4xx de POST /v1/emails porte, en plus de message et errors, un champ code : une chaîne stable et lisible par machine, sur laquelle brancher la logique d’un client plutôt que sur le texte français de message, susceptible d’être reformulé.

403 Forbidden - domaine d'envoi non vérifié
{
  "message": "Le domaine d'envoi n'est pas vérifié : ajoutez-le à votre compte et validez ses enregistrements DNS avant d'envoyer.",
  "error": "Forbidden",
  "statusCode": 403,
  "code": "DOMAIN_NOT_VERIFIED",
  "errors": [
    "Le domaine d'envoi n'est pas vérifié : ajoutez-le à votre compte et validez ses enregistrements DNS avant d'envoyer."
  ]
}
CodeStatut HTTPSignification
ACCOUNT_SUSPENDED403Compte suspendu (réputation d'envoi dégradée).
EMAIL_NOT_VERIFIED403Adresse email du compte non confirmée.
INVALID_FROM400Adresse d'expédition (from) illisible, ou nom d'affichage de plus de 128 caractères.
INVALID_TO400Adresse destinataire (to) illisible, ou nom d'affichage de plus de 128 caractères.
INVALID_REPLY_TO400Adresse de réponse (replyTo) illisible, ou nom d'affichage de plus de 128 caractères.
MISSING_BODY400Ni html ni text fourni.
BODY_TOO_LARGE400Un corps (html ou text) dépasse 500 Ko.
DOMAIN_NOT_VERIFIED403Domaine de l'adresse d'expédition non vérifié ou non rattaché au compte.
CREDENTIAL_DOMAIN_OUT_OF_SCOPE403La clé API n'est pas autorisée pour le domaine de l'adresse d'expédition (portée limitée à certains domaines). L'adresse de test du bac à sable reste permise.
CREDENTIAL_DOMAIN_OUT_OF_SCOPE403Variante : « Cette clé API n'est autorisée pour aucun domaine : créez-en une nouvelle avec la portée voulue. » (clé dont tous les domaines ont disparu).
REPLY_TO_NOT_VERIFIED403Domaine de l'adresse de réponse (replyTo) non vérifié ou non rattaché au compte. Nouveau.
TEST_SENDER_UNAVAILABLE503Mode bac à sable momentanément indisponible (configuration serveur).
TEST_SENDER_RECIPIENT_RESTRICTED403Depuis l'adresse de test, destinataire autre que l'adresse du compte.
INSUFFICIENT_CREDITS402Solde de crédits insuffisant.
DAILY_LIMIT_REACHED429Quota journalier atteint.

INVALID_REPLY_TO et REPLY_TO_NOT_VERIFIED sont nouveaux, liés à la prise en charge du champ replyTo, voir Envoyer un email.

Pour le détail des messages exacts renvoyés par POST /v1/emails, voir le tableau d’erreurs de la page « Envoyer un email ».

Ces codes ne couvrent que l’API HTTP. Le relais SMTP répond en codes à trois chiffres (RFC 5321), pas en HTTP - sa propre table de correspondance est documentée sur cette page.