Skip to main content
HTTP 409 · type proposal. This is the one error that is good news: your follow-up turn would change what the session is looking for and needs new source passes, so it was recorded, not run, and came back priced. Nothing has been charged. It’s a 409 — not a 200 with a flag — precisely so a naive retry loop can never silently spend.

The shape

kept_rows is what the session already holds and keeps either way; passes and estimated_usd are what saying yes costs.

How to proceed

  • Confirm: re-POST the same turn with "confirm": true — same text, a fresh Idempotency-Key (the body changed; reusing the old key gets idempotency_conflict, not a silent spend). The confirmed run reserves its own estimate.
  • Or pre-authorize: send max_spend_usd ≥ the estimate on the original turn and the proposal runs without the 409 round-trip.
  • Or decline: do nothing, or post a different turn — a new non-confirm turn voids the outstanding proposal (at most one is outstanding per session; the newest ask wins).
  • Proposals expire after 24 hours. A late confirm doesn’t run at stale prices — the server responds with a fresh proposal (new estimate, new expires_at).
Full flow, with the state machine: Budgets and proposals.

Reproduce it

Deterministic on an fn_test_ key with the FN_TEST_PROPOSAL sentinel: create a session with the sentinel in the query, then post the first follow-up turn — it returns this 409, $0: