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

# AgentcardClient

> Holds your company's credentials, renews the access token, and sends each purchase call to Agentcard.

`AgentcardClient` is the class every buy tool and listener shares. The class exchanges your company's client credentials for an access token, renews that token a minute before it expires, and sends purchase turns, conversation reads and Vault link requests to Agentcard. Build one and keep it for the life of your process.

```ts agent.ts theme={null}
import { AgentcardClient } from '@agent-cards/commerce-sdk';

const agentcard = new AgentcardClient({
  clientId: process.env.AGENTCARD_CLIENT_ID ?? '',
  clientSecret: process.env.AGENTCARD_CLIENT_SECRET ?? '',
});

const turn = await agentcard.buy({ ask: 'two packs of AA batteries from Amazon', external_user_id: 'user-42', resume: true });
console.log(turn.status, turn.reply);
```

The call returns one `BuyTurn` for the shopper `user-42`. Most apps never call `buy` themselves: [`buyTool`](https://docs.agentcard.com/api-reference/commerce-sdk/buy-tool) calls it for the model. See [Quickstart](https://docs.agentcard.com/commerce-sdk/quickstart.md) for the full wiring.

## Create the client

```ts theme={null}
new AgentcardClient(options)
```

Pass your company's credentials, or one shopper's own access token, and not both. The constructor throws a `TypeError` for any other combination.

| Option | Type | Description |
| - | - | - |
| `clientId` | `string`, required with `clientSecret` | Your company's client ID. |
| `clientSecret` | `string`, required with `clientId` | Your company's client secret. |
| `accessToken` | `string`, required without credentials | One shopper's own Agentcard access token. The client then buys only for that shopper. |
| `baseUrl` | `string`, optional | Where Agentcard answers. Default `https://api.agentcard.sh`. A trailing slash is dropped. |
| `fetch` | `FetchLike`, optional | The fetch to call Agentcard with. Default: the global `fetch`. The constructor throws when the runtime has none. |

## Properties

| Property | Type | Description |
| - | - | - |
| `baseUrl` | `string` | The address the client calls. |
| `company` | `boolean` | `true` when the client buys for many shoppers with company credentials, `false` under one shopper's token. |
| `sandbox` | `boolean` or `null` | `true` for sandbox credentials and `false` for live credentials, as the token exchange said. `null` until the first exchange, and under a shopper's own token. |

## Methods

### `bearer`

```ts theme={null}
agentcard.bearer(refused?: string): Promise<string>
```

Returns the access token the client sends: the shopper's own, or your company's, exchanged once and renewed a minute before it expires. Pass a company token that Agentcard refused to get a new one. The first call also sets `sandbox`.

### `buy`

```ts theme={null}
agentcard.buy(body: BuyRequest, stream?: BuyStreamHandlers): Promise<BuyTurn>
```

Sends one purchase turn and waits up to `BUY_TURN_TIMEOUT_MS`. Name the shopper once, with `external_user_id` or `user_id`, when you use company credentials, and leave both out under a shopper's own token.

The client sends a turn once, because a turn runs against live stores. The client sends again only after a `401`, once, with a renewed token. After a `timeout`, `unreachable` or `invalid_answer` error, read the conversation with `conversation` before you send the turn again.

Pass `stream` with `onProgress`, `onCatalog` or both to ask Agentcard to stream the turn. A stream that breaks after the turn started returns a `BuyTurn` with `error_code` set to `turn_timeout`, so the caller reads the conversation for what the turn left.

| Parameter | Type | Description |
| - | - | - |
| `body` | `BuyRequest`, required | The request. See [BuyRequest](#buyrequest). |
| `stream.onProgress` | `(progress: BuyProgress) => void`, optional | Hears each stage the turn reaches: `searching`, `building_cart`, `checking_out` or `working`. |
| `stream.onCatalog` | `(update: BuyCatalogUpdate) => void`, optional | Hears the products a search found the moment the search answers. The callback may return a promise, and the client waits for it before it returns the turn. |

### `conversation`

```ts theme={null}
agentcard.conversation(conversationId: string, who?: Shopper): Promise<BuyConversation>
```

Reads where one conversation stands: whether a turn is running, the last checkout, the orders it placed and the carts on the table. Pass `who` with company credentials. The call waits up to 30 seconds.

### `mintVaultLink`

```ts theme={null}
agentcard.mintVaultLink(input: { externalUserId: string }): Promise<VaultLink>
```

Returns the Vault link a shopper opens to connect to your company and add a card. Agentcard returns the link it already opened for that shopper while the link has time left, so a shopper who asks twice gets one link. The method needs company credentials and throws a `TypeError` under a shopper's token.

### `listen`

```ts theme={null}
agentcard.listen(options: ListenOptions): AgentcardListener
```

Hears every event Agentcard sends your company over one connection your server opens. See [listen](https://docs.agentcard.com/api-reference/commerce-sdk/listen.md).

### `delivered`

```ts theme={null}
agentcard.delivered(externalUserId: string, message: ShopperMessage): void
```

Records that `message` reached the chat of the shopper you call `externalUserId`, so that shopper's next `buy.turn` hands it to your model. `listen` and `agentcardWebhook({ agentcard })` call it once `onMessage` returns, so call it yourself only for a message you delivered another way. The client keeps a message once per `eventId`, at most 20 messages per shopper, in this process's memory.

### `answered`

```ts theme={null}
agentcard.answered(externalUserId: string, message: ShopperMessage): void
```

Records that your agent answers `message` with a turn of its own, so the shopper never reads its `text`. `delivered` skips the message from then on, and no later turn tells the model its text. [`buy.nextStep`](https://docs.agentcard.com/api-reference/commerce-sdk/turn#next-step) calls it.

### `unanswered`

```ts theme={null}
agentcard.unanswered(externalUserId: string, message: ShopperMessage): void
```

Takes back `answered` for a message your agent's turn could not answer. After you post the message's text, `delivered` keeps it again. `turn.release()` calls it.

### `keptMessages`

```ts theme={null}
agentcard.keptMessages(externalUserId: string): ShopperMessage[]
```

Returns the messages kept for this shopper, oldest first: each one that reached the shopper's chat and that no history passed to `turn.messages` holds yet.

### `forgetMessages`

```ts theme={null}
agentcard.forgetMessages(externalUserId: string, messages: readonly ShopperMessage[]): void
```

Forgets these messages for this shopper. `turn.messages` calls it once the history it reads holds them.

## Constants

| Constant | Value | Description |
| - | - | - |
| `DEFAULT_BASE_URL` | `https://api.agentcard.sh` | Where the client calls when you pass no `baseUrl`. |
| `BUY_TURN_TIMEOUT_MS` | `130000` | The wait for one purchase turn. The wait runs past Agentcard's own 110-second budget, so a slow turn answers `turn_timeout` instead of aborting. |

## BuyRequest

The body of one `buy` call. Every field is optional.

| Field | Type | Description |
| - | - | - |
| `ask` | `string` | The shopper's words. |
| `confirm` | `string` or `true` | A cart hash from an earlier answer, or `true` for the cart Agentcard last answered with. |
| `resume` | `true` | Continues the shopper's newest open conversation instead of starting one. |
| `conversation_id` | `string` | The conversation that a confirm of a cart hash belongs to, in place of `resume`. |
| `new_order` | `true` | Drops the cart on the table and starts a new order. |
| `pick` | `{ product_id: string }` or `{ store_id: string }` | The product the shopper picked from the last search, or the store they picked from the last answer. Goes with an `ask`, never with `confirm` or `new_order`. |
| `payment_source` | `'vault'` | Pays from the shopper's Vault. |
| `delivery_address` | `BuyDeliveryAddress` | Where the order goes, when you already hold the address. |
| `external_user_id` | `string` | Your own id for the shopper. |
| `user_id` | `string` | Agentcard's user id for the shopper. |

`BuyDeliveryAddress` has the required fields `street`, `city`, `state` and `zip`, and the optional fields `address2`, `phone`, `name`, `country` and `can_leave_at_door` (`boolean`).

## BuyTurn

One turn's answer. Every field is present, and a field an older Agentcard does not send reads as `null`.

| Field | Type | Description |
| - | - | - |
| `conversation_id` | `string` | The conversation the turn belongs to. Empty when Agentcard named none. |
| `status` | `BuyStatus` | `needs_input`, `needs_approval`, `order_placed`, `partially_placed` or `declined`. A status Agentcard adds later reads as `needs_input`. |
| `reply` | `string` | Agentcard's answer for the shopper, links included. |
| `messages` | `string[]` | Agentcard's separate messages for the shopper. |
| `cart` | `BuyCart` or `null` | The most recently shown cart. |
| `carts` | `BuyCart[]` | Every cart on the table. |
| `catalog` | `BuyCatalog` or `null` | The last product search. |
| `order_id` | `string` or `null` | Set when this turn placed an order. |
| `payment_source` | `{ source, brand, last4 }` or `null` | How the order was paid. `brand` and `last4` are `string` or `null`. |
| `decline_code` | `string` or `null` | Why Agentcard did not buy. |
| `approval_url` | `string` or `null` | The page where the shopper approves the purchase. |
| `approval_expires_at` | `string` or `null` | When the approval link stops working, as an ISO 8601 timestamp. |
| `approval_in_progress` | `boolean` or `null` | Whether an approval is under way. |
| `charge_status` | `string` or `null` | Whether money moved. |
| `error_code` | `string` or `null` | A machine-readable failure of the turn, such as `turn_timeout`. |
| `authorization_id` | `string` or `null` | The checkout authorization for this turn. |
| `merchant_total_cents` | `number` or `null` | The store's own total, in the smallest unit of `merchant_currency`. |
| `merchant_currency` | `string` or `null` | The store's currency as a lower-case code. |
| `add_card_url` | `string` or `null` | The Vault link for a shopper who has no card. |
| `add_card_expires_at` | `string` or `null` | When that link stops working. |
| `placement_announced` | `boolean` or `null` | Whether Agentcard announced the placement. |
| `next_action` | `BuyNextAction` or `null` | The one step the shopper takes next. A value this version does not know reads as `null`. |
| `store_login_url` | `string` or `null` | The page where the shopper signs in to a store's own account. |
| `store_login_expires_at` | `string` or `null` | When that page stops working. |
| `store_login_merchant_name` | `string` or `null` | The store that page signs in to. |
| `pick` | `BuyPick` or `null` | The product this turn's `pick` named, and whether the cart holds it. |
| `stores` | `BuyStores` or `null` | The stores this answer's reply presents to choose from. |
| `store_pick` | `BuyStorePick` or `null` | The store this turn picked, and whether Agentcard selected it. |
| `cart_building` | `{ store, started_at }` or `null` | The store whose checkout is still building the cart. Both fields are `string`. |
| `raw` | `Record<string, unknown>` | The answer as Agentcard sent it. |

`BuyNextAction` is one of `shipping_details`, `guest_details`, `store_login`, `add_card`, `confirm`, `verification_code` or `approve`, in the order a purchase asks for them.

## BuyCart

A cart as Agentcard answers it. Every amount is in the smallest unit of `currency`.

| Field | Type | Description |
| - | - | - |
| `merchant` | `string` | Agentcard's id for the merchant. |
| `merchant_name` | `string` | The merchant's display name. |
| `store` | `{ id: string; name?: string }` or `null` | The store the cart is at. |
| `items` | `BuyCartLine[]` | The cart's lines. |
| `subtotalCents` | `number` or `null` | The items alone. |
| `serviceFeesCents` | `number` | The store's fees. |
| `merchantFeeLines` | `{ label: string; amountCents: number }[]` or `null` | The store's own fee and tax lines inside `serviceFeesCents`. |
| `tipCents` | `number` | The tip. |
| `totalCents` | `number` | The all-in total the shopper sees. |
| `totalIsEstimate` | `boolean` | `true` when the store has not priced fees and tax yet, so `totalCents` is an estimate. |
| `currency` | `string` or `null` | The ISO 4217 code the store prices the cart in. |
| `approvedCeilingCents` | `number` or `null` | The most a confirm can authorize. |
| `hash` | `string` | Identifies exactly this cart at this price. A confirm names it. |

### BuyCartLine

| Field | Type | Description |
| - | - | - |
| `name` | `string` | The line's name. |
| `qty` | `number` | The quantity. |
| `priceCents` | `number`, optional | One unit's price. |
| `lineTotalCents` | `number` or `null` | What the line costs with its add-ons and quantity. `null` when the price is unknown. |
| `options` | `string[]` | The choices made on the line, in the store's names. Empty when none. |
| `variant` | `string` or `null` | The variant the line is, in the store's name. |
| `product_id` | `string` or `null` | The product's id. |
| `ref` | `string` or `null` | The id of the product card this line is. A product whose `ref` or `id` equals it is in the cart. |
| `url` | `string` or `null` | The product's page, as an `http` or `https` address. |

## BuyCatalog

The products the last search found.

| Field | Type | Description |
| - | - | - |
| `merchant` | `string` | Agentcard's id for the merchant. |
| `merchant_name` | `string` | The merchant's display name. |
| `items` | `BuyCatalogItem[]` | The products. |
| `presented` | `boolean` | `true` when this answer's reply presents the products. |

### BuyCatalogItem

| Field | Type | Description |
| - | - | - |
| `id` | `string` | The item's id. |
| `ref` | `string` or `null` | The item's id with no link in it, which `pick.product_id` also takes. |
| `name` | `string` or `null` | The product's name. |
| `priceCents` | `number` or `null` | The price in the smallest unit of `currency`. |
| `price_from` | `true`, optional | Present when `priceCents` is the lowest price of a product priced by a required choice, such as a size. |
| `currency` | `string` or `null` | The ISO 4217 code the store prices the product in. |
| `image_url` | `string` or `null` | The product's main photo. |
| `image_urls` | `string[]` | Every photo, `image_url` first, at most eight. Only `https` addresses without credentials join the list. |
| `store` | `{ id: string; name?: string }` or `null` | The store that sells it. |
| `url` | `string` or `null` | The product's page at its store, as an `http` or `https` address. |
| `flight` | `BuyFlightItinerary`, optional | For a flight: `fareCabinClass` (`string` or `null`, optional) and `legs`, each with `origin`, `destination`, `departureAt`, `arrivalAt` and `cabinClasses`. |

### BuyPick, BuyStores and BuyStorePick

| Type | Fields |
| - | - |
| `BuyPick` | `product_id` (`string`), `ref` (`string` or `null`), `in_cart` (`boolean`). |
| `BuyStores` | `merchant` (`string`), `merchant_name` (`string`), `items` (`BuyStoreChoice[]`), `as_of` (`string` or `null`). |
| `BuyStoreChoice` | `id` (`string`, holds no link), `name` (`string`), `store_page_url` (`string` or `null`), `eta_minutes` (`number` or `null`), `image_url` (`string` or `null`). |
| `BuyStorePick` | `store_id` (`string`), `name` (`string` or `null`), `selected` (`boolean`). |

## BuyConversation

Where one conversation stands.

| Field | Type | Description |
| - | - | - |
| `conversation_id` | `string` | The conversation. |
| `status` | `'open'` or `'closed'` | Whether the conversation still takes turns. |
| `turn_in_progress` | `boolean` | `true` while Agentcard works on a turn. |
| `cart_building` | `{ store, started_at }` or `null` | The cart the store's checkout is still building. |
| `last_checkout` | `BuyLastCheckout` or `null` | The last checkout attempt. |
| `orders` | `BuyOrder[]` | The orders the conversation placed. |
| `carts` | `BuyCart[]` | The carts on the table. |
| `raw` | `Record<string, unknown>` | The answer as Agentcard sent it. |

`BuyOrder` has `order_id` (`string` or `null`), `merchant`, `merchant_name`, `status`, `total_cents` and `placed_at` (`string` or `null`). `BuyLastCheckout` has `at`, `merchant`, `merchant_name`, `status`, `code`, `message`, `order_id`, `decline_code`, `approval_url`, `approval_expires_at`, `placement_announced`, `approval_in_progress`, `add_card_url`, `add_card_expires_at` and `total_cents`. All of its fields except `at`, `merchant`, `merchant_name` and `status` are `null` when absent.

## VaultLink

| Field | Type | Description |
| - | - | - |
| `id` | `string` | The Vault session's id. |
| `url` | `string` | The page where the shopper connects and adds a card. |
| `expiresAt` | `string` or `null` | When the link stops working, as an ISO 8601 timestamp. |
| `reused` | `boolean` | `true` when Agentcard returned a link it had already opened for this shopper. |

## Shopper

Who a call is for. Name the shopper exactly once with company credentials, and leave both fields out under a shopper's own token.

| Field | Type | Description |
| - | - | - |
| `externalUserId` | `string`, optional | Your own id for the shopper. |
| `userId` | `string`, optional | Agentcard's user id for the shopper. |

## AgentcardApiError

The error every refusal and failed call throws. `AgentcardApiError` extends `Error`.

| Property | Type | Description |
| - | - | - |
| `status` | `number` | The HTTP status. `0` when Agentcard never answered. |
| `code` | `string` | Why the call failed. Branch on it and never on `message`. |
| `body` | `Record<string, unknown>` | The error body as Agentcard sent it. |
| `conversationId` | `string` or `null` | The conversation the refusal names. |
| `carts` | `BuyCart[]` | The carts as they stand, when the refusal names them. A changed cart's fresh hash is here. |

Through `buy.answer`, five refusals still answer the shopper: they come back as `needs_input` with the reason in `reply` and the code in `error_code`. They are `cart_changed`, `nothing_to_confirm`, `conversation_closed`, a delivery address that changed since the cart was built (a code that starts with `delivery_address`), and `turn_in_progress`. Every other refusal throws an `AgentcardApiError`.

### Error codes

| `code` | What it means | What to do |
| - | - | - |
| `external_user_id_ambiguous` | Two of your shoppers share the `externalUserId` you passed. | Give each shopper their own id. |
| `oauth_failed` | Agentcard refused your client credentials. | Check `clientId` and `clientSecret`. |
| `unauthorized` | Agentcard refused the access token. | Sign the shopper in again when the token was their own. |
| `timeout` | Agentcard did not answer in time, so whether the turn ran is unknown. A purchase turn waits 130 seconds. | Read the conversation with `agentcard.conversation(conversationId, { externalUserId })`. Send the turn again only once that read shows it did not finish. |
| `unreachable` | No answer came back. The request can still have reached Agentcard. | Act as for `timeout`. |
| `invalid_answer` | Agentcard answered with a body the client cannot read. | Act as for `timeout`. |
| `cart_changed` | The cart changed since its hash was issued. The error's `carts` holds the fresh cart. | Show the shopper the new total, then confirm the new hash. |
| `nothing_to_confirm` | A confirm arrived with no cart on the table. | Ask for a cart first. |
| `conversation_closed` | A confirm named a conversation that is closed. | Start a new order. |
| `conversation_not_found` | Agentcard has no conversation by that id. | Check the id, or start a new order. |
| `user_not_connected` | The shopper has not connected to your company. | Send the shopper a Vault link. The buy tool does this for you. |
| `turn_in_progress` | Agentcard is still working on an earlier request for this conversation. | Send the message again once the earlier request finishes. |
| `pick_not_found` | The `pick` names a product or store Agentcard does not hold. | Search again with the shopper's words. |
| `http_<status>` | Agentcard answered with a status that carries no code, such as `http_502`. | Retry later, or read the conversation first when the call was a turn. |

`buyTool` sends each purchase request once. Only a request that Agentcard refused with a `401` goes again, once, with a renewed token.


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