Aller au contenu

Gestion des erreurs

Cord utilise des codes HTTP conventionnels et une structure d’erreur prévisible pour indiquer la réussite ou l’échec.

Codes de statut HTTP

L’API de Cord renvoie toujours des codes de statut HTTP standard. En règle générale :

  • 2xx indique une réussite.
  • 4xx indique une erreur provenant des informations fournies (par ex. paramètres manquants, jeton non valide).
  • 5xx indique une erreur sur les serveurs de Cord (c’est très rare).

Récapitulatif des codes

Code Description
200 OK Tout a fonctionné comme prévu.
400 Bad Request La requête n’était pas acceptable. Il s’agit généralement de invalid_request (champs manquants) ou de invalid_json ; parfois aussi de invalid_cursor ou invalid_api_version.
401 Unauthorized Votre clé API est manquante, incorrecte ou a été révoquée.
402 Payment Required L’opération exige une offre supérieure (subscription_required), dépasse une limite de votre offre actuelle (plan_limit_reached, par ex. nombre de clients ou de produits) ou la clé dépasse le nombre de clés incluses dans votre offre (subscription_key_limit).
403 Forbidden Vos droits sont insuffisants (par ex. vous avez tenté d’écrire avec une clé en lecture seule, ou une clé publiable a tenté une opération hors de sa portée).
404 Not Found La ressource (devis, client, facture) n’existe pas.
409 Conflict Un changement d’état non valide pour la ressource (invalid_state) — par exemple, approuver un devis encore en brouillon, ou supprimer les données de test (/api/test-mode/reset) en dehors de l’environnement de test. Également lorsqu’une autre requête avec la même Idempotency-Key est encore en cours (idempotency_in_progress), ou lorsqu’une facture timbrée nécessite un avoir au lieu d’être annulée (credit_note_required).
413 Payload Too Large Le corps dépasse 1 Mo (payload_too_large) ; sur POST /api/v1/elements/ai-draft, le fichier dépasse 4 Mo, et sur POST /api/v1/setup/plans, le fichier dépasse 3 Mo.
415 Unsupported Media Type Le corps n’a pas été envoyé en application/json (unsupported_media_type).
422 Unprocessable Entity Vous avez réutilisé une Idempotency-Key avec une requête différente (idempotency_key_reused), ou l’IA n’a trouvé aucun produit dans le texte (no_items).
429 Too Many Requests Vous avez dépassé une limite de requêtes par minute (rate_limited, voir Limites de requêtes), le quota mensuel d’API de votre offre (api_quota_exceeded) ou son quota d’IA (ai_quota_exceeded). Respectez l’en-tête Retry-After.
502 Bad Gateway Un fournisseur externe a rejeté ou n’a pas terminé l’opération : le fournisseur fiscal lors de l’émission (provider_error) ou l’envoi d’un e-mail (send_failed).
503 Service Unavailable Un service dont dépend l’opération n’a pas répondu — par exemple, impossible d’obtenir un taux de change (fx_unavailable) ou le catalogue de taxes (tax_catalog_unavailable), ou de vérifier votre abonnement (subscription_verification_unavailable). Cord n’invente jamais un taux : s’il ne peut pas le justifier, l’opération échoue de manière fermée. Réessayez plus tard.

Structure de l’objet d’erreur

Lorsqu’une erreur 4xx ou 5xx se produit, Cord répond toujours avec un objet JSON plat (non imbriqué) contenant deux champs : error (un message lisible, à afficher ou à journaliser) et code (une chaîne stable pour votre logique de traitement). Certaines erreurs ajoutent des champs supplémentaires selon le cas (voir les exemples ci-dessous).

Exemple de réponse d’erreur :

{
  "error": "The company name is required",
  "code": "invalid_request"
}

Exemple avec des champs supplémentaires (limite d’offre, 402) :

{
  "error": "Your plan allows 50 clients. Free up space or upgrade your plan to continue.",
  "code": "plan_limit_reached",
  "resource": "clients",
  "limit": 50,
  "plan": "starter"
}

Ne vous fiez pas à la forme imbriquée error.code / error.message pour analyser la réponse : error est toujours la chaîne du message, et code est un champ frère au même niveau, pas une propriété de error.

Toute réponse de /api/v1 comporte l’en-tête Cord-Request-Id (req_…). En cas d’erreur, le corps le reprend sous la forme request_id et ajoute doc_url, le lien vers l’explication de ce code sur cette page. Indiquez le request_id lorsque vous écrivez au support : il nous permet de retrouver votre requête exacte.

{
  "error": "API key inválida o revocada.",
  "code": "invalid_key",
  "request_id": "req_3f9c2a1b7d4e6f8a0b1c2d3e",
  "doc_url": "https://docs.cordhq.app/docs/desarrolladores/esenciales/errores#invalid_key"
}

Codes d’erreur courants (code)

  • invalid_json : le corps de votre requête POST n’est pas un JSON valide, ou Content-Type: application/json n’a pas été envoyé.

  • invalid_request : une validation métier échoue (champ manquant, remise hors limites, devis sans client valide). Le champ error explique la raison exacte.

  • invalid_fiscal_data (400) : les informations fiscales de cliente.fiscal ne passent pas la validation du pays. La réponse contient issues, une liste de { field, code }. Voir Formulaire fiscal.

  • missing_key / invalid_key : l’en-tête Authorization: Bearer est manquant, ou la clé n’existe pas / a été révoquée.

  • token_expired : l’identifiant d’une connexion autorisée (Zapier, Make) a expiré. L’application le renouvelle d’elle-même avec son refresh token.

  • insufficient_scope : votre clé n’a pas la portée nécessaire (une clé en lecture seule a tenté d’écrire, ou une clé publiable a tenté une route hors de sa liste autorisée).

  • insufficient_permissions (403) : une clé restreinte (rk_) n’a pas le droit sur la ressource de cette route, ou a été utilisée avec MCP. Le champ error nomme la ressource.

  • ip_not_allowed (403) : la clé a des IP autorisées et la requête est arrivée depuis une autre. Le champ error indique l’IP qu’a vue Cord.

  • key_expired (401) : la clé a expiré, ou elle a été renouvelée et sa période de grâce est terminée. Utilisez la nouvelle clé ou générez-en une autre.

  • not_found : la ressource demandée n’existe pas dans votre organisation.

  • subscription_required : la fonctionnalité exige une offre supérieure. La réponse inclut plan (l’offre actuelle) et plan_required.

  • plan_limit_reached : vous avez atteint une limite stricte de votre offre (clients, produits ou devis actifs). La réponse inclut resource, limit et plan.

  • integration_limit_reached (403) : vos intégrations ont déjà créé les 100 abonnements de webhook autorisés par organisation. Supprimez ceux que vous n’utilisez plus.

  • rate_limited / api_quota_exceeded : limite de requêtes par minute ou quota mensuel de l’API. Réessayez en respectant Retry-After.

  • fx_unavailable / tax_catalog_unavailable : une donnée tierce (taux de change ou catalogue de taxes) n’a pas pu être obtenue avec certitude. L’opération n’a pas abouti — ne supposez pas qu’elle a eu lieu.

  • subscription_key_limit (402) : votre organisation a plus de clés actives que son offre n’en inclut (par exemple, après un passage à une offre inférieure) et cette clé se trouve hors du quota ; les plus anciennes continuent de fonctionner. Révoquez les clés que vous n’utilisez plus ou passez à une offre supérieure. Le quota compte les clés secrètes, restreintes et publiables actives, sans les connexions autorisées par OAuth : 2 avec l’offre Gratuite, 5 avec Starter, 20 avec Professionnel, 50 avec Scale et 200 avec Developer.

  • subscription_verification_unavailable (503) : Cord n’a pas pu vérifier votre abonnement ni enregistrer l’utilisation de l’appel. L’opération n’a pas été exécutée ; réessayez.

  • invalid_api_version (400) : l’en-tête Cord-Version demande une version qui n’existe pas. Le message liste les versions disponibles. Voir Versions de l’API.

  • invalid_cursor (400) : le cursor n’est pas valide. Utilisez tel quel le next_cursor de la réponse précédente.

  • payload_too_large (413) : le corps JSON dépasse 1 Mo, ou le fichier envoyé à POST /api/v1/elements/ai-draft dépasse 4 Mo.

  • unsupported_media_type (415) : le corps n’est pas envoyé en application/json (ni en multipart/form-data sur POST /api/v1/elements/ai-draft).

  • test_mode_only (403) : les simulateurs de /api/v1/test_helpers ne fonctionnent qu’avec une clé sk_test_ et sur l’environnement de test.

  • credit_note_required (409) : la facture déjà timbrée ne peut pas être annulée directement. Émettez un avoir avec action: "credit_note".

  • provider_error (502) : le fournisseur fiscal a rejeté ou n’a pas terminé l’émission. Le champ error en explique la raison.

  • send_failed (502) : l’e-mail de la facture n’a pas pu être envoyé. Relancez l’envoi.

  • ai_quota_exceeded (429) / no_items (422) / ai_unavailable (502 ou 503) / missing_text / text_too_long (400) : erreurs des routes avec IA (POST /api/v1/cotizaciones/ia et POST /api/v1/elements/ai-draft) : le quota d’IA de votre offre est épuisé, le texte ne contenait aucun produit, l’IA n’est pas disponible ou n’a pas pu traiter la demande, ou le texte est absent ou dépasse le maximum. Sur ai-draft, ces codes arrivent sous forme d’événement error dans le stream.

  • invalid_state : la ressource n’est pas dans un état qui permet l’opération, ou elle a changé pendant que nous la traitions. Rien n’a été appliqué.

  • invalid_url : l’URL d’un webhook n’est pas en https:// ou pointe vers un hôte interne.

  • invalid_idempotency_key / idempotency_key_reused / idempotency_in_progress / idempotency_unavailable : voir la section suivante.

  • secret_key_in_browser (403) : une clé secrète est arrivée depuis un navigateur. Elle est déjà exposée dans le code source de la page : révoquez-la dans l’onglet Clés API du Cord Workbench et n’appelez l’API que depuis votre serveur.

  • missing_origin / unauthorized_origin / invalid_origin (403) : une clé publiable est arrivée sans Origin, ou depuis un domaine qui ne figure pas parmi les domaines autorisés de votre organisation (Paramètres › Générateur de devis intégrable).

  • origin_allowlist_required (403) : une clé publiable live ne fonctionne qu’avec des domaines autorisés configurés dans Paramètres › Générateur de devis intégrable. Les clés pk_test_ fonctionnent depuis n’importe quelle origine.

Limites de requêtes

Cord applique ces limites par fenêtre d’une minute, sauf indication contraire. Lorsque vous en dépassez une, il répond 429 avec code: "rate_limited", en général avec l’en-tête Retry-After en secondes. Celles marquées comme strictes ne laissent passer aucune requête lorsque Cord ne peut pas les compter : sur /api/v1/cotizaciones/{id} et /api/v1/elements/ai-draft, elles répondent alors 503 avec error: "access_temporarily_unavailable".

Limite Portée S’applique à
120 par minute Par IP d’origine Toute requête à /api/v1 et au serveur MCP, avec n’importe quelle clé, avant la validation de la clé.
600 par minute Par clé secrète (sk_, rk_ ou connexion OAuth) Toute requête à /api/v1 et au serveur MCP.
120 par minute Par clé publiable (pk_) Les quatre routes qu’accepte une pk_.
120 par minute (stricte) Par organisation POST et DELETE sur /api/v1/cotizaciones/{id} et les outils MCP qui changent l’état d’un devis.
20 par minute Par clé POST /api/v1/webhooks.
20 par minute Par organisation POST /api/v1/cotizaciones/ia.
6 par minute par IP, 60 par minute et 500 par jour par organisation (strictes) IP et organisation POST /api/v1/elements/ai-draft.
6 par jour (stricte) Par organisation POST /api/v1/setup/plans et proponer_configuracion dans MCP.

Comme la limite par IP est inférieure à celle d’une clé secrète, un serveur qui envoie tout depuis une seule IP atteint d’abord 120 par minute. En dehors de ces limites, les clés live consomment le quota mensuel d’API de votre offre (api_quota_exceeded).

Nouvelles tentatives sûres avec Idempotency-Key

Si une requête qui crée ou modifie quelque chose (POST, PATCH, DELETE) est interrompue par le réseau ou un délai d’attente, vous ne savez pas si Cord l’a appliquée. Envoyez l’en-tête Idempotency-Key avec une valeur unique par opération (par exemple, un UUID que vous générez) et réessayez avec la même clé et le même corps :

curl -X POST "https://cordhq.app/api/v1/clientes" \
     -H "Authorization: Bearer sk_live_tU..." \
     -H "Content-Type: application/json" \
     -H "Idempotency-Key: 5d9c2b7e-6f1a-4c3d-9e8b-2a1f0c7d6e5b" \
     -d '{ "empresa": "ACME Corp" }'
  • Nouvelle tentative avec la même clé et le même corps : Cord renvoie la réponse d’origine sans réexécuter l’opération ni la décompter de votre quota. La réponse répétée comporte l’en-tête Idempotent-Replayed: true.
  • La même clé avec un autre corps, une autre méthode ou une autre route : 422 idempotency_key_reused. Utilisez une nouvelle clé pour chaque opération différente.
  • La première requête n’est pas encore terminée : 409 idempotency_in_progress. Attendez et réessayez.
  • Erreurs 5xx et 429 : elles ne sont pas conservées ; vous pouvez donc réessayer avec la même clé.
  • La clé est propre à chaque clé API, accepte de 1 à 255 caractères visibles (sinon invalid_idempotency_key) et expire au bout de 24 heures.
  • Si Cord ne peut pas réserver la clé, il répond 503 idempotency_unavailable sans exécuter l’opération.

L’en-tête est facultatif : sans lui, chaque requête s’exécute telle qu’elle arrive.