> ## Documentation Index
> Fetch the complete documentation index at: https://docs.fiveninelabs.com/llms.txt
> Use this file to discover all available pages before exploring further.

# proposal_required

> 409 proposal — not a failure. The turn needs new passes; here's the price. Confirm it or pre-authorize it.

**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

```json theme={null}
{
  "error": {
    "type": "proposal",
    "code": "proposal_required",
    "message": "This follow-up changes the merchant set and needs 3 new passes (~$1.20).",
    "doc_url": "https://docs.fiveninelabs.com/errors/proposal_required",
    "request_id": "req_2xk91a",
    "proposal": {
      "turn": 4,
      "estimated_usd": 1.2,
      "passes": 3,
      "re_resolve": true,
      "kept_rows": 228,
      "expires_at": "2026-08-26T15:32:07Z"
    }
  }
}
```

`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](/errors/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](/guides/proposals).

## Reproduce it

Deterministic on an `fn_test_` key with the
[`FN_TEST_PROPOSAL` sentinel](/guides/test-mode): create a session with the sentinel
in the query, then post the first follow-up turn — it returns this 409, \$0:

```bash theme={null}
curl https://api.fiveninelabs.com/v1/sessions \
  -H "Authorization: Bearer $FIVENINE_TEST_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query": "FN_TEST_PROPOSAL vinyl records", "max_spend_usd": 2}'
# → take the returned id, then:
curl https://api.fiveninelabs.com/v1/sessions/sess_REPLACE/turns \
  -H "Authorization: Bearer $FIVENINE_TEST_KEY" \
  -H "Content-Type: application/json" \
  -d '{"text": "also check San Antonio shops"}'
```
