> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.lucanto.eu/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.lucanto.eu/_mcp/server.

# Idempotency

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):

```bash
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.