Crossmint Agent Checkouts
Buy any product from a URL through Crossmint's Agent Checkouts API, authorized by a single server-side API key (no user JWT). You store the key once; then create a buyer profile, create a checkout, and drive it to completion by answering the checkout's user-action prompts.
All API details live in reference/agent-checkouts-api.md.
All calls go through scripts/checkout.mjs (Node.js 18+, no dependencies).
⚠️ Real money. Agent Checkouts run in production against real merchants. A successful checkout places a real, paid order. Always set a
maxCost. Submitting a user action executes immediately — there is no separate confirmation step afterward. Once youcheckout-respondto the action that completes the purchase, payment is charged and the order is placed. Get the user's approval of the total before that call, not after.
The command wrapper
node scripts/checkout.mjs <command> [...]
Run from the skill directory. Every command prints JSON. The key is read from
$CROSSMINT_API_KEY if set, otherwise from the stored file
(~/.crossmint/agent-checkouts.json, mode 0600). The host (production vs
staging) is chosen automatically from the key prefix.
Step 0 — Ensure an API key is configured
Before anything else, check:
node scripts/checkout.mjs key-status
- If
configured: true, continue. - If
configured: false, ask the user for their Crossmint server-side API key (starts withsk_production_...). It must have all Agent Checkouts scopes enabled — the wholeagent-checkouts.*family (create, read, update, and theagent-checkouts.buyer-profiles.*scopes). A key missing any of these fails mid-flow with a403; if that happens, tell the user which scope to add. Store the key with:
node scripts/checkout.mjs set-key sk_production_XXXXXXXX
Store the key only when the user provides it. To rotate/remove: set-key again,
or clear-key. Never print the full key back — key-status shows a masked
preview.
Step 1 — Ensure a buyer profile
A buyer profile holds the reusable name / contact / shipping address so you don't re-enter it each purchase.
- If the user already has a
buyerProfileId, use it (verify withprofile-get <id>). - Otherwise offer to create one. Gather: first & last name, email, phone number, and full shipping address. Always ask for the phone number — merchants frequently require it for shipping/delivery and order updates, and a profile without one often stalls the checkout on a user action. Then:
node scripts/checkout.mjs profile-create '{
"label": "Home",
"name": { "first": "Ada", "last": "Lovelace" },
"contact": { "email": "ada@example.com", "phone": "+1 415 555 0100" },
"shipping": {
"addressLines": ["1 Market St"],
"locality": "San Francisco",
"administrativeAreaCode": "US-CA",
"postalCode": "94105",
"countryCode": "US"
}
}'
Capture the returned id — that's the buyerProfileId. (administrativeAreaCode
is country-prefixed ISO 3166-2, e.g. US-CA; countryCode is ISO 3166-1
alpha-2; contact.phone is a free-form string up to 40 chars — prefer E.164
like +14155550100.) You can also pass a file: profile-create @profile.json.
Step 2 — Create the checkout
Collect the purchase intent from the user and encode it in --request. The
--request string is where you steer everything the merchant page will ask
about. Resolve shipping method and payment method up front and put them in
--request — every checkout needs both, and specifying them here keeps the
run from stalling on an avoidable user action later. Cover, when relevant:
- Variant / options — size, color, style, model, edition.
- Quantity.
- Shipping method — always include one. Ask the user, or fall back to a sensible default and state it: use "cheapest shipping" unless they need it fast (then "fastest shipping") or named a carrier/tier.
- Payment method — always include one. Ask, or default to "pay by card" and say so. Name the method only — do not put card numbers here; if the merchant needs card details they come later as a user action.
- Billing preference — default to "bill to the shipping address" unless the user gives a separate billing address.
If the user hasn't specified these, don't block — apply the defaults above ("cheapest shipping, pay by card, bill to shipping address"), tell the user the defaults you used, and proceed. Only ask first when the choice clearly matters (e.g. they mentioned a deadline, or multiple cards/addresses are in play).
Shopify example — buy a specific variant, cheapest shipping, card, billing same as shipping, capped at $60:
node scripts/checkout.mjs checkout-create \
--url "https://merchant.example/products/classic-tee" \
--request "Buy 1 of the Medium in Black. Use the cheapest shipping method. Pay by card. Bill to the same address as shipping." \
--buyer-profile "<buyerProfileId>" \
--max-cost "60.00" --currency "USD"
Notes:
--urlis required;direct_urlis the only supported target kind.--buyer-profileprefills name/contact/shipping from Step 1.--max-costis a hard ceiling — the checkout fails (max_cost_exceeded) rather than overspending. Always set it. Use the user's stated budget, or the expected price plus a small buffer for shipping/tax, and tell the user the cap you used.--merchant-context "..."adds optional navigation hints.- Repeat
--metadata key=valueto tag the checkout.
Capture the returned checkout id.
Step 3 — Poll and answer actions until terminal
Loop:
node scripts/checkout.mjs checkout-get <checkoutId>
React to status:
| status | what to do |
|---|---|
queued, running |
Wait ~1.5s and poll again. |
awaiting_user_action |
Handle the action (below), then poll again. |
succeeded |
Done. Read receipt (total, merchantOrderId, evidence.confirmationUrl). Report it. |
failed |
Read failure.reason + failure.message. Report it; offer to retry/adjust. |
cancelled |
Report that it was cancelled. |
To pace polling from the shell you can sleep between calls, e.g.
sleep 2 && node scripts/checkout.mjs checkout-get <id>.
Handling awaiting_user_action
The response contains pendingUserAction with:
message— a human description of what's needed.responseSchema— a JSON Schema for the exactvaluesto submit. Read it each time; the fields vary per merchant/step. Never hardcode field names.expiresAt— submit before this or the checkout fails (user_action_expired).
To build the values:
- Parse
responseSchema(properties,required,enums, types). - Fill every field you can from what you already know — the buyer profile, the user's stated variant/shipping/payment/billing preferences, and the checkout context (e.g. pick the shipping option matching "standard", the variant matching "Medium / Black").
- For anything you can't infer (an ambiguous option, a required detail the user never gave, payment/card details, a final-total confirmation), ask the user, then continue.
- There is no "confirm the order" action and no undo. Submitting an action
runs it immediately — the action that completes the purchase charges payment
and places the order the moment you call
checkout-respond. So before submitting any action that finalizes the buy, show the user the total (and what you're about to submit) and get explicit approval. Approve before the call — nothing asks for confirmation after it.
Submit (the actionId is pendingUserAction.id):
node scripts/checkout.mjs checkout-respond <checkoutId> <actionId> \
--values '{ "shippingOption": "standard", "confirm": true }'
--valuesmust satisfyresponseSchema(respect required fields and enums). Inline JSON or@values.json.actionIdgoes in the path only, never in the body (the wrapper handles this).- Default verb is
submit; pass--action cancelto abort that step if the user declines.
After submitting, poll again. Repeat until a terminal status.
Payment / card handling
Assume a usable payment method is available to the checkout (an agent card or a
single-use / virtual card supplied when the payment action appears). This skill
does not create payment methods. If a user action's responseSchema asks
for card details, only ever provide an agent card or a single-use / virtual
card number — never a reusable PAN. Ask the user for the specific value if you
don't have it.
Cancelling
node scripts/checkout.mjs checkout-cancel <checkoutId>
Command reference
| Command | Purpose |
|---|---|
key-status |
Show whether a key is configured (masked) and its environment. |
set-key <key> |
Store a sk_... (or ck_...) key locally at 0600. |
clear-key |
Delete the stored key. |
profile-create <json|@file> |
Create a buyer profile → returns id. |
profile-get <id> |
Retrieve a buyer profile. |
profile-update <id> <json|@file> |
Update a buyer profile. |
profile-delete <id> |
Delete a buyer profile. |
checkout-create --url ... [--request ...] [--buyer-profile ...] [--max-cost ... --currency ...] [--merchant-context ...] [--metadata k=v] |
Create a checkout → returns id. |
checkout-get <id> |
Poll a checkout's status. |
checkout-respond <checkoutId> <actionId> --values <json|@file> [--action submit|cancel] |
Answer a pending user action. |
checkout-cancel <id> |
Cancel a checkout. |