> ## Documentation Index
> Fetch the complete documentation index at: https://docs.impellabs.tech/llms.txt
> Use this file to discover all available pages before exploring further.

# Idempotency

> The Idempotency-Key contract, its 24-hour window, and the two header spellings.

Send `Idempotency-Key` on any operation that lists it, and a retry becomes safe.

```bash theme={null}
curl "$IMPEL_API/responses" \
  -H "Authorization: Bearer $IMPEL_KEY" \
  -H "Idempotency-Key: 018f2c9e-7a41-7c3e-9d2b-6f0a1b2c3d4e" \
  -H "Content-Type: application/json" \
  -d '{"agent": "agt_support", "input": "…"}'
```

Idempotency is **opt-in**. A caller who sends no key gets a fresh execution every
time.

Use a value you generate per logical operation — a UUID is fine. Reuse it across
retries of *that* operation and never across two different ones.

## Three rules

<AccordionGroup>
  <Accordion title="1. Authorization happens first">
    The key is looked up **after** the agent has been resolved and the tenant
    asserted, and the record is keyed on the identity that just passed those
    checks.

    The opposite ordering has a real failure mode: replaying a stored payload
    before any permission check runs means a forbidden caller receives the
    permitted caller's result. That bug existed once on this platform and was
    fixed; this ordering is why it cannot come back.
  </Accordion>

  <Accordion title="2. Same key, same request → replay">
    Byte for byte the stored response, **including its HTTP status**.

    A retry that produces a *different* answer is not idempotent even when both
    answers are correct.
  </Accordion>

  <Accordion title="3. Same key, different request → 409">
    ```json theme={null}
    { "error": { "code": "idempotency_conflict",
                 "message": "This Idempotency-Key was already used for a different request.",
                 "request_id": "req_…" } }
    ```

    **Never a replay.** Returning the earlier response for a request that does not
    match it is what turns a client-side key collision into a silently wrong
    answer.
  </Accordion>
</AccordionGroup>

## What counts as "the same request"

A stable hash of the agent, the input and the options — canonical JSON with
sorted keys and no whitespace. Two semantically identical bodies that differ only
in key order are **one** request, not two.

## A replay may answer 200 where the first call answered 201 or 202

The stored status is replayed as it was recorded, so this is expected:

```
POST /documents  Idempotency-Key: abc   → 201 Created
POST /documents  Idempotency-Key: abc   → 200 OK      (same body)
```

The second call created nothing, and saying otherwise would be a lie a client
acts on.

## Telling a replay from a fresh execution

<Warning>
  **The header is not spelled the same on every endpoint.** Read the operation
  you are calling rather than assuming one name across the surface.

  A client checking for the wrong header reads every replay as a fresh
  execution — which for a create is the one conclusion idempotency exists to
  prevent.
</Warning>

| Endpoints                                                                                                           | Signal                                                                  |
| ------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
| `POST /responses`<br />`POST /conversations`<br />`POST /conversations/{id}/messages`<br />`POST /agents/{id}/runs` | `Idempotency-Replayed: true`                                            |
| `POST /jobs`<br />`POST /extractions`<br />`POST /reports`<br />`POST /knowledge/ingestions`                        | `Idempotent-Replay: true`                                               |
| `POST /documents`                                                                                                   | **No header.** A **200** where a create answers **201** *is* the replay |

The safe client check is: look for either header, and treat a 200 on
`POST /documents` as a replay.

<Note>
  This is a known inconsistency, stated in the generated reference on each
  operation rather than smoothed over. The generated document reads the header
  out of each handler, so the name on an operation is the name that endpoint
  actually sets.
</Note>

## The window

A key stays replayable for **24 hours**. Long enough to cover a client's retry
budget and a queue's redelivery; short enough that the idempotency table is not a
second, longer-lived copy of your responses.

An unfinished claim may be taken over after **15 minutes**. A worker that died
mid-run would otherwise leave a record that 409s the retry forever — which is the
opposite of what an idempotency key is for.

## Keys are compartmented, never global

A key is scoped by **endpoint, environment and caller identity** — never by
workspace alone.

So two end users of one embedded application, both sending
`Idempotency-Key: 1`, cannot meet. Nor can the same key on
`POST /responses` and `POST /documents`.

The identity part is the end-user principal when a delegated token named one, and
otherwise the credential prefix. Nothing in the compartment would be a secret if
it appeared in a log line.

## Where to send it

The header is `Idempotency-Key` on every endpoint that supports it.

`POST /jobs` additionally accepts `idempotency_key` in the body, because a caller
using a generated client that cannot set headers is not a caller who should have
to give up idempotency.

`idempotency_key` is **refused as a runtime option** with a 400 telling you to
send the header instead.

## Longer-lived deduplication

For conversations there is a better tool than an idempotency key.
`external_ref` is your own id for a thread: resolving the same one twice returns
the same conversation rather than a second one beside it, and it has no 24-hour
window. See [Conversations](/v2/conversations#two-ways-not-to-create-a-duplicate).

## Which operations support it

Every `POST` that creates something:
`/responses`, `/conversations`, `/conversations/{id}/messages`,
`/agents/{id}/runs`, `/documents`, `/extractions`, `/reports`,
`/knowledge/ingestions`, `/jobs`.

Each operation in the generated reference lists `Idempotency-Key` explicitly when
it reads one.

<Note>
  Cancellation endpoints need no key. `POST /responses/{id}/cancel` and
  `POST /jobs/{id}/cancel` are idempotent by construction: doing either twice is
  indistinguishable from doing it once.
</Note>
