# Idempotency

Network timeouts and aborted connections make it hard to know whether a metering POST actually landed. Replaying the call could mean double-counted usage, which then has to be cleaned up by hand. Revenium accepts a Stripe-style `Idempotency-Key` header on metering write endpoints so retries are safe by default.

## How it works

Send a unique, client-generated string in the `Idempotency-Key` header on a metering POST. Revenium caches the response (status code and body) for 24 hours keyed by your account and that key. Any retry that reuses the same key replays the cached response without re-executing the request. Use a fresh key for every distinct call, and reuse the same key only when retrying that exact call.

The header is opt-in. Requests without it pass through unchanged.

## Supported endpoints

Idempotency applies to every REST metering POST endpoint.

| Method | Path                       |
| ------ | -------------------------- |
| `POST` | `/meter/v2/ai/completions` |
| `POST` | `/meter/v2/ai/images`      |
| `POST` | `/meter/v2/ai/audio`       |
| `POST` | `/meter/v2/ai/video`       |
| `POST` | `/meter/v2/apis/requests`  |
| `POST` | `/meter/v2/apis/responses` |
| `POST` | `/meter/v2/events`         |
| `POST` | `/meter/v2/tool/events`    |

OTLP endpoints (`/v2/otlp/**`) are not covered today. The OTLP SDKs already retry internally, so end-to-end retry safety on that path will land in a future release.

## Key format

| Constraint  | Value                                                                    |
| ----------- | ------------------------------------------------------------------------ |
| Length      | 1 to 255 characters                                                      |
| Charset     | Printable ASCII only (`!` through `~`, no spaces, no control characters) |
| Recommended | UUID v4 generated client-side, one per logical request                   |

Keys are scoped per account, so two accounts can use identical key strings without colliding.

## Behavior matrix

What the server returns depends on whether a record already exists for `(account, key)` and what the request body looks like.

| Scenario                                                                                                                                    | Result                                                                          |
| ------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
| New key                                                                                                                                     | Request runs normally, response is cached for 24 hours                          |
| Same key, same request                                                                                                                      | Cached response is replayed: original status code, body, and content type       |
| Same key, different body                                                                                                                    | `409 Conflict` with code `idempotency_key_mismatch`                             |
| Same key, concurrent retry while the first call is in flight                                                                                | `409 Conflict` with code `idempotency_key_in_progress`, header `Retry-After: 1` |
| Key fails format validation (empty value, longer than 255 characters, non-printable characters, or sent more than once in the same request) | `400 Bad Request` with code `invalid_idempotency_key`                           |

The fingerprint that defines "same request" is `(HTTP method, request path, request body)`. Request headers (including content type) and query string are not part of it. If you send the same body to the same endpoint with the same key, you will get the cached response back.

## Error envelope

All idempotency errors share the same JSON envelope.

```json
{
  "error": {
    "type": "idempotency_error",
    "code": "idempotency_key_mismatch",
    "message": "The same Idempotency-Key was used with a different request.",
    "doc_url": "https://docs.revenium.io/integrations/idempotency"
  }
}
```

| Field     | Description                                                                                 |
| --------- | ------------------------------------------------------------------------------------------- |
| `type`    | `idempotency_error` for `409` conflicts, `validation_error` for `400` bad key format        |
| `code`    | One of `idempotency_key_mismatch`, `idempotency_key_in_progress`, `invalid_idempotency_key` |
| `message` | Human-readable description of the specific case                                             |
| `doc_url` | Link back to this page                                                                      |

## Recommended client behavior

1. **Generate one UUID v4 per logical request.** Treat the key as part of that request, not a per-session token. The same logical retry must reuse the same key.
2. **Persist the key with your retry state.** If your client retries from another process or after a restart, it still needs the original key to benefit from replay.
3. **Send the same body on retry.** If your client mutates the payload between attempts (a fresh timestamp, a regenerated trace id), the second call returns `idempotency_key_mismatch`. Build the body once, then resend the same bytes.
4. **Handle `idempotency_key_in_progress` with a short wait.** Your previous call is still executing. Respect `Retry-After: 1` and try again.
5. **Treat the cache TTL as 24 hours.** A retry sent more than 24 hours after the original call will execute fresh, with no replay. By that point a real duplicate is extremely unlikely; if you need stricter guarantees, deduplicate client-side as well.

If you have a use case that needs broader idempotency coverage, [contact support](mailto:support@revenium.io).


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.revenium.io/integrations/idempotency.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
