Skip to main content
The Purchase API — POST /buy — is the one-call purchase surface. Your agent sends what the user wants in plain language; Agentcard’s shopping agent handles every merchant’s flow — store selection, delivery address, carts, per-merchant quirks — and hands back typed state your agent can verify. You integrate one endpoint and get every merchant in the catalog, current and future, without writing per-merchant logic.
Authentication. Every /buy call is authorized with the user’s connection access_token as the bearer — the token pair your backend stored from user authentication. A purchase always runs as one user (their carts, their address, their card), so the org client secret and platform client_credentials tokens are rejected here. On 401, refresh the pair via POST /api/v2/connect/refresh and retry.
Over MCP: the same loop is available as the buy tool on the Agentcard MCP server, using the same connection token — one surface, two transports.

Instructions for your agent

Paste this into your coding agent to integrate the Purchase API. It assumes users are already connected (you store their tokens — see user authentication).
Instructions for your agent

How it works

1

Send the ask

POST /buy with the user’s request in plain language, authorized with that user’s connection token. The first response returns the conversation_id you thread through every follow-up.
2

Answer questions

needs_input turns carry a question in reply — address, store choice, which item. Relay it to your user and send the answer as the next ask.
3

Verify the cart, confirm the hash

When a cart is on the table the response carries it as typed data with a hash. Your agent verifies the items and total as types and confirms by echoing the hash — never by parsing prose.
4

The order places

order_placed carries the confirmation. Track and manage follow-ups over the same conversation, or the MCP shopping tools.

The response envelope

Every successful turn (HTTP 200) carries the same fields — always present, null when empty — so a typed client never guesses:
needs_input is the normal path, not an error: the agent asked a question (address, choice, confirmation). Send the answer as the next ask with the same conversation_id. Non-200 responses are compact error objects, not the envelope:

The typed cart, and the deterministic confirm

A real purchase needs one thing to be exact: what gets charged. When the agent has a cart ready, the response carries it as data:
Your agent verifies items, qty, priceCents, and totalCents as types — no prose parsing — and confirms by echoing the hash instead of saying “yes”:
The hash is checked before any model turn against the server’s record of the cart as last shown — the same record checkout executes from. If that shown cart changed since your hash was issued (an item added or removed, a quantity, a price, a fee — anything that re-recorded it), the call fails hard with 409 and the fresh cart:
Two gates protect the money moment. The hash is a deterministic acknowledgment of the exact cart your agent verified (items, quantities, prices, fees, tip) — a stale or wrong hash never reaches the shopping agent at all. Then, at checkout itself, the server re-checks the live merchant total against what was shown: a total that drifted after your confirm makes checkout refuse and re-show rather than silently charge a different amount. (The second gate is total-scoped — it does not detect a merchant-side substitution at an identical total.) Prose confirmations ("ask": "yes, place it") pass through the same checkout gate — the hash path adds the deterministic first gate that makes the verification yours.
Money only moves after a cart has been shown and confirmed in a later call — one-shot purchases are deliberately impossible. Checkout runs behind the same spend controls as everything else: budgets, per-merchant policies, approvals, and the order-markup disclosure.

Statuses

A purchase that needs an owner approval reports needs_input — the reply explains the pending approval, and once it is granted the next ask (or confirm) proceeds.

Multiple merchants, one conversation

A conversation can hold a shown cart at several merchants at once — ask for sushi from DoorDash and batteries from Amazon in the same thread, and carts carries one entry per merchant, each with its own hash. Confirm any subset in one call by sending an array:
The hashes are all verified up front (any mismatch fails the whole call with 409 and the fresh carts — nothing places). Then the orders place sequentially, each as its own turn, and the response reports each outcome:
There is deliberately no cross-merchant atomic transaction — merchants settle and fail independently at the money layer, so partial success is a real outcome. Branch on placements entries, not just the top-level status: order_placed = all placed, partially_placed = some, and a multi-cart confirm cannot carry an ask (400) — send follow-ups as their own turns.

Conversation threading

conversation_id is returned on every successful turn, including the first (error bodies carry it only when a conversation is involved — see the table above). Thread every follow-up with it. A conversation keeps its cart, address, and merchant context across turns; a placed order clears the cart. Continuing a closed conversation returns 409.

Merchants

The catalog behind /buy is Agentcard’s merchant network — food delivery, groceries, flights, retail, subscriptions — and it grows without any change on your side: your integration is the endpoint, not the merchants. To shop with your own retailer accounts on the retail catalog (order history, Prime and loyalty benefits), see Retailer accounts.

Timeouts and confirm retries

A turn can take up to a few minutes when it searches and builds carts against live merchants — use a client timeout of at least 120 seconds. A lost confirm response leaves the outcome unknown to your client — and a turn’s server-side budget is 10 minutes, so your client timing out does not stop an in-flight placement. After a completed placement the cart is cleared, so a replayed confirm returns 409 (“nothing to confirm”) and can’t double-place. But while the outcome is unresolved — the first attempt still running, or stopped before placing — the cart can still be live, and a replayed hash re-attempts checkout rather than reporting status. There is no idempotency key on confirm today. So on a lost confirm response, send nothing to /buy — no replayed confirm, and no fresh ask that rebuilds a cart (that’s a second order, not a retry) — until you have verified the outcome out of band. Merchants with order history expose it typed — buy_order_history over MCP or GET /buy/v1/merchants/<merchant>/orders (the retail catalog does not support order history) — and every placement ultimately shows in the user’s transaction feed. When the result stays ambiguous, surface it to the human (“please check your orders before I try again”) rather than guessing. A first-class idempotent confirm with a typed placement lookup is on our roadmap.