Skip to main content
Every quote-creation route accepts an optional idempotencyKey in the request body. Retrying with the same key and the same payload replays the original Quote instead of creating a duplicate — so a flaky network or a double-clicked submit is always safe.

How it works

The key is unique per Environment together with the quote scope. The server stores a hash of the payload alongside the key:
  • Same key, same payload → the original Quote Intent and Receipt are returned (a replay). No new Quote, no duplicated usage event.
  • Same key, different payload409 with code: "idempotency_conflict". The server refuses to guess which request you meant.
  • No key → the server generates one (auto_<uuid>). The Quote is still safe from accidental double-submits inside the same request lifecycle, but you cannot replay it yourself — always send your own key for any request you might retry.

Key rules

The retry pattern

  1. Generate one key per logical submission (for example, per checkout attempt).
  2. Send the request. On a timeout or 5xx, retry with the same key and the same payload.
  3. On 2xx, store the Quote ID. Any later retry with the same key returns that same Quote.
  4. On 409 idempotency_conflict, the payload changed under a reused key — treat it as a bug in your key discipline, not as a transient error.
Never reuse a key across edited payloads. If the customer changes an answer, mint a new key for the new submission — otherwise the edit is rejected with 409. Retries reuse keys; edits regenerate them.

Edited submissions

The rule of thumb: one key per payload. The @pricing/widget controller implements this discipline for you — it mints a fresh qi_<uuid> key whenever the offering or any factor changes after the last submit, and reuses the pending key only for a genuine retry of an unchanged payload. Mirror the same behavior in custom integrations:

What idempotency does not do

  • It does not deduplicate across different keys. Two different keys with identical payloads create two Quotes.
  • It does not make reads consistent — GET replays nothing; it reads current state (Receipts are immutable, so Quote reads are stable anyway).
  • It does not replace error handling. A 422 will fail identically on every replay.