> ## Documentation Index
> Fetch the complete documentation index at: https://developer-staging.secfix.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Idempotency

> Retry write requests safely with an Idempotency-Key.

Every `POST` and `PATCH` request to `/v1` **requires** an `Idempotency-Key` header. This includes
creates and batch requests.

When a request succeeds with a `2xx`, the API stores its response under the key for at least 24
hours. A retry with the same key returns the stored response. The change isn't applied twice.

The API stores only `2xx` responses. After a `4xx`, a `5xx`, or a request that never finished, a retry
with the same key can run the request again.

> [!WARNING]
>
> A `/v1` `POST` or `PATCH` request without an `Idempotency-Key` returns `400 validation_failed`.
> `POST /oauth/token` doesn't take the header, so don't send it there.

## Send the key

Generate a unique value for each operation, such as a UUID, and send it as the `Idempotency-Key`
header. A key can be up to **256 characters**. A longer key returns `400 validation_failed`.

```bash theme={null}
curl -X PATCH "https://api.secfix.com/v1/inventory/assets/$ASSET_ID" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: 5f3c9a2e-8b4d-4e7a-9c1f-2a5b6c7d8e9f" \
  -H "Content-Type: application/json" \
  -d '{ "classification": "CONFIDENTIAL" }'
```

## Behavior

| Situation | Result |
| - | - |
| First request with a new key | Runs normally. A `2xx` response is stored under the key for at least 24 hours. |
| Retry with the **same key and body** after a `2xx` | Returns the stored response. The change is **not** applied again. |
| Retry with the same key **while the first request is still running** | Returns `409 idempotency_in_progress` with a `Retry-After` header. Wait that many seconds and retry with the same key. Once the first request finishes with a `2xx`, you get its response. A request still marked as running after about 10 minutes is treated as stopped, and a retry with its key runs again. |
| Retry with the same key after a `4xx` or `5xx` | Runs again as a new request, because `4xx` and `5xx` responses aren't stored. |
| Retry with the same key **after the stored response expires** (24 hours or later) | Runs again as a new request. |
| Retry with the **same key and a different body or endpoint** after a `2xx` | Returns `409 idempotency_conflict`. |
| Missing or empty header | Returns `400 validation_failed`. |
| Key longer than 256 characters | Returns `400 validation_failed`. |

Keys are scoped to your API key within your workspace. Two API keys can use the same key value
without conflict. The API compares request bodies byte for byte, so resend the body exactly as you
first sent it.

A `4xx` response changed nothing. Fix the request and send it again, with the same key or a new one.

A `5xx` response may still have saved changes: a create may have made the asset, and a batch may have
saved some records. Before you retry a create, check whether the asset exists. See
[Batch requests](#batch-requests).

> [!WARNING]
>
> A request can keep running on our side after your client times out. While it runs, a retry with the
> same key returns `409 idempotency_in_progress`, so the request never runs twice at the same time. A
> retry with a **new** key does run again, so a create can make a second asset. To prevent that, send
> `externalSource` and `externalAssetId`: the second create then updates the first asset.

## Deletes

`DELETE` requests don't take an `Idempotency-Key` header. Repeating a delete is safe: the first call
deletes the asset, and a second call returns `404 not_found`.

## Batch requests

A batch request returns `200` even when some of its records fail, so its response is stored like any
other `2xx`. A retry with the same key returns that response and doesn't run the failed records
again. To retry failed records, send only those records with a **new** key. See
[Read a batch result](https://developer-staging.secfix.com/guides/inventory.md#read-a-batch-result).

A batch that returns a `5xx` or times out may have saved some records, because each record is saved
on its own. Nothing may be stored under the key, so a retry with the same key runs every record
again. Before you resend a batch create, list your assets and send only the records that don't exist
yet, with a **new** key. If every record has `externalSource` and `externalAssetId`, you can resend
the whole batch instead. See
[When a batch fails or times out](https://developer-staging.secfix.com/guides/inventory.md#when-a-batch-fails-or-times-out).

## Recommendations

* Use a **new** key for each operation. **Reuse** that key only when you retry the same operation.
* Don't reuse a key for a different change. That returns `409 idempotency_conflict`.
* Store the key with the operation, so a retry after a crash uses the same value.
* Asset names and `customIdentifier` values don't have to be unique, so a create that runs twice makes
  two assets. Send `externalSource` and `externalAssetId` on creates: a create that runs twice then
  updates the asset the first one made. Without them, after a `5xx` or a timeout on a create, wait a
  few minutes and check whether the asset exists before you retry.
* A stored response is kept for at least 24 hours after its request finishes. A retry after that runs
  as a new request.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.