# Idempotent requests
Source: https://docs.file.ai/api/idempotency

> Send an Idempotency-Key so a retried write is never applied twice.

Production

# Idempotent requests

Copy pageCopy page

Send an Idempotency-Key so a retried write is never applied twice.

Copy pageCopy page

A network timeout leaves you guessing whether a write landed. Send an `Idempotency-Key` header and retrying becomes safe: the first request with a key does the work and its response is stored for 24 hours; a retry with the same key and the same body gets that stored response back, marked `Idempotent-Replayed: true`, instead of doing the work again. The header is optional on most write endpoints — leave it out and requests behave exactly as before. A few newer endpoints [require it](#where-it-applies).

## 

[​

](#how-it-works)

How it works

1

Generate a key per operation

Any string of 1 to 255 characters; a UUIDv4 is recommended. Use the same key for every retry of one operation, and a new key for a new operation.

2

Send it as a header

Here `FILEAI_BASE_URL` is `https://api.orion.file.ai/prod/v1`, or your [regional host](/authentication#regions).

```
curl -X POST "$FILEAI_BASE_URL/directories" \
  -H "x-api-key: $FILEAI_API_KEY" \
  -H "Idempotency-Key: 0f1c8e03-978e-40d5-bc93-6894a57f9324" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Q3 Invoices" }'
```

3

Retry with the same key and body

On a timeout, `429` or `5xx`, resend the identical request. If the original had already finished, you get its response with `Idempotent-Replayed: true` and nothing is done twice.

### 

[​

](#what-counts-as-the-same-request)

What counts as the same request

Keys are scoped to your **workspace and API key**: the same key string sent with a different API key, or in a different workspace, is a different key. (Workspace-scoped keys are further scoped to the project in `x-project-id`.) Within that scope, fileAI fingerprints the request from its method, path, workspace and body. The body is compared in canonical form, so key order and whitespace don’t matter. The query string is **not** part of the fingerprint.

### 

[​

](#outcomes)

Outcomes

Situation

Result

First request with this key

Runs normally. The outcome is stored for 24 hours.

Same key and body, original finished

The stored response is replayed with `Idempotent-Replayed: true`.

Same key and body, original still running

`409 IDEMPOTENT_REQUEST_IN_PROGRESS`.

Same key, different body

`422 IDEMPOTENCY_KEY_REUSED`.

Key older than 24 hours

Treated as new; the request runs again.

Header missing

No idempotency handling — or `400` where the header is required.

Header empty or over 255 characters

`400 BAD_REQUEST`.

A request is only treated as “still running” for **120 seconds**. If the original takes longer than that, a retry with the same key is allowed to run again. For slow operations, wait before retrying, or check the resource first.

### 

[​

](#large-responses)

Large responses

Responses larger than about 100 KB aren’t stored. The request is still recorded as finished, so a retry does **not** run it again; instead it gets a short placeholder at the original status:

```
{
  "message": "The original response was too large to cache and was not re-executed. Query the resource to retrieve it."
}
```

Fetch the resource you created to read its full state.

### 

[​

](#which-errors-are-cached)

Which errors are cached

An error raised by the endpoint itself is stored and replayed like a success, so a retry under the same key returns the same error. This covers `404`, a `403` raised by the endpoint itself, and most `409` and `422` responses. These are **not** stored, so a retry under the same key runs again:

*   `401`, and `403` from the API key check (`READONLY_KEY`, an inactive key, or no access to the project): these are rejected before idempotency runs;
*   every `5xx`;
*   `408`, `425` and `429`;
*   errors whose `code` is `VALIDATION_FAILED`, `BAD_REQUEST`, `BATCH_PARTS_TOO_LARGE` or `AGENT_RUN_IN_PROGRESS`;
*   a success that reports a server-side failure for part of the request (an item in `results` with a `5xx` `error.status`, or a non-empty `failedDirectoryIds`).

Some `400` and `422` errors are raised internally without an explicit code. Their response still shows `BAD_REQUEST` or `VALIDATION_FAILED`, but they are stored, so retrying with the same key returns the same error for 24 hours. After fixing the request, send it with a **new** key.

Idempotency fails open: if its store is briefly unavailable, the request still runs, without idempotency. Keep your own bookkeeping for operations where a duplicate would be costly.

## 

[​

](#where-it-applies)

Where it applies

The header is honored on every `POST`, `PATCH`, `PUT` and `DELETE` request that isn’t `multipart/form-data`, with these exceptions.

### 

[​

](#required)

Required

Endpoint

Without the header

`POST /v1/agent/sessions`

`400`

`POST /v1/agent/sessions/{session_id}/ask`

`400`

`POST /v1/agent/sessions/{session_id}/stop`

`400`

### 

[​

](#accepted-but-ignored)

Accepted but ignored

These accept the header but never store or replay: every call runs fresh, with no `Idempotent-Replayed` header and no key-reuse `422`.

Endpoint

Why

`POST /v1/extract`, `/v1/split`, `/v1/classify`, `POST /v1/files/schema/import`

Any `multipart/form-data` request is skipped: the file bytes can’t be fingerprinted.

`POST /v1/query/execute`

A read: a later run can legitimately return different rows.

`POST /v1/files/preview`, `/v1/files/preview/batch`

Presigned URLs expire in an hour; a replay would return dead URLs.

`POST /v1/files/upload/multipart/batch`

The response is the only copy of the upload handles and is too large to store.

`POST /v1/files/upload/multipart/urls/refresh`

Replaying would return the very URLs it exists to replace.

Because extract, split and classify ignore the key, a retried submit creates a second job. Use `client_ref` to recognize duplicates on your side.

### 

[​

](#special-handling)

Special handling

*   `POST /v1/files/upload/batch` runs its own idempotency: a replay re-signs the presigned URLs instead of returning expired ones.
*   `POST /v1/files/upload/multipart/complete/batch` stores per-item server errors too, because completing an upload can’t be repeated. To retry a failed item, send it as a new request with a new key.

## 

[​

](#related)

Related

## Errors

`IDEMPOTENT_REQUEST_IN_PROGRESS`, `IDEMPOTENCY_KEY_REUSED` and when to retry.

## Async jobs

Submit work once and track it to completion.

## Agent sessions

The endpoints where the key is required.

## Uploads

Batch and multipart uploads and their retry rules.

[Previous](/api/pagination)[

Versioning

How the fileAI API is versioned today, how breaking changes are caught, and how a deprecation is signalled.

Next

](/api/versioning)

⌘I

## On this page

*   [How it works](#how-it-works)
    *   [What counts as the same request](#what-counts-as-the-same-request)
    *   [Outcomes](#outcomes)
    *   [Large responses](#large-responses)
    *   [Which errors are cached](#which-errors-are-cached)
*   [Where it applies](#where-it-applies)
    *   [Required](#required)
    *   [Accepted but ignored](#accepted-but-ignored)
    *   [Special handling](#special-handling)
*   [Related](#related)
