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

# Idempotency

> Use Idempotency-Key to make write requests safe to retry.

Network requests can fail anywhere — connection reset, timeout,
gateway 502, ambiguous 5xx. To make retries safe on write endpoints
without creating duplicate resources, send an **`Idempotency-Key`**
header on the original request and on every retry.

## How it works

When the API receives a request with an `Idempotency-Key`:

1. **First time** with that key + tenant: process the request normally
   and cache the response keyed off `(tenant_id, idempotency_key,
   body_hash)` for 24 hours.
2. **Same key + same body**: return the cached response. The request
   is not processed again.
3. **Same key + different body**: return
   [`idempotency_conflict`](/errors/idempotency_conflict) (HTTP 409)
   so you know the key was reused with mismatched intent.

Idempotency keys are tenant-scoped; another tenant with the same key
value sees no collision.

## Where to use it

Use `Idempotency-Key` on every **write** call:

* `POST /api/v1/policies/`
* `POST /api/v1/yara-rules/`
* `POST /api/v1/yara-policies/`
* `POST /api/v1/sdp/policies/`
* `POST /api/v1/safety-policies/`
* `POST /api/v1/api-keys/`
* `POST /api/v1/tenants/`
* `POST /api/v1/billing/portal-session`
* `POST /api/v1/billing/create-checkout-session`
* `POST /api/v1/support/tickets`

Read endpoints (`GET`) are naturally idempotent and ignore the header.

The hero `POST /api/v1/analyze/` deliberately does **not** dedupe on
`Idempotency-Key`. Each analysis is a fresh decision; you almost never
want a cached one. If you need dedupe at your application layer
(e.g. retrying the same prompt should not run analyzers twice in 100
ms), do it in your own caller.

## Generating a key

A UUID v4 is the simplest choice. Both SDKs accept any string; pick
something with enough entropy that you will not accidentally collide
with another caller in your tenant.

```bash theme={null}
curl -X POST https://api.lasscyber.com/api/v1/policies/ \
  -H "Authorization: Bearer ak_live_…" \
  -H "Idempotency-Key: 6f1e3c00-2c5a-4c58-9b2c-2b6f7a4f3a9d" \
  -H "Content-Type: application/json" \
  -d '{ "name": "inbound-strict", "slug": "inbound-strict", ... }'
```

Reuse the same key across **every retry of the same logical write**.
If you generate a fresh key per retry, you defeat the protection.

## SDK helpers

### Python

The Python SDK auto-generates an idempotency key on every retried write
when you do not pass one explicitly. To control it yourself:

```python theme={null}
from agnes import Agnes
import uuid

agnes = Agnes()

agnes.policies.create(
    policy,
    idempotency_key=str(uuid.uuid4()),
)
```

### TypeScript

```ts theme={null}
import { Agnes } from "@lasscyber/agnes-security";

const agnes = new Agnes();

await agnes.policies.create(policy, {
  idempotencyKey: crypto.randomUUID(),
});
```

## Conflicts

If you reuse a key with a *different* body, the API returns:

```http theme={null}
HTTP/1.1 409 Conflict
```

```json theme={null}
{
  "detail": "Idempotency-Key was reused with a different request body.",
  "code": "idempotency_conflict",
  "request_id": "...",
  "doc_url": "https://docs.lasscyber.com/errors/idempotency_conflict"
}
```

Recovery: pick a new key and re-send.

## Cache lifetime

* **24 hours** from the first request. After that the key is forgotten
  and a new request with the same key is treated as a fresh write.
* Cache entries are not visible across tenants.
* Cache entries survive Cloud Run autoscale events (the cache is
  Postgres-backed).

## Common pitfalls

* **Generating the key inside the retry loop.** A fresh key per retry
  is a different request as far as Agnes is concerned. Generate the
  key *once* outside the retry loop.
* **Using the same key for different operations.** A key scopes to the
  full request body, so different operations naturally have different
  bodies — but using `"create-policy-1"` for both a policy create and
  a rule create is asking for confusion. Prefer UUIDs.
* **Sending an empty key.** The API ignores empty / whitespace-only
  values. To use idempotency, send a real value.

## Next

* [Errors → `idempotency_conflict`](/errors/idempotency_conflict)
* [Rate limits](/api-reference/rate-limits) — the other reason a
  retry might come back differently.
