> ## Documentation Index
> Fetch the complete documentation index at: https://docs.agentcard.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# Purchases

> One conversational endpoint that builds a cart at a real merchant and places the order once you confirm.

The **Purchase API** is a turn-based loop on one endpoint. You send what the user wants as plain text in `ask`, follow up with the same `conversation_id`, and place a shown cart by echoing its `hash` in `confirm`. Money moves only on a confirm, and only for exactly the cart the hash describes.

The bearer is a **user** token: the connection `access_token` or a cardholder `buy_token`. A platform token is rejected, because a purchase always runs as one user. Use a client timeout of at least 120 seconds.

## The purchase response object

Every `200` from `POST /buy` carries the same envelope. Each field is always present and `null` when empty.

| Field | Type | Description |
| - | - | - |
| `conversation_id` | string | The thread. Send it back on every follow-up. |
| `status` | string | `needs_input`, `order_placed`, `partially_placed`, `declined`. The only field you branch on. |
| `reply` | string | The assistant's turn as prose. `messages` carries it split into chat bubbles. |
| `cart` | object or null | The most recently shown cart. See below. |
| `carts` | array | Every open cart in the conversation, each with its own `hash`. |
| `catalog` | object or null | The last product search as data. See below. |
| `unmatched` | array | Asks that did not make it into a cart: `{ merchant, requested, reason, detail, at }`. |
| `order_id` | string or null | Set when this call placed an order. Reconcile on it. |
| `payment_source` | object or null | `{ source, brand, last4 }`. `source` is `balance`, `added_card`, `vault`, `company_balance`, or `stored_payment_method`. |
| `decline_code` | string or null | `vault_approval_required`, `byoc_approval_required`, `sandbox_mode`, `items_unavailable`, `unsupported_currency`, `currency_changed`, `no_cart`, … `in_progress` means another attempt (usually Agentcard placing the order the moment the approval landed) holds this checkout: wait ten seconds and read `GET /buy/conversations/{id}` rather than confirming again. The payment refusal codes are listed with what to do next on [`order.failed`](/webhooks/orders/order-failed). |
| `approval_url` | string or null | Send to the user when the confirm paused. |
| `charge_status` | string or null | `none`, `confirming`, `settled`, `unknown`. |
| `merchant_total_cents` | integer or null | The merchant's own total for a cart priced outside US dollars, in that currency's smallest unit. Null for a US dollar cart. |
| `merchant_currency` | string or null | The merchant's currency as a lower-case code, `cad`. Null for a US dollar cart. |
| `placements` | array or null | Per-cart outcomes of a multi-cart confirm. |
| `error_code` | string or null | Machine-readable loop failure. |

## The cart object

| Field | Type | Description |
| - | - | - |
| `merchant`, `merchant_name` | string | Merchant id (`retail`, `doordash`, …) and display name. |
| `items[]` | array | `{ name, qty, priceCents, product_id }`. |
| `serviceFeesCents`, `tipCents`, `totalCents` | integer | The all-in total the user approves. |
| `approvedCeilingCents` | integer or null | The most a confirm can authorize when tax and shipping finalize later. Null when equal to the total. |
| `hash` | string | Identifies exactly this cart at this price. Echo it in `confirm`. |

```json theme={null}
{
  "merchant": "retail",
  "merchant_name": "Amazon",
  "items": [{ "name": "Cafe Mesa de los Santos Colombian Ground Coffee, 16 oz", "qty": 1, "priceCents": 2250, "product_id": "https://www.amazon.com/dp/B07ZQ5C4RB" }],
  "serviceFeesCents": 56,
  "tipCents": 0,
  "totalCents": 2306,
  "approvedCeilingCents": 3444,
  "hash": "9f2c4a1b8e3d5f07"
}
```

## The catalog object

Show your own product list from `catalog` instead of reading the results out of `reply`. `catalog` holds the last product search in the conversation, on the turn that searched and on the turns after it. Fifteen minutes after that search, `catalog` is `null`, because prices and item ids can change by then. Send a new `ask` to search again.

| Field | Type | Description |
| - | - | - |
| `merchant`, `merchant_name` | string | Merchant id and display name. `merchant_name` names the store for `retail`, such as `Amazon`, and the merchant otherwise, such as `DoorDash`. |
| `store` | object or null | The `id` of the store searched, and its `name` when known. `null` when the search covered several stores. |
| `store.eta_minutes` | integer or null | A delivery store's estimate in minutes when the search ran, `null` when it showed none. Absent for other merchants. |
| `items[]` | array | Up to 20 results: `{ id, name, priceCents, image_url }`. Only `id` is always present. |
| `as_of` | string | When the search ran, ISO 8601. |

Match a `retail` cart line to its search result: the line's `product_id` and the item's `id` are the same product page URL. Other merchants use their own item id.

Check for `priceCents` before you show a price. An item leaves out a field it does not have instead of sending `null`, unlike the fields of the purchase response object. A restaurant reservation time slot never has a price. A `retail` search drops any product listed without a price, because a cart cannot hold it. An item without a picture has no `image_url`.

```json theme={null}
{
  "merchant": "retail",
  "merchant_name": "Amazon",
  "store": null,
  "items": [
    {
      "id": "https://www.amazon.com/dp/B00R92W0AU",
      "name": "Folgers 100% Colombian Medium Roast Ground Coffee, 10.3 Ounces",
      "priceCents": 1595,
      "image_url": "https://m.media-amazon.com/images/I/81Xh6IS8O7L._AC_UL320_.jpg"
    },
    {
      "id": "https://www.amazon.com/dp/B07P5LD4V8",
      "name": "Folgers 100% Colombian Coffee, Medium Roast Ground Coffee, 9.6 Ounce Canister",
      "priceCents": 1024,
      "image_url": "https://m.media-amazon.com/images/I/816JHMYPHFL._AC_UL320_.jpg"
    }
  ],
  "as_of": "2026-09-28T19:26:04Z"
}
```

Show `image_url` where your app can render an image (a chat bubble, a card) and send `id` when the user wants the page. Both come from the merchant; never build either from the name.

## Endpoints

| Endpoint | |
| - | - |
| `POST /buy` | [Buy](/api-reference/purchases/buy): ask, follow up, confirm |
| `GET /buy/merchants` | [List the merchants](/api-reference/purchases/merchants) `/buy` can place at |
| `GET /buy/conversations/{id}` | [Read a purchase conversation](/api-reference/purchases/conversation): reconcile a confirm whose response never arrived |

Webhooks: `order.placed`, `order.failed`, `order.confirmed`. The guide is [Agentcard's Purchase API](/vault/integrations/ecommerce-apis/purchase-api).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.