Try the live demo
See the full flow in action without setting anything up locally.
Agent Checkouts Quickstart
Explore the full reference implementation this guide is based on.
Server-side or client-side
Every endpoint in this guide accepts both call styles, so pick the one that matches your app and use the same style for every request:
You choose once in Setup; every call after that is identical. Server-side is the shorter path because it requires no external auth provider. Checkouts and buyer profiles are isolated per project and subject, so resources created under one subject are only visible to that subject.
How it works
A checkout walks a simple lifecycle with no webhooks in v1 — you poll until it reaches a terminal state:- Create a checkout and get back an
id. - Poll
GET /{id}(≈ every 1.5s) as it walks the lifecycle. - Embed the live browser session in an
<iframe>so the user can watch. - Respond to each
pendingUserActionby rendering a form from the JSON Schema it carries. - Read the terminal result — a receipt on success, or a failure reason.
Prerequisites
- Node.js 20+
- A production API key server-side (
sk_production_...) or client-side (ck_production_...) with the scopesagent-checkouts.create | read | update | cancel, plusagent-checkouts.buyer-profiles.create | read | update | deletefor buyer profiles - For client-side calls only: an external auth provider (e.g. Stytch, Auth0, Clerk) that issues JWTs for your signed-in users.
Setup
Pick your call style once — the rest of the guide is the same for both.- Server-side
- Client-side
1
Get a server-side API key
Sign in to the Crossmint Console and create a project.Under API Keys, create a server-side key (
sk_production_...) with the agent-checkouts.create | read | update | cancel and agent-checkouts.buyer-profiles.create | read | update | delete scopes. Keep it secret and never ship it to a browser or a mobile app.There is no auth provider to register: a server key authenticates on its own, and checkouts run under a fixed service subject for your project.2
Configure your requests
Point your code at the production API and send the same headers on every request.Send
x-crossmint-user-id to run the checkout on behalf of a specific end user, using your own identifier for that user. The subject is namespaced under your project, so a server key can only address its own project’s subjects; omit the header and everything runs under the shared service subject.Run a checkout
Every step below reuses theBASE_URL and headers from setup, so the code is identical for both call styles.
1
Save a buyer profile
A buyer profile is a reusable set of the buyer’s name, contact, and shipping (there is deliberately no payment block). Save one, then attach it to any checkout so the agent fills those fields from it instead of asking every time.
2
Create a checkout
Give the agent a The checkout fails with
target.url, an optional request (natural-language instruction), and a required maxCost. Attach the saved profile with buyerProfileId, and save the returned id.max_cost_exceeded rather than ever spending above maxCost.The request is where you steer the agent. The more you specify up front — size, delivery option, billing address, which payment method to use — the fewer questions it stops to ask; say nothing and it surfaces a pending action for each. The one thing you can’t put here is card details — the agent always collects those through a payment user action (the step below).3
Poll the lifecycle
There are no webhooks in v1 — poll
GET /{id} (≈ every 1.5s) until the status is terminal. Swallow transient poll errors and retry rather than killing the loop.4
Embed the live browser session
The view returns a
browser.embedUrl so your user can watch (and, with write permission, interact with) the session the agent is driving. When you poll server-side, forward this URL to your frontend to render the same iframe.5
Respond to user actions
When Payment is one of these actions. When the agent reaches the payment step it raises a payment action, and you submit the card details in the
status is awaiting_user_action, the view carries a pendingUserAction with a responseSchema (a JSON Schema). Render the form dynamically from that schema — never hardcode fields — so the same code handles shipping, payment, or size selection.values you send back — you can’t pre-supply them in the create request. Submit only an agent card or a single-use / virtual card number, never a real, reusable card number (PAN): a scoped or one-time number can’t be reused if it leaks; a reusable PAN can.Why single-use and virtual card numbers are safe to submit, but a real PAN is not
Why single-use and virtual card numbers are safe to submit, but a real PAN is not
The card networks put scoped, one-time numbers outside PCI-DSS scope precisely because a leak can’t be reused:
- Visa: single-use virtual Visa account numbers are “out of scope for PCI DSS protection requirements based on the low risk of fraud,” and EMVCo payment tokens “are not considered to be Visa account data and are not in scope for PCI DSS” — while “all other Visa primary account numbers (PANs) must be protected in accordance with PCI DSS” (Expanded Position on PCI DSS Applicability for Virtual Visa Accounts).
- Mastercard: Single-Use Virtual Card Numbers are treated as out of PCI-DSS scope because the number is disabled after one authorization and cannot be reused (Virtual Card Numbers and SDP Compliance FAQs).
6
Read the result
When the checkout reaches a terminal state, read the outcome.To stop a checkout early, call
DELETE /{id} — it’s accepted immediately and the status flips to cancelled on a later poll.Next Steps
Provide Purchase Context
Buyer profiles and the buyer request — prefill identity, shipping, and intent.
Choose a Payment Method
Supported methods, and how to pass a card safely.
API Reference
The full request/response schema for every endpoint.

