Idempotency
When Idempotency-Key matters, what it protects against, and how cached replays interact with credits and rate limits.
Distributed systems lose requests. Idempotency is how Brime makes retries safe — and free.
Why it exists
Two endpoints can do non-trivial work that you don't want to repeat under retry:
POST /v1/extract— pays per URL fetched.POST /v1/research— kicks off a multi-round agent loop.
If your client retries one of these after a transient 5xx, you want the second call to either (a) come back with the original response or (b) be safely re-executed. Naive retry would charge you twice and might launch a duplicate research job.
Idempotency-Key solves this: include the header on your call and
Brime guarantees that any subsequent call within the cache window
carrying the same key + same body receives the original response
verbatim, with no extra credits charged.
Where the header is required vs. optional
| Endpoint | Header status |
|---|---|
POST /v1/extract | Required. The SDKs auto-generate a UUID4 per call if you don't set one. Calling raw HTTP without it returns 400. |
POST /v1/research | Required. Same SDK auto-gen behaviour. |
POST /v1/search | Optional. Search is cheap and idempotent by design. |
GET /* | Not applicable. |
How the cache decides what to replay
Idempotency-Key + hash(canonical request body)
↓
{ response, status, credits_used: 0 }
↓
24-hour windowThe cache identifier combines your header value with a content hash of the request body. The branches:
- Same key, same body → cached response is replayed.
credits_usedon the replay is 0; the original debit stands. - Same key, different body → 422
idempotency_conflict. We refuse to replay because we cannot tell which intent was correct. - Different key, same body → fresh call, fresh debit.
The cache window is 24 hours. After expiry, the same key is treated as fresh.
Interaction with rate limits
Cached idempotent replays do not consume quota. A flaky network
shouldn't burn your QPS budget. The internal request order is
rate-limit → idempotency → handler, so cached replays still pass
through the rate limiter, but the limiter accounts for them separately
and rolls back the quota tick when idempotency detects a cache hit.
Auto-generation in the SDKs
Both Python and Node SDKs generate a UUID4 per call when the caller does not pass one. That gives you the safety of automatic retry without double-billing risk. If you build your own retry loop on raw HTTP, you must pass the same key on every retry attempt.
The MCP server (mcp-server-brime) generates a fresh key per tool call,
since each tool call is a separate logical request.
What the header value should be
Any string up to 255 characters. UUIDs are conventional; any stable unique-per-intent identifier works. Common patterns:
- Per-call random UUID (what the SDKs do).
- Per-user-action ID (e.g., the form submission ID in your app).
- Hash of
(user_id, business_event_id)for at-most-once business semantics across deploys.
Avoid time-derived values without random suffixes — two requests in the same millisecond could collide.
What the header does not do
- It is not an authentication token. Anyone with your
sk-brime-…key can call with anyIdempotency-Key. - It does not prevent two different keys from racing the same body. Use a stable application-level key (option 2 or 3 above) if you need cross-process at-most-once.
- It does not extend a credit refund window. Once a real call has been billed, the credit debit is final; cached replays just stop further debits.
See also
- Credit model — what
credits_used: 0on a replay actually means for your balance. - Errors —
idempotency_conflictand other 4xx codes you can get from this header.