# Agent Checkouts

> Buy things through the Crossmint Agent Checkouts API using a stored server-side API key. Use whenever the user wants to purchase, order, buy, or check out a product from a URL via Crossmint; run an "agentic checkout"; spend against a Crossmint server key; or set up / view / clear the Crossmint API key used for purchases. The skill stores the key locally, creates a reusable buyer profile if none exists, creates the checkout, then polls status and answers each user-action prompt (variant, shipping method, payment, confirmation) — filling in what it knows and asking the user for the rest — until the order completes.

- Skill: `crossmint/agent-checkouts` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add crossmint/agent-checkouts`
- Raw SKILL.md: https://api.skillmd.com/api/skills/crossmint/agent-checkouts/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: crossmint (https://skillmd.com/u/crossmint)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/crossmint/agent-checkouts

---


# 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](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 you `checkout-respond` to 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 with `sk_production_...`). It must have **all Agent Checkouts
  scopes** enabled — the whole `agent-checkouts.*` family (create, read, update,
  and the `agent-checkouts.buyer-profiles.*` scopes). A key missing any of these
  fails mid-flow with a `403`; 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 with
  `profile-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:
- `--url` is required; `direct_url` is the only supported target kind.
- `--buyer-profile` prefills name/contact/shipping from Step 1.
- `--max-cost` is 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=value` to 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 exact `values` to 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`:
1. Parse `responseSchema` (properties, `required`, `enum`s, types).
2. 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").
3. 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.
4. **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 }'
```

- `--values` must satisfy `responseSchema` (respect required fields and enums).
  Inline JSON or `@values.json`.
- `actionId` goes in the path only, never in the body (the wrapper handles this).
- Default verb is `submit`; pass `--action cancel` to 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. |

