Failure modes
The agent surface (propose/apply, claim_branch, redeem_approval,
plus Idempotency-Key and Plan-Key/Plan-Step headers) is engineered
to fail in shapes you can branch on instead of strings you have to parse.
This page is the contract for those failure shapes.
For the structured SQL envelope (unique_violation, not_null_violation,
etc.), see Structured SQL errors.
Common HTTP shape
Section titled “Common HTTP shape”Every /v1/* failure has the same outer shape:
{ "error": "human-readable string", "errorDetail": { "kind": "...", ... }}Branch on errorDetail.kind first, fall back to error for display.
propose + apply
Section titled “propose + apply”A propose call EXPLAIN-validates the SQL, estimates affected rows,
and returns a single-use pmut_… token pinned to
(tokenId, databaseId, branchRef). apply redeems it.
| What can fail | kind | What it means | What to do |
|---|---|---|---|
| Token expired | proposal_expired | pmut_… was minted >TTL ago (default 10 min, max 1 h). | Re-propose with the same SQL + params. |
| Token already redeemed | proposal_consumed | Single-use; second apply always rejects. | Don’t retry apply after a 5xx if you got a 2xx earlier — use Idempotency-Key instead. |
| Token bound to a different database | proposal_scope_mismatch | The pmut_… was minted against (ns, slug, ref) ≠ the one you’re applying to. | Re-propose against the right target. |
| Token bound to a different bearer | proposal_token_mismatch | The bearer that called apply isn’t the one that called propose. | Both legs must use the same psql_live_…. |
| SQL is now invalid (schema changed between propose and apply) | falls through to the regular SQL envelope | The proposal’s row estimate is stale; the actual apply errored at execute time. | Re-propose to revalidate against current schema. |
propose itself can fail with proposal_too_many_rows if the estimate
exceeds the per-token write budget — adjust the SQL or use a wider token.
claim_branch
Section titled “claim_branch”One-shot lease that creates (or resets) a branch with TTL and mints a scoped token in the same call. Admin-only.
| What can fail | kind | What it means | What to do |
|---|---|---|---|
| Caller isn’t an admin | forbidden | Branch claims require namespace owner/admin. | Use a token tied to an admin user. |
| Branch ref already locked by another claim | branch_claim_conflict | Another claim is in flight on the same ref. | Wait + retry with backoff, or pick a different purpose (the suffix randomises). |
| Database is suspended | database_suspended | Namespace billing/abuse hold. | Resolve the hold — claims can’t bypass it. |
The minted scoped token expires with the lease. After expiry, query
and apply from that token return token_expired — the branch itself
is not garbage-collected; it just becomes admin-managed again.
redeem_approval
Section titled “redeem_approval”Runs an originally-blocked write after a namespace member approves it through the console. Single-use.
| What can fail | kind | What it means | What to do |
|---|---|---|---|
| Approval revoked between issue and redeem | approval_revoked | A reviewer denied or rolled back the approval. | Don’t retry. Re-issue the original write so a fresh approval can be requested. |
| Approval expired | approval_expired | TTL passed; approvals don’t auto-renew. | Re-issue the original write. |
| Approval already redeemed | approval_consumed | Single-use. | Treat as success on idempotent retries — the write happened. |
| Database/branch removed since issue | approval_scope_invalid | The target no longer exists. | Re-issue if appropriate. |
Idempotency-Key (24 h KV replay)
Section titled “Idempotency-Key (24 h KV replay)”Send Idempotency-Key: <uuid> on /v1/query, /v1/batch, /v1/apply,
/v1/redeem_approval. The first call’s response is cached for 24 h and
replayed on subsequent calls with the same key.
Replays carry Idempotency-Replay: true in the response headers. The
body is identical to the original — including a previously-cached
errorDetail. Don’t treat a replayed errorDetail as a fresh signal:
the underlying state may have changed since the original call. If you
want to revalidate, use a new key.
Edge cases:
- Same key, different body →
idempotency_key_mismatch. Bump the key. - Key longer than 200 chars or non-ASCII →
idempotency_key_invalid. - Network drop mid-write before the response landed → next call with the same key returns the original response. The write is not re-executed.
Plan-Key + Plan-Step (per-step caching)
Section titled “Plan-Key + Plan-Step (per-step caching)”Use these for multi-call agent plans where the model may retry from a
failed step. Plan-Key identifies the plan; Plan-Step the step
within it. The pair is cached for 24 h like Idempotency-Key, but
gives you per-step granularity instead of one-shot replay.
Same shape rules apply — replays carry Plan-Step-Replay: true. Mismatch
errors share the idempotency_* family above (the implementation is
the same KV path).
Token & spend shapes
Section titled “Token & spend shapes”kind | Status | Meaning |
|---|---|---|
token_invalid | 401 | Bearer is malformed, revoked, or doesn’t exist. |
token_expired | 401 | Scoped token (from claim_branch) past its lease. |
balance_exhausted | 402 | Namespace prepaid balance is at the grace floor. The response body carries an x402-compatible accepts[] array pointing at POST /v1/billing/topup; MCP runtimes can also call the topup_balance tool. |
rate_limited | 429 | Per-IP flood control or the bounded fail-open during a billing-meter outage. Retry honouring Retry-After. |
database_locked | 503 | DO is busy; SQLite returned SQLITE_BUSY. Retry with backoff. |
What’s not a structured failure
Section titled “What’s not a structured failure”- Network 5xxs from Cloudflare itself — surface as the underlying
status (502, 521, etc.) without an
errorDetail. Treat as transient and retry withIdempotency-Keyso you don’t double-write. - Schema drift that breaks a previously-validated
propose— surfaces as the regular SQL envelope onapply, not aproposal_*kind. - Branch deletion mid-plan — currently surfaces as
unknown_table/database_lockedrather than a dedicatedbranch_gonekind. May promote to its ownkindlater; agents should treat 4xx with a branch-scoped token as “re-claim and replay.”
If you hit a failure shape that isn’t here, file it — the contract is load-bearing and gaps are bugs.