Idempotency

Safe retries on POST requests
View as Markdown

Network errors are inevitable. To retry a POST without risking a duplicate record, send an Idempotency-Key header with a unique value (a UUID v4 is ideal):

$curl -X POST https://app.lucanto.eu/api/v1/workspaces/{workspace_id}/invoices \
> -H "Authorization: Bearer lct_pat_xxx" \
> -H "Idempotency-Key: 9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d" \
> -H "Content-Type: application/json" \
> -d '{ "invoice": { "...": "..." } }'

If the original request reached the server, the retry returns the cached response from the first call instead of creating a second invoice.

Semantics

  • Cache window: 24 hours, scoped per API key.
  • Replay headers: a replayed response carries Idempotent-Replayed: true and X-Original-Request-Id (the first call’s request id, for log correlation).
  • Body mismatch: reusing a key with a different request body returns 409 idempotency_key_conflict — generate a fresh key for a different request.
  • In progress: if a request with the same key is still running, the retry returns 409 idempotency_in_progress — wait briefly and retry.
  • Methods: only POST honors the header. GET, PATCH, and DELETE are already idempotent under HTTP semantics.

Server errors (5xx) are not cached — they may be transient, so a retry is allowed to execute for real.