brime

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

EndpointHeader status
POST /v1/extractRequired. The SDKs auto-generate a UUID4 per call if you don't set one. Calling raw HTTP without it returns 400.
POST /v1/researchRequired. Same SDK auto-gen behaviour.
POST /v1/searchOptional. 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 window

The 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_used on 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 any Idempotency-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: 0 on a replay actually means for your balance.
  • Errors — idempotency_conflict and other 4xx codes you can get from this header.