Skip to content

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.

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.

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 failkindWhat it meansWhat to do
Token expiredproposal_expiredpmut_… was minted >TTL ago (default 10 min, max 1 h).Re-propose with the same SQL + params.
Token already redeemedproposal_consumedSingle-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 databaseproposal_scope_mismatchThe pmut_… was minted against (ns, slug, ref) ≠ the one you’re applying to.Re-propose against the right target.
Token bound to a different bearerproposal_token_mismatchThe 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 envelopeThe 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.

One-shot lease that creates (or resets) a branch with TTL and mints a scoped token in the same call. Admin-only.

What can failkindWhat it meansWhat to do
Caller isn’t an adminforbiddenBranch claims require namespace owner/admin.Use a token tied to an admin user.
Branch ref already locked by another claimbranch_claim_conflictAnother claim is in flight on the same ref.Wait + retry with backoff, or pick a different purpose (the suffix randomises).
Database is suspendeddatabase_suspendedNamespace 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.

Runs an originally-blocked write after a namespace member approves it through the console. Single-use.

What can failkindWhat it meansWhat to do
Approval revoked between issue and redeemapproval_revokedA reviewer denied or rolled back the approval.Don’t retry. Re-issue the original write so a fresh approval can be requested.
Approval expiredapproval_expiredTTL passed; approvals don’t auto-renew.Re-issue the original write.
Approval already redeemedapproval_consumedSingle-use.Treat as success on idempotent retries — the write happened.
Database/branch removed since issueapproval_scope_invalidThe target no longer exists.Re-issue if appropriate.

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.

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).

kindStatusMeaning
token_invalid401Bearer is malformed, revoked, or doesn’t exist.
token_expired401Scoped token (from claim_branch) past its lease.
balance_exhausted402Namespace 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_limited429Per-IP flood control or the bounded fail-open during a billing-meter outage. Retry honouring Retry-After.
database_locked503DO is busy; SQLite returned SQLITE_BUSY. Retry with backoff.
  • Network 5xxs from Cloudflare itself — surface as the underlying status (502, 521, etc.) without an errorDetail. Treat as transient and retry with Idempotency-Key so you don’t double-write.
  • Schema drift that breaks a previously-validated propose — surfaces as the regular SQL envelope on apply, not a proposal_* kind.
  • Branch deletion mid-plan — currently surfaces as unknown_table / database_locked rather than a dedicated branch_gone kind. May promote to its own kind later; 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.