Create a checkout
Returns immediately with a queued checkout. The run happens asynchronously in a durable orchestrator, so a client crash or a network blip never orphans a half-finished purchase.
Requires the checkouts.write scope.
Retries. Send an Idempotency-Key header and a duplicate POST returns the original checkout with 200 rather than buying the item twice. Without one, two identical requests are two runs and two orders. A key is live for 24 hours from the creation of the checkout that claimed it; after that the same string starts a new purchase.
Strict body. An unrecognised top-level field is a 400. A misspelled subjectt used to return 201 with an untagged checkout the whole account could read, and an isolation control that fails open on a typo is not a control.
Authorizations
Authorization: Bearer ck_test_… or ck_live_…. There is no other auth scheme — no query-string keys, no request signing, no OAuth.
Headers
Makes a create safe to retry. A second request with the same key returns the original checkout with 200 instead of starting a second run; the same key with a different body is a 409.
Strongly recommended on every create. A POST that times out after the server accepted it is indistinguishable, from the caller's side, from one that never landed, and every autonomous caller retries a timeout by default — so without this header the worst failure mode of the product is reachable by an ordinary network blip.
The request body is hashed with its object keys sorted and its defaults applied, so key order does not matter and "constraints": {} matches an omitted constraints. Keys are namespaced per (tenant, subject) and live 24 hours, measured from the creation of the checkout that claimed one. Inside the window a retry replays; outside it the key is released and the same string starts a new purchase. Release is an hourly background sweep, so the boundary is approximate to within an hour, and it clears the key off the checkout without otherwise touching the row. An empty or whitespace-only value is treated as absent.
Read by POST /v1/checkouts only; every other endpoint ignores it.
Body
The top level is strict: an unrecognised field is a 400 with an unrecognized_keys issue, not a field the server drops for you. The field most worth misspelling here is subject, and a 201 carrying an untagged row is an isolation control that failed open on a typo.
target, constraints and metadata stay permissive inside — they are extension points, and unknown keys within them are ignored.
Must belong to your tenant, or the request is a 400. Without one the agent has no address, and on most merchants that is where the run stops — or it raises a shipping action.
Checked against this checkout's subject, not the authenticating key's. A subject-bound key naming another end user's profile gets the same 400 buyerProfileId not found — from where it stands the profile does not exist. A tenant-wide key naming one end user's profile alongside another's subject gets a 403 naming both. An untagged profile on a tagged checkout is allowed, and so is the reverse, so subjects can be adopted incrementally.
Your own opaque id for one of your end users. 1 to 200 characters after trimming; never parsed, never validated against anything, and never sent to a merchant. An empty or whitespace-only value is a 400 — a subject that means nothing reads as isolation you do not have.
On a key bound to a subject this is filled in for you: omit it and the row is stamped with the key's subject, name a different one and the request is a 403. On a tenant-wide key it is how a row gets tagged in the first place.
It also namespaces the Idempotency-Key: two end users may pick the same key string without colliding. GET /v1/checkouts?subject= filters the list by it, and checkout.status_changed deliveries carry it.
1 - 200"user_a91f"
Anything you want echoed back on every poll.
Response
An Idempotency-Key replay: this key has already been used with this body. The original checkout, as it is now — not a recording of the original 201, so the status may have moved on and a pendingUserAction may already be waiting. Nothing was created, no quota was consumed, and no second browser was launched.
The whole state of one run. This is what you poll.
succeeded, failed and cancelled are terminal; nothing leaves them.
queued, running, awaiting_user_action, succeeded, failed, cancelled Yours. Stored and echoed back untouched; never sent to a merchant, and not searchable.
Append-only, ordered by sequence. Build your UI around this rather than a spinner.
Your own id for the end user this purchase is for. Absent when the checkout is not tagged to one. Set explicitly at creation, or stamped from the authenticating key's binding.
"user_a91f"
Absent when no profile was attached, and absent after the referenced profile was deleted.
Present once a browser session exists. Not a URL you can watch at — a pointer to the endpoint that mints one.
Present only while the run is blocked. At most one per checkout at a time.
Present only on a succeeded checkout.