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

# Idempotency

> Which Operata write endpoints are safe to retry today, and the planned `Idempotency-Key` header contract for future POST endpoints.

The Operata API is idempotent today by HTTP method — `GET`, `PUT`, and `DELETE` are all safe to retry without coordination. `POST` endpoints are not yet exposed; when they are, the platform will honor a Stripe-style `Idempotency-Key` header contract described below.

## Endpoint

```http theme={null}
Any write endpoint under https://api.operata.io
```

## Parameters

| Name              | Type            | Required            | Description                                                                                                             |
| ----------------- | --------------- | ------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `Idempotency-Key` | string (header) | no, not yet honored | Reserved for a future contract. The server accepts the header today but does not act on it. See the rollout plan below. |

## Response

Today, idempotency is a property of the HTTP method, not of an extra header. The current Operata API behaves as follows on retry:

| Method   | Endpoint                      | Idempotent today | Notes                                                                                                                         |
| -------- | ----------------------------- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `GET`    | every read endpoint           | yes              | Reads never change state. Retry without coordination.                                                                         |
| `PUT`    | `/v1/config/json`             | yes              | Sending the same redaction config body twice yields the same final state. Sending a different body replaces the previous one. |
| `DELETE` | `/v1/config/REDACTION_CONFIG` | yes              | Deleting an already-deleted config returns `404 not_found`. The end state matches.                                            |
| `POST`   | none today                    | n/a              | The current API exposes no `POST` write endpoints. When it does, the rules below apply.                                       |

The planned `Idempotency-Key` contract follows the Stripe pattern:

* The key is a client-generated string, opaque to the server, 1–255 characters. UUIDv4 is the recommended shape.
* The server stores the response for 24 hours, keyed by the tuple `(token, method, path, idempotency_key)`.
* A repeat request with the same key returns the stored response with header `Idempotency-Replayed: true`.
* A repeat request with the same key but a different body returns `409 conflict` with code `idempotency_conflict`.
* After the 24-hour window, the key is forgotten and the request executes normally.

## Example

Sending the same redaction config twice leaves one final state:

<CodeGroup>
  ```bash curl theme={null}
  # First request — writes the config.
  curl -X PUT -u "$OPERATA_GROUP_ID:$OPERATA_API_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{"key":"REDACTION_CONFIG","value":{"type":"BLACKLIST","fields":["Agent.ARN"]}}' \
    "https://api.operata.io/v1/config/json"

  # Same request, sent again — same final state, no duplicate side-effects.
  curl -X PUT -u "$OPERATA_GROUP_ID:$OPERATA_API_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{"key":"REDACTION_CONFIG","value":{"type":"BLACKLIST","fields":["Agent.ARN"]}}' \
    "https://api.operata.io/v1/config/json"
  ```

  ```python Python theme={null}
  import os, uuid, requests

  # Generate one key per logical operation, not per HTTP attempt.
  # The header is accepted today but not honored — include it now to be ready.
  key = str(uuid.uuid4())
  headers = {"Idempotency-Key": key, "Content-Type": "application/json"}
  body = {"key": "REDACTION_CONFIG", "value": {"type": "BLACKLIST", "fields": ["Agent.ARN"]}}

  response = requests.put(
      "https://api.operata.io/v1/config/json",
      auth=(os.environ["OPERATA_GROUP_ID"], os.environ["OPERATA_API_TOKEN"]),
      headers=headers,
      json=body,
  )
  response.raise_for_status()
  ```
</CodeGroup>

## Errors

| Status | Code                      | When                                                                                                                    | Recover                                              |
| ------ | ------------------------- | ----------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- |
| 409    | `idempotency_conflict`    | A future request reuses an `Idempotency-Key` from the last 24 hours with a different body. Reserved; not emitted today. | Either send the original body or generate a new key. |
| 400    | `invalid_idempotency_key` | Reserved. The key violates the length or character set rules.                                                           | Generate a fresh UUIDv4.                             |

See [Errors](/docs/api-errors) for shared codes such as `401 unauthorized` and `429 rate_limited`.
