Facturino / Documentation / Idempotence

Idempotence

L'API Facturino supporte l'idempotence sur toutes les requêtes POST. En cas d'erreur réseau, de timeout ou de retry automatique, rejouer la même requête avec la même clé garantit qu'une seule opération est effectivement réalisée — la deuxième tentative retourne le résultat de la première.

Quand utiliser l'idempotence

  • Toujours sur les opérations de création non-rejouables : POST /v1/invoices, POST /v1/customers, POST /v1/quotes
  • Toujours sur les actions à effet de bord : POST /v1/invoices/:id/send, POST /v1/invoices/:id/payments, POST /v1/invoices/:id/finalize.
  • Optionnel sur PATCH et DELETE — ces verbes sont nativement idempotents au niveau HTTP, mais l'idempotency-key reste utile pour tracer une intention métier précise.
  • Inutile sur les GET — ils sont déjà idempotents.

Ajoutez un header Idempotency-Key contenant une chaîne unique pour l'opération métier :

$ curl -X POST https://facturino.com/api/v1/invoices \
  -H "Authorization: Bearer fac_test_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-7842-create-invoice" \
  -d '{...}'

Génération de la clé

Une bonne clé d'idempotence est :

  • Stable — la même opération métier doit produire la même clé (ne pas générer aléatoirement à chaque retry).
  • Unique — deux opérations métier différentes doivent produire des clés différentes.
  • Lisible — préférez un préfixe sémantique pour faciliter le debug : invoice-create-{orderId} plutôt qu'un simple UUID.
  • Courte — maximum 255 caractères, format libre (lettres, chiffres, tirets, underscores).

Exemples par SDK

Node.js — les SDKs officiels exposent le paramètre idempotencyKey sur chaque méthode :

import { randomUUID } from 'crypto'

// Génération d'une clé stable et persistée côté client
const idempotencyKey = `invoice-create-${orderId}`

const invoice = await facturino.invoices.create(
  { customerId: 'cus_...', lines: [...] },
  { idempotencyKey },
)
// Un retry avec la MÊME clé retourne l'invoice déjà créée (HTTP 200)

Python :

import uuid

key = f"invoice-create-{order_id}"

invoice = client.invoices.create(
    customerId="cus_...",
    lines=[...],
    idempotency_key=key,
)

Go :

key := fmt.Sprintf("invoice-create-%s", orderID)

// La clé d'idempotence est un champ du struct de paramètres
invoice, err := client.Invoices.Create(&facturino.InvoiceParams{
  Customer:       "cus_...",
  Items:          []*facturino.ItemParams{ /* ... */ },
  IdempotencyKey: key,
})

Cycle de vie d'une clé

ÉvénementComportement
Première requêteLe serveur exécute l'opération et stocke {key, body_hash, response}
Retry avec même clé + même payloadLe serveur retourne la réponse mise en cache (succès ou l'erreur métier persistée) — l'opération n'est jamais rejouée
Retry avec même clé + payload différentErreur 409 Conflict avec code = "conflict"
Échec de validation (4xx avant toute exécution : schéma, champ en lecture seule)La clé est libérée — un retry corrigé avec la même clé peut s'exécuter
Erreur métier pendant l'exécution (4xx)La réponse d'erreur est persistée : un retry avec la même clé renvoie la même erreur, sans jamais ré-exécuter d'effet de bord
Échec serveur (5xx)La réservation reste en attente (self-heal) — un retry après la fenêtre retentera réellement l'opération
ExpirationLes clés sont conservées 24 heures à compter de la première utilisation, puis supprimées automatiquement

Portée de la clé

Les clés d'idempotence sont scopées à votre entreprise et à l'environnement (livemode). Vous pouvez donc réutiliser la même chaîne dans deux environnements différents (test et production), ou entre deux entreprises distinctes.

En revanche, à l'intérieur d'un même environnement, une clé est globale tous endpoints confondus — ne réutilisez pas order-7842 à la fois pour une création de facture et une création de client.

Erreurs courantes

Si vous réutilisez une clé avec un payload différent, l'API refuse la requête :

HTTP/1.1 409 Conflict

{
  "error": {
    "type": "conflict_error",
    "code": "conflict",
    "message": "Idempotency key already used with a different request body.",
    "hint": "Generate a new idempotency key for new operations.",
    "request_id": "req_..."
  }
}

Pour résoudre :

  • Générez une nouvelle clé pour la nouvelle opération.
  • Ou attendez 24 h (expiration automatique) si vous voulez réutiliser la même chaîne sémantique.

Bonnes pratiques

  • Persistez la clé en base de données avant le premier appel API. Si votre processus crashe avant la réponse, vous saurez quelle clé réutiliser au prochain démarrage.
  • Liez la clé à un objet métier (numéro de commande, ID de session) plutôt qu'à un UUID aléatoire.
  • Logguez la clé avec chaque requête pour faciliter le rapprochement en cas d'incident.
  • Ne déduisez rien de l'absence de la clé en cache — l'expiration 24 h peut intervenir avant un retry tardif.

Étapes suivantes