Skip to main content
The Purchase API is one endpoint, POST /buy. You send the shopper’s request in plain language, Agentcard works out the store, the cart and the money, and answers with what it needs next. The Commerce SDK wraps this endpoint for TypeScript agents. If your agent runs in another language or another framework, call it directly: everything the SDK does is on this page. The base URL is https://api.agentcard.sh.

Get a token

Your company’s client_id and client_secret exchange for a bearer token. A sandbox client runs every call in the sandbox.
The CLI creates the Agentcard account these credentials belong to. The Get started form on agentcard.com asks the Agentcard team for an invite, so a signup that stops at the website has no credentials to copy. For HTTP alone, run npx agent-cards signin, where a new email creates the account, then npx agent-cards credentials --mode sandbox, which asks your company’s name the first time and copies the pair to your clipboard. Neither command installs a package or edits your project. npx agent-cards init creates the same account and goes further: it installs @agent-cards/commerce-sdk and has Agentcard’s agent build the buy tool into a TypeScript project.
Send it as Authorization: Bearer <access_token> on every call below. Create an access token documents the endpoint in full.

Send the shopper’s id

A client-credentials token buys for a connected person, so each call carries your own id for that shopper as external_user_id. Agentcard’s own user_id, which the connect flow returns for that shopper, works in its place.
external_user_id goes in every request, not only the first. A follow-up call that leaves it out is refused with user_id_required, even when it carries the right conversation_id.

Expect one call per turn

A real order needs facts the first message rarely carries: which store, where it ships, and above all an explicit yes to the final price. Agentcard refuses to check out in the same turn that it showed the cart, so one call cannot always finish a purchase. That is deliberate, and it is why needs_input is the normal answer rather than an error.
Every answer carries a conversation_id, including the first. Send it back on each later call to stay in the same purchase. The answer also carries reply and messages for the shopper, message_id, and the typed blocks below. Fields that do not apply to the turn are null.

Read the products it found

A search turn answers with a catalog block. Each item carries the id a later call picks it by.
Put one in the cart by sending the shopper’s words as ask together with pick:

Verify the cart, then confirm

Whenever a cart is on the table, the answer carries a typed cart block. Read it rather than the prose: it is the same server-side snapshot the checkout gate trusts.
Confirm by sending the hash, never prose:
The hash is checked before any model turn. confirm needs a conversation_id. One conversation can hold a cart at several stores at once: carts[] lists them, each with its own hash, cart is the most recent, and confirm also takes an array of hashes. A cart that drifted after the shopper saw it, in an item, a quantity, a price, a tip or a fee rate, is refused, and nothing is charged:
The refusal carries the carts as they stand now, in the same cart and carts[] blocks as a cart answer. For a conversation holding one cart, show the shopper cart and confirm its hash on their new yes:
With a cart at more than one store, confirm every hash you still mean to buy. One stale hash refuses the whole array and charges nothing, and cart is only the most recent cart, not necessarily the one that drifted: match carts[] against the stores you asked for, show the shopper those carts, and send their hashes together in confirm.

Show the shopper a step

Some turns answer with a URL instead of a question, because only the shopper can finish the step. Show it to them, and call /buy again once they are back. A retail order also needs a shipping address, a name for the label and a phone number. Agentcard asks for them one at a time through reply, and suggested_address and address_issue carry what it made of an address you sent.

Check where Agentcard can pay

Ask before you send a shopper to a store. q takes a name, a domain, or a full URL: the scheme, www. and the path are stripped for you.
supported: false with a supported_reason describes that store on that read, not a fault in your call. To list every store, omit q and keep kind=all. When next_cursor comes back as anything but null, send the same request again with cursor set to that value: the endpoint reads cursor, never next_cursor, so reusing the response’s own field name returns the first page again. Repeat until next_cursor is null.

Test in the sandbox

A sandbox token runs the whole flow against real stores up to the confirm. The Vault code that connects a test shopper is always 111111, and you save any of Stripe’s published test cards. The confirm then ends with decline_code: sandbox_mode, because a sandbox card cannot pay a real store. Which status carries it depends on the route: a catalog order ends declined, and a store’s own checkout that the shopper approved ends approved_not_charged, where the card was approved and never sent to the store. Both are the pass condition, not a failure: everything before them, the search, the cart and the Vault steps, is identical to live.

Read a refusal, take a next step

Every refusal carries a code, and the ones a first integration meets are below. A refusal is an HTTP status with a JSON body; a declined answer is a 200 whose decline_code says why.