Erreurs
Format d'erreur normé, codes HTTP, gestion des retries idempotents.
L'API Inkr utilise une enveloppe d'erreur normée inspirée de Stripe. Tous les codes HTTP d'erreur (4xx, 5xx) retournent ce format :
{
"error": {
"type": "invalid_request_error",
"code": "invalid_request",
"message": "template_id must match the format tpl_<24 base62 chars>"
}
}Types d'erreurs
Le champ type ne prend que 4 valeurs.
| Type | HTTP | Quand |
|---|---|---|
authentication_error | 401 | Clé API manquante, invalide ou révoquée. |
invalid_request_error | 400 / 404 / 409 / 422 | Requête refusée : JSON invalide, schéma Zod refusé, ressource introuvable ou état incompatible. |
rate_limit_error | 429 | Quota par minute dépassé (header Retry-After). |
api_error | 500 | Erreur interne Inkr ou dépendance amont. |
Pour distinguer précisément un cas, branchez sur le statut HTTP puis sur error.code. Traitez type et code comme des énumérations ouvertes, avec un cas par défaut : une version future peut ajouter des codes sans changer les 4 types.
Codes d'erreur, requête invalide
| Code | HTTP | Sens |
|---|---|---|
missing_api_key | 401 | Header Authorization absent. |
invalid_api_key | 401 | Format invalide, clé inexistante ou révoquée. |
invalid_json | 400 | Le body n'est pas du JSON valide. |
invalid_id | 400 | L'identifiant dans l'URL ne respecte pas <préfixe>_<24 base62>. |
idempotency_key_required | 400 | Header Idempotency-Key absent ou mal formé. |
invalid_request | 422 | Le body ne correspond pas au schéma. message porte le détail de la première violation. |
idempotency_key_reused_with_different_body | 422 | Même clé d'idempotence avec un body différent. |
invalid_pagination | 422 | limit hors de [1, 100] ou curseur illisible. |
invalid_status | 422 | Valeur de filtre status inconnue. |
invalid_created_via | 422 | Filtre created_via différent de ui ou api. |
invalid_type | 422 | Filtre type inconnu sur /v1/events. |
invalid_template_id | 422 | Filtre template_id mal formé. |
invalid_submission_id | 422 | Filtre submission_id mal formé. |
invalid_email | 422 | Filtre email de plus de 320 caractères. |
invalid_pdf | 422 | Base64 illisible, taille dépassée ou octets magiques %PDF absents. |
invalid_pdf_url | 422 | L'URL fournie ne renvoie pas un PDF exploitable. |
invalid_docx | 422 | Le base64 DOCX est illisible. |
invalid_field_area | 422 | Une zone déborde de la page ou pointe une page inexistante. |
invalid_template_tag | 422 | Variable {{…}} mal formée ou de type non supporté. |
no_tags_found | 422 | Aucune variable {{…}} trouvée dans le HTML ou le DOCX. |
template_not_found | 422 | Le template_id du body n'existe pas dans votre scope. |
template_archived | 422 | Le template visé est archivé. |
submitter_role_not_in_template | 422 | Un role de submitters[] ne correspond à aucun rôle du document. |
phone_2fa_not_supported | 422 | require_phone_2fa: true demandé. Utilisez require_email_2fa. |
invalid_events | 422 | Liste d'events webhook vide ou contenant un type inconnu. |
webhook_url_rejected | 422 | URL webhook refusée par le garde-fou SSRF. |
webhook_endpoint_limit_reached | 422 | 25 endpoints webhook actifs déjà enregistrés. |
resource_not_found | 404 | Ressource inexistante ou hors scope. Le 404 couvre les deux cas volontairement. |
submission_not_completed | 404 | Audit log demandé sur une submission non completed. |
submission_already_finalized | 409 | Annulation d'une submission déjà terminée, refusée, expirée ou annulée. |
submitter_already_finalized | 409 | PATCH sur un submitter déjà signé, refusé ou pré-signé. |
slug_already_exists | 409 | Un template porte déjà ce slug. |
rate_limit_exceeded | 429 | Quota par minute dépassé. |
Codes d'erreur, côté serveur
| Code | HTTP | Sens |
|---|---|---|
storage_upload_failed | 500 | Échec d'envoi du PDF vers le stockage. |
template_create_failed | 500 | Échec de création du template interne. |
html_render_failed | 500 | Échec du rendu HTML vers PDF. |
docx_convert_failed | 500 | Échec de conversion DOCX vers HTML. |
db_insert_failed, db_update_failed, db_error | 500 | Erreur base de données. |
post_create_fetch_failed, post_update_fetch_failed | 500 | Objet écrit mais non relu. |
internal_inconsistency | 500 | Incohérence interne détectée. Retentez. |
token_issue_failed | 500 | Échec d'émission du token d'embed. |
handler_error, server_misconfigured | 500 | Exception non gérée ou configuration serveur incomplète. |
Codes HTTP
| Code | Sens |
|---|---|
200 | Succès. Les DELETE renvoient aussi 200 avec un petit objet de confirmation, pas 204. |
201 | Ressource créée (template, submission, endpoint webhook). |
400 | JSON invalide, identifiant d'URL mal formé ou Idempotency-Key manquante. |
401 | Problème d'authentification. |
404 | Ressource inexistante ou hors scope. |
409 | Conflit d'état : objet déjà finalisé, slug déjà pris. |
422 | Schéma refusé ou règle métier violée. C'est le cas le plus fréquent. |
429 | Rate limit dépassé (cf. header Retry-After). |
500 | Erreur Inkr ou upstream. Retry recommandé avec backoff. |
Idempotence
Les 5 endpoints de création de document exigent le header Idempotency-Key :
POST /v1/templatesPOST /v1/submissionsPOST /v1/submissions/from_pdfPOST /v1/submissions/from_htmlPOST /v1/submissions/from_docx
Les autres POST (/v1/webhook_endpoints, /v1/webhook_endpoints/{id}/test, /v1/submitters/{id}/embed_token) ne l'exigent pas.
Format accepté : ^[A-Za-z0-9_\-+.:=]{1,255}$. Une UUID v4 fraîche est recommandée.
-H "Idempotency-Key: $(uuidgen)"Comportement :
| Situation | Réponse |
|---|---|
| Premier appel avec cette clé | Traité normalement. Réponse mise en cache 24 h. |
| Même clé, même body | Réponse rejouée à l'identique avec le header Idempotent-Replayed: true. Rien n'est recréé ni refacturé. |
| Même clé, body différent | 422 idempotency_key_reused_with_different_body. Générez une nouvelle clé. |
| Clé absente ou mal formée | 400 idempotency_key_required. |
La clé est scopée par utilisateur et par route. Réutiliser la même clé sur POST /v1/submissions puis sur POST /v1/submissions/from_pdf ne déclenche donc pas de collision.
Bonnes pratiques retry
async function withRetry(fn, maxAttempts = 3) {
let lastError
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
return await fn()
} catch (err) {
lastError = err
// Ne retry que sur erreurs réseau, 429 ou 5xx.
if (err.status && err.status < 500 && err.status !== 429) throw err
// Backoff exponentiel + jitter
const delay = Math.min(2 ** attempt * 1000, 10000) + Math.random() * 500
await new Promise((r) => setTimeout(r, delay))
}
}
throw lastError
}Ne retry jamais sur 400, 401, 404, 409 ou 422 : le retry produira la même erreur. Retry uniquement sur 429 (avec respect de Retry-After) et sur 5xx, en réutilisant la même Idempotency-Key.