Skip to navigation

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 per key and principal (the API key, or the signed-in user for JWT sessions).
  • Same request requirement: A retry must match the original request: method, path, query, and body. Reusing an existing key with a different request returns 422 idempotency_key_conflict.
  • In progress: If a request with the same key is still executing, the retry returns 409 idempotency_in_progress — wait briefly and retry.
  • Key length: Keys longer than 255 characters return 400 invalid_idempotency_key.
  • Replay headers: A replayed response is returned byte-for-byte with Idempotent-Replayed: true and X-Original-Request-Id (the initial call’s request ID for log correlation).
  • Methods: Every POST endpoint honors the header. Browser CORS preflights (OPTIONS) allow Idempotency-Key. GET, PATCH, and DELETE ignore the header (those methods are naturally idempotent under HTTP semantics).

Response caching and retries

A response is cached once the action itself starts executing, whether the result is a 2xx success, a domain refusal, or a 5xx server error (since an action that failed mid-flight may have already committed partial work).

Requests rejected before the action executes — such as authentication failures, rate limits, or model validation errors — are not stored. Once the underlying issue is resolved, the same key can be resent and will execute normally.

After any error, retry with the same key:

  • If the retry returns Idempotent-Replayed: true, the original request ran and this is its cached outcome. Fix the cause and retry with a new key.
  • If Idempotent-Replayed is absent, the retry executed afresh.

Status codes follow the IETF Idempotency-Key HTTP header specification.