Try the Live Demo
See the complete run loop without setting up an app
Example Repository
Explore a complete implementation of this quickstart
Prerequisites
- Node.js: Version 20 or later
- Crossmint API key: A production server-side key with
agent-checkouts.create,agent-checkouts.read,agent-checkouts.update, andagent-checkouts.cancelscopes - Buyer identifier: A stable identifier from your system for the user making the purchase
Understand the Run Loop
Your integration repeats the same loop until the run reaches a terminal status:
The run and its messages are two views of the same work. Prefer the message stream for a live integration. It emits both run updates and message updates, so one connection can drive your control loop and user experience. Use polling when your runtime cannot hold an open connection.
Run a Checkout
1
Configure the client
This quickstart uses a server-side key. Send Keep the server key outside browser and mobile bundles.
x-crossmint-user-id so the checkout, payment authorization, and protected inputs belong to the same user.Call the API from a browser
Call the API from a browser
Use a client-side production key (
ck_production_...) in X-API-KEY and send the signed-in user’s JWT as Authorization: Bearer YOUR_USER_JWT. Register the JWT issuer under 3P Auth providers in the Crossmint Console and restrict the key to your app’s allowed origins. The request bodies and run loop stay the same.2
Start a run
Every run needs a To start from a product page without extra instructions, omit The request returns
startUrl and maxCost. The task is optional.- Use a product URL when the page already identifies what to buy.
- Use a merchant home page, category, search, cart, or any other URL when the agent must navigate before checkout.
- Add a task whenever the URL alone does not fully describe the goal, such as the product, variant, quantity, delivery preference, or payment method.
task:202 Accepted with a runId. The run starts as queued, then moves to running.The checkout returns blocked with code policy.max_cost_exceeded instead of spending above maxCost.3
Choose how to observe the run
Agent Checkouts supports several integration shapes. The checkout browser is managed by Crossmint and does not need to be displayed to the user.The stream emits two event types:For each event:Reconnect to A poll immediately after your response can still contain the same action. Track each
Stream from a Server (Recommended)
Open the stream with the same server-side headers used to start the run. It is a standard HTTP response with content typetext/event-stream, so it works in Node.js, serverless runtimes that support streaming responses, workers, and long-running services.- Store its
idas the resume cursor. - On
run.updated, replace your current run state only when itsrevisionis greater than your stored run revision. If it isawaiting_input, handle itsrequiredActiononce byrequestId. - On
message.upsert, pass the message to the part handler described below. - Reconnect with the cursor in the
last-event-idheader orafterquery parameter. - If a cursor is rejected or the stream closes unexpectedly, list the message history again and reconnect from its
streamCursor.
succeeded, blocked, failed, or cancelled.An iMessage agent, ChatGPT integration, or other conversational agent normally consumes this stream on its backend. It maps agent replies and input requests into its own channel, then sends the user’s response back through the Messages API.
Bootstrap or Resynchronize the Stream
For an existing run, list its messages before opening the stream. The final page’sstreamCursor forms a lossless handoff to live events. Use it as the stream’s after value, and ignore duplicate or stale message and run revisions.Use the same procedure after an unexpected disconnect or rejected cursor:GET /{runId}/messages/stream?after=${streamCursor}. Reuse answeredRequestIds in the stream handler so a replayed run.updated event does not submit the same response twice. See List Agent Checkout Messages and Stream Agent Checkout Messages for pagination and stream details.Poll as a Fallback
When you cannot keep a stream open, poll the run forstatus and requiredAction. Start with a short interval while the agent is active, increase it while nothing changes, and reset it after a status change or response.The handleRequiredAction dispatcher is implemented below because its form, payment, and protected-input branches connect to the appropriate user surface.requestId so you answer it only once.The run’s browser object is null until a browser session exists. When present, a web app can optionally render browser.embedUrl in an <iframe> so the user can watch the read-only session. Other integrations can ignore it.4
Handle message parts
Both listed history and
message.upsert events use the same message shape. Store messages by id, replace a stored message only when a higher revision arrives, and use role to identify the speaker.A message revision orders versions of one message ID. A run revision orders complete run snapshots from polling and run.updated events. Revision values may skip and have no meaning beyond ordering; the stream cursor is the separate replay position used to reconnect.Handle every part in a message because one message can contain several parts.5
Handle required actions
Every First, use one helper for every response. Generate a stable message Submit the keys defined by that request’s The order intent authorizes a secure, purchase-scoped card credential. Raw card details never appear in the run request, message history, or response.The password never reaches your app, your server, the model, or the message history.If the user does not want to submit the requested value, send either:
requiredAction points to an open input_request. Dispatch on requiredAction.request.interaction.kind: form, payment, or protected.id before the call and reuse it if you retry the same HTTP request.Handle a Form Request
A form request carriesresponseSchema and uiSchema. Render the fields from those schemas instead of hardcoding them; the same path handles shipping details, product options, contact information, and merchant-specific questions.responseSchema, and answer before expiresAt.Handle a Payment Request
A payment request carries the amount and merchant domain. Follow Choose a Payment Method to save or select the card, create an order intent for the exact amount, and complete any required verification.When that process returns anorderIntentId, submit it as the response:Handle a Protected Input Request
A protected request currently haspurpose: "password". Follow Buy from Authenticated Stores to create the secure user handoff and render CrossmintProtectedInput.When the component returns a protectedInputId, submit it as the response:action: "alternative"with free-texttext, such as"Check out as a guest instead"action: "decline"to refuse the request
6
Let the user steer
The initial The API returns
task is not final. Send a text message at any time before the run finishes to change preferences, answer a conversational question, or redirect the agent.202 Accepted when it accepts the message. This does not mean the agent has consumed it yet. Read the message again and check its delivery field when your UI needs to show that distinction.7
Read the result
The terminal status tells you how the run ended. Use the structured result for application logic and the final
result message for the user-facing summary.succeeded: The purchase was confirmed.result.purchasecontains a captured receipt or reports that the merchant confirmed the order without exposing one.blocked: The agent stopped safely because of a policy, product, fulfillment, merchant, or payment condition. Useresult.codefor logic andresult.summaryfor display.failed: The run could not continue because of an input timeout, model, browser, accounting, or runtime failure. Usereasonfor recovery logic.cancelled: Your app or the user cancelled the run.
POST /{runId}/cancel. The request returns 202 Accepted; continue following the run until its status becomes cancelled.Launching in Production
Agent Checkouts already uses the production API. Before serving users:- Create a production server-side or client-side API key with the required Agent Checkouts scopes.
- Keep one stable user subject across the run, order intent, and protected input.
- Route card authorization through an order intent and passwords through
CrossmintProtectedInput; never place secrets in text or form messages. - Treat message IDs as idempotency keys, resume the SSE stream from its cursor, and retry transient read failures.
Learn More
Provide Purchase Context
Prefill identity, shipping, and intent so the agent asks fewer questions
Choose a Payment Method
Authorize a secure card credential with an order intent
Buy from Authenticated Stores
Collect passwords securely and reuse merchant logins
Other Links
Create a Checkout
Review the complete run request and response schemas
Send a Message
Review every message your app can send

