# Idempotency (/api-reference/idempotency)



A network blip between your client and Zumik leaves you unsure whether a mutation landed. Retrying blindly risks a double charge, a duplicate session event, or two artifacts. An idempotency key makes the retry safe: the same key replays the original response instead of running the mutation again.

## How it works [#how-it-works]

Attach a key to any `POST` request:

```bash
-H "Agent-Idempotency-Key: a1b2c3-your-unique-token"
```

Zumik scopes the key to your project and the request path. The first time it sees a given `(project, path, key)` triple, it runs the handler and caches the successful response. A later request with the same triple returns that cached response verbatim, flagged as a replay, without re-executing.

The key is yours to choose. Use a UUID, or a deterministic value derived from the operation (for example a hash of the work unit) so a retry naturally reuses it.

<Note>
  Idempotency applies to `POST` only. `GET` and `DELETE` are naturally safe to repeat, so a key on them is ignored.
</Note>

## The replay header [#the-replay-header]

Every idempotency-eligible response carries `agent-idempotent-replay` so you can tell a fresh execution from a replay:

| Header value                     | Meaning                                                                           |
| -------------------------------- | --------------------------------------------------------------------------------- |
| `agent-idempotent-replay: false` | This request executed for the first time and its result was cached.               |
| `agent-idempotent-replay: true`  | This is a replay of a previously cached response. The mutation did not run again. |

A replay returns the original status code, content type, and body byte-for-byte.

## In-flight conflict [#in-flight-conflict]

If a second request carrying the same key arrives while the first is still executing, Zumik does not run it. Running it could double-charge. Instead it returns `409` with the `idempotency_conflict` type:

```json
{
  "error": {
    "type": "idempotency_conflict",
    "message": "A request with this Agent-Idempotency-Key is already in progress. Retry after it completes."
  }
}
```

Wait for the original to settle, then retry; you will get the cached result with `agent-idempotent-replay: true`.

## What gets cached, and for how long [#what-gets-cached-and-for-how-long]

* **Only successful responses are cached.** A `4xx` or `5xx` releases the key immediately, so a transient failure stays retryable and a corrected request can run.
* **Replays last 24 hours.** After that the key ages out and a request with it executes fresh.
* A reservation from a handler that crashed mid-flight is reclaimed after 60 seconds, so a key is never stuck.

The cache is keyed per project, so two tenants using the same key string never collide.

## Example [#example]

```bash
# First call: executes, caches, returns agent-idempotent-replay: false
curl https://api.zumik.ai/v2/artifacts \
  -H "Authorization: Bearer zk_live_..." \
  -H "Content-Type: application/json" \
  -H "Agent-Idempotency-Key: create-policy-2026-06-15" \
  -d '{"artifact_type":"policy","content":"Run the linter before every commit."}'

# Retry after a timeout: same key returns the identical artifact,
# now with agent-idempotent-replay: true
curl https://api.zumik.ai/v2/artifacts \
  -H "Authorization: Bearer zk_live_..." \
  -H "Content-Type: application/json" \
  -H "Agent-Idempotency-Key: create-policy-2026-06-15" \
  -d '{"artifact_type":"policy","content":"Run the linter before every commit."}'
```

See [idempotency and retries](/guides/idempotency-and-retries) for a complete client retry pattern, and [headers](/api-reference/headers) for the full header reference.
