Facturino / Documentation / Gestion des erreurs

Gestion des erreurs

L'API Facturino utilise des codes HTTP standards et retourne systématiquement un objet error structuré. Chaque erreur contient un identifiant de requête (request_id) à fournir au support pour toute investigation.

Format de réponse

Toutes les erreurs partagent la même enveloppe — un seul objet error en racine, jamais d'autres champs frères. Inspiré du modèle Stripe pour faciliter l'intégration.

{
  "error": {
    "type": "invalid_request_error",
    "code": "missing_required_field",
    "message": "Field 'customerId' is required.",
    "param": "customerId",
    "doc_url": "https://facturino.com/docs/errors#missing_required_field",
    "hint": "Either provide a customerId or inline a 'buyer' object.",
    "request_id": "req_8a3b9f2e1d"
  }
}
ChampDescription
typeCatégorie de l'erreur (voir tableau ci-dessous)
codeCode machine-readable pour le branchement (ex. missing_required_field)
messageMessage en clair, lisible pour un humain (ne pas afficher tel quel aux utilisateurs finaux)
paramOptionnel — Nom du paramètre invalide quand applicable
doc_urlOptionnel — Lien vers la documentation détaillée pour ce code
hintOptionnel — Suggestion d'action concrète
request_idIdentifiant unique de la requête (préfixe req_) — à fournir au support

Codes HTTP

CodeSignificationAction
200 / 201SuccèsContinuer
202Accepté (job asynchrone)Suivre l'id du job via GET /v1/jobs/:id
204Succès, pas de contenuContinuer
400Requête invalide (validation, format)Corriger le payload, ne pas retenter
401Authentification manquante ou invalideVérifier la clé API
402Paiement requis (plan insuffisant)Mettre à niveau le plan
403Action interdite (scope, transition d'état)Vérifier permissions et statut
404Ressource introuvableVérifier l'identifiant
409Conflit (idempotency-key réutilisée, version)Voir error.code
422Donnée invalide après validation (SIRET inconnu, TVA invalide)Corriger le payload
429Trop de requêtesRetentez après le délai indiqué dans Retry-After
500Erreur serveurRetentez avec backoff exponentiel
503Service indisponible (PA injoignable, maintenance)Retentez après quelques secondes

Types d'erreur

Le champ error.type classifie l'erreur — utilisez-le comme premier discriminant dans votre code. Liste exhaustive :

TypeStatut HTTPDescription
invalid_request_error400Le payload est mal formé, un champ requis manque, une valeur est invalide.
validation_error422Schéma Zod refusé — voir error.param pour le champ fautif.
authentication_error401Clé API absente, invalide, révoquée ou expirée.
permission_error403La clé n'a pas le scope requis pour cette opération.
not_found_error404La ressource n'existe pas, ou n'est pas accessible avec cette clé (livemode mismatch).
conflict_error409Conflit (transition d'état impossible, ressource déjà finalisée).
rate_limit_error429Limite de requêtes dépassée — consulter Retry-After.
plan_limit_error402Plan insuffisant ou quota dépassé (factures/mois, taille storage…).
api_error500Erreur serveur — Facturino est notifié automatiquement.

Codes d'erreur fréquents

Le champ error.code est le discriminant fin sous le type. Le code est aussi le fragment du doc_url retourné dans la réponse (https://facturino.com/docs/errors#<code>).

CodeTypeCause
missing_required_fieldinvalid_request_errorUn champ obligatoire est absent (voir error.param).
invalid_field_valueinvalid_request_errorValeur invalide (date hors plage, code pays inconnu, livemode mismatch…).
invalid_content_typeinvalid_request_errorLe header Content-Type doit être application/json.
validation_errorvalidation_errorSchéma Zod refusé : SIRET invalide, format date, longueur dépassée, valeur hors enum.
invalid_api_keyauthentication_errorClé inconnue ou mal formée.
api_key_revokedauthentication_errorLa clé a été révoquée depuis le dashboard.
api_key_expiredauthentication_errorLa clé a dépassé sa date d'expiration optionnelle.
missing_api_keyauthentication_errorHeader Authorization absent ou mal formé.
captcha_requiredauthentication_errorTrop de tentatives — résoudre le captcha avant de retenter.
scope_insufficientpermission_errorLa clé n'a pas le scope nécessaire pour cette opération.
not_foundnot_found_errorLa ressource n'existe pas ou n'est pas dans votre environnement (fac_test_ vs fac_live_).
conflictconflict_errorRequête concurrente avec la même Idempotency-Key encore en cours de traitement.
invalid_status_transitioninvalid_request_errorTransition d'état impossible — 400 (cf. machine à états des factures/devis/avoirs ; ex: éditer une facture finalisée → émettre un avoir via POST /v1/credit-notes).
resource_deletedconflict_errorLa ressource a été supprimée (soft-delete) et n'est plus modifiable.
conflictconflict_errorMême Idempotency-Key réutilisée avec un payload différent (24 h).
rate_limit_exceededrate_limit_errorLimite par minute dépassée — voir Retry-After.
plan_limit_errorplan_limit_errorFonctionnalité réservée à un plan supérieur.
quota_exceededplan_limit_errorQuota mensuel du plan dépassé (factures, e-reporting, établissements…).
storage_quota_exceededplan_limit_errorQuota de stockage de fichiers (PDF, logos) dépassé pour ce plan.
payload_too_largeinvalid_request_errorCorps de requête au-delà de la limite (CSV, pièce jointe).
pa_unavailableinvalid_request_errorLa Plateforme Agréée est injoignable (timeout, 5xx en amont).
pa_not_configuredinvalid_request_errorAucune PA connectée pour cet établissement. Configurer depuis Paramètres → Facturation électronique.
siret_not_foundinvalid_request_errorSIRET introuvable dans l'annuaire SIRENE.
vat_validation_failedinvalid_request_errorLe numéro TVA intracom n'a pas pu être validé par VIES.
schematron_validation_failedinvalid_request_errorLe document viole une règle Schematron EN16931 ou CIUS-FR (BT-46, BR-55…).
invalid_payment_amountinvalid_request_errorMontant de paiement nul ou négatif.
payment_exceeds_amount_dueinvalid_request_errorLe paiement dépasse le restant dû — non autorisé.
sandbox_payment_unavailableinvalid_request_errorPaiement en ligne indisponible pour une facture de test — le portail public répond 409, POST /v1/invoices/:id/payment-link répond 422 (les clés Stripe plateforme sont 100 % live). Remède : tester le flux avec une clé fac_live_ sur une facture live.
deposit_not_enabledinvalid_request_errorLe dépôt PA automatique n'est pas activé pour cet établissement (Paramètres → Facturation électronique).
related_invoice_not_foundinvalid_request_errorLa facture liée à l'avoir est introuvable (remboursement).
credit_note_not_refundableinvalid_request_errorL'avoir n'est pas dans un état remboursable (non finalisé, ou déjà intégralement remboursé).
invalid_refund_amountinvalid_request_errorMontant de remboursement nul, négatif ou mal formé.
refund_exceeds_credit_noteinvalid_request_errorLe remboursement dépasse le montant restant de l'avoir.
subscription_pausedinvalid_request_errorL'abonnement de l'émetteur est suspendu — l'opération est bloquée jusqu'à régularisation.
not_implementedinvalid_request_errorEndpoint pas encore disponible (statut 501).
internal_errorapi_errorErreur serveur — Facturino est notifié automatiquement.

Stratégie de retry

Tous les codes 4xx sauf 409/429 sont permanents — corrigez le payload, ne retentez pas.

CasStratégie
429Lire le header Retry-After (secondes) et attendre exactement cette durée
500 / 502Backoff exponentiel : 1s, 2s, 4s, 8s — max 5 tentatives
503 (PA)Backoff long : 30s, 60s, 120s — max 3 tentatives sur 5 min
409 idempotencyNE PAS retenter — le payload diffère du précédent

Idempotence et retry. Toujours envoyer un header Idempotency-Key sur les requêtes POST. Sans cela, un retry après un timeout réseau peut créer un doublon. Voir Idempotence.

Exemples

Node.js :

try {
  const invoice = await facturino.invoices.create({ /* ... */ })
} catch (err) {
  if (err.type === 'invalid_request_error') {
    console.error(`Champ invalide: ${err.param} — ${err.message}`)
  } else if (err.type === 'rate_limit_error') {
    // Retentez après le délai indiqué dans Retry-After
    await sleep(err.retryAfterMs)
  } else {
    // Toujours logguer err.requestId pour le support
    logger.error({ requestId: err.requestId, code: err.code }, err.message)
  }
}

Python :

from facturino import Facturino, FacturinoError

try:
    invoice = client.invoices.create(...)
except FacturinoError as err:
    if err.type == "invalid_request_error":
        print(f"Champ invalide: {err.param} — {err.message}")
    elif err.type == "rate_limit_error":
        time.sleep(err.retry_after_seconds)
    else:
        logger.error({"request_id": err.request_id}, err.message)

Contacter le support

Pour toute erreur inexpliquée ou un comportement inattendu, contactez le support en fournissant :

  • Le request_id exact (préfixe req_) — il permet de retrouver les logs côté Facturino.
  • L'horodatage de la requête (à la seconde près).
  • Le payload envoyé (masquez les champs sensibles si nécessaire).

Contacter le support · best-effort (Gratuit, Essential), prioritaire (Pro).