Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.mareaalcalina.com/llms.txt

Use this file to discover all available pages before exploring further.

Safe mutations

Marea honors Idempotency-Key on every POST and PATCH. Replays of the same key + same body return the same response (status + body, including failures). This lets agents retry network errors without double-creating resources.
POST /v1/users HTTP/1.1
Host: api.mareaalcalina.com
Authorization: Bearer mk_dev_...
Content-Type: application/json
Idempotency-Key: 2f1a8c4b-2e3a-4b9d-9f1a-8c4b2e3a4b9d

{
  "email": "owner@taqueria.example",
  "displayName": "La Taquería",
  "sourceAgent": "claude-code"
}

Header contract

PropertyValue
Header nameIdempotency-Key
Length1–255 characters
Character classASCII printable only (0x200x7E)
Recommended formatUUID v4
Applies toPOST and PATCH
Ignored onGET, OPTIONS, DELETE
Invalid headers (too short, too long, or containing non-printable bytes) return 400 invalid_idempotency_key. The regex is ^[\x20-\x7e]{1,255}$.

Match key

A stored idempotency record is keyed on the triple (keyId, method+path, Idempotency-Key). The body is hashed (canonical JSON, sorted keys, SHA-256 hex) and compared on every replay.
  • Cross-key replays don’t work. Different API keys with the same Idempotency-Key are independent requests.
  • Cross-endpoint replays don’t work. POST /v1/storefronts and POST /v1/storefronts/:id/products with the same Idempotency-Key are independent.

Outcomes

Match statusWhat you getWhen
ownedHandler runs; response is stored for future replaysFirst time the key is seen
replayOriginal response replayed verbatim (same status + body)Same key + same body; original completed
in_flight409 conflict + Retry-After: 1 (recoverable: true)Same key + still processing
conflict409 idempotency_conflict (recoverable: false)Same key + different body
oversize410 idempotency_snapshot_unavailable (rare)Original response exceeded the 100 KB snapshot cap

Missing the header

Sending POST / PATCH without Idempotency-Key is not an error. The request proceeds and Marea returns a Marea-Recommendation: include-idempotency-key response header to nudge you. Skipping the header is fine for one-shot calls; include it for any retry-prone integration.

The four 409 / 410 error shapes

in_flight — same key still processing

{
  "error": {
    "type": "conflict",
    "code": "idempotency_in_flight",
    "message": "Another request with this Idempotency-Key is still being processed. Retry shortly.",
    "recoverable": true,
    "retryAfterMs": 1000
  }
}
Wait 1s and retry with the same key. Two concurrent calls on the same key are serialized server-side — only one wins owned; the other waits or replays.

conflict — same key, different body

{
  "error": {
    "type": "idempotency_conflict",
    "code": "idempotency_conflict",
    "message": "Idempotency-Key was used earlier with a different request body.",
    "recoverable": false,
    "nextActions": [
      { "label": "Generate a new Idempotency-Key for this request.", "method": null, "url": null }
    ]
  }
}
Generate a fresh key. Don’t reuse it. The most common cause is an agent retrying with edits (“same call, but change the email”) — that’s a new request and needs a new key.

invalid_idempotency_key — format error

{
  "error": {
    "type": "invalid_request",
    "code": "invalid_idempotency_key",
    "message": "Idempotency-Key must be 1-255 ASCII printable chars.",
    "param": "Idempotency-Key",
    "recoverable": false
  }
}
Fix the header. UUID v4 is the safe default.

idempotency_snapshot_unavailable — oversize response

{
  "error": {
    "type": "invalid_request",
    "code": "idempotency_snapshot_unavailable",
    "message": "The original response was too large to store. Retry without the Idempotency-Key.",
    "recoverable": false,
    "nextActions": [
      { "label": "Reissue the request without the Idempotency-Key.", "method": null, "url": null }
    ]
  }
}
Response snapshots are capped at 100 KB (Firestore doc-size headroom). On the rare oversize response, the original is marked oversize and future replays return 410 — drop the header and reissue.

What is stored

The first successful response is persisted as (keyId, method+path, Idempotency-Key) → { status, body }. Failed responses are also stored — a replay of a failed call returns the same failure. The stored snapshot is held to back replays; there is no client-controlled TTL.

Convention

Stripe-style. If you already use a Stripe retry library that drives Idempotency-Key, it works on Marea unchanged.

Verification in code

  • src/api/middleware/idempotency.ts — header parsing + replay vs. owned vs. conflict branching.
  • src/api/services/apiIdempotency.service.tsacquireOrReplay, complete, fail, snapshot cap (SNAPSHOT_MAX_BYTES).