> ## 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.

# Showing products and carting the one the shopper picks

> Render a card for each product Agentcard finds, and cart the exact product the shopper taps.

The shopper asks for something, and your chat shows a card for each product Agentcard found, with its name, price, and picture. The shopper taps one, and Agentcard adds exactly that product to the cart, with no new search.

Your agent writes one short line beside the cards, such as which one the shopper wants. The cards carry the rest.

## Render a card for each product

Pass `sendLink` to `buy.turn`. Agentcard hands it one `product` link for each product a search finds, up to eight for each answer, and the turn waits for your callback before the model answers.

```ts chat.ts theme={null}
const turn = buyFor(shopper.id).turn(message, {
  sendLink: (link) => {
    if (link.kind === 'product') return sendProductCard(shopper.id, link); // your code
    return deliverStepLink(shopper.id, link); // Vault, approval, store sign-in: see "Sending each link"
  },
});
```

A `product` link carries these fields:

| Field | Type | What it holds |
| - | - | - |
| `title` | string | The product's name. |
| `price` | string or null | The price the shopper reads, such as `$12.99`, or `from $10.69` for a product priced by a choice the shopper makes, such as a burger's patty count. |
| `url` | string or null | The product's page at its store. Null for a flight. |
| `imageUrl` | string or null | The product's picture, or null when the store sent none. |
| `images` | string\[] | Every photo of the product with `imageUrl` first, at most eight. One entry for one photo, none for none. |
| `productId` | string | Agentcard's id for the product. It holds no link. |
| `action` | `buy`, `reserve`, or `book` | What taking the product does. |

Show the card with the picture, the title, the price, and a button. Use `action` to word the button: `reserve` for a restaurant table, `book` for a flight, room, class, or ticket, and `buy` for anything else, so the button says Reserve instead of Buy this. When `images` holds more than one entry, show the others as thumbnails that swap the main picture.

Every price the SDK writes uses the currency Agentcard names for the product, in that currency's own minor units. The amount `1331` reads `$13.31` in USD, `CA$13.31` in CAD, and `¥1,331` in JPY. A price in a currency Agentcard does not name shows with no symbol.

The model never reads a link. It reads `product_links_sent`, the number of cards the shopper holds, and its description tells it to write one short line instead of listing the products again. A shopper who asks for links hears that the links are there.

### Show a flight

A flight has no page at a store, so each flight is a `product` link whose `url` is null. The airline, the route, and its times are in `title`. Show it as a card with no link to open, and hand its `productId` back when the shopper picks it.

### Handle a product link that fails to send

A `sendLink` that throws on a product link counts that product as not sent, and the call goes on. The model reads `link_sent: false` on that product. A throw on a Vault or approval link makes the call throw too.

## Cart the one the shopper picks

Each product in the model's view carries a `product_id`, Agentcard's id for it with no link in it. When the shopper picks a product by its name, its place in the list, or a tap on its card, the model passes that `product_id`, and Agentcard adds exactly that product from the products it showed.

Your app sends no id of its own. A Buy this button that sends the shopper's words is enough:

```text theme={null}
i'll take the KODAK AA Alkaline Batteries (24 Pack) ($13.49)
```

The model reads the `product_id` beside that name in its last result, so keep every tool result in the model's history, as a tool runner does. Without the history, the model no longer holds the ids on the shopper's next message.

Your own code can pick too. A card's button can call `buy.answer` with the link's `productId`, and Agentcard adds that product:

```ts cart-button.ts theme={null}
const result = await buyFor(shopper.id).answer({
  ask: link.title ?? '',
  confirm: false,
  new_order: false,
  product_id: link.productId, // from the product link, never typed by the shopper
});
```

`result.pick` says what happened: the product's `product_id`, and `in_cart`, whether the cart now holds it. The model reads the same `pick` in its view. Show the cart from `result.cart`, as "Showing the cart" describes.

### Handle a product that Agentcard no longer holds

Agentcard keeps a search's products for 15 minutes, and a newer search replaces them. A pick of a product Agentcard no longer holds adds nothing, and the model reads that it should search again with the shopper's words.

### Handle a pick that is not in the cart

When `pick.in_cart` is false, the cart does not hold the product. The model tells the shopper what Agentcard said, and it asks for no card and no yes for an older cart.

## Check it in the sandbox

1. Send "two packs of AA batteries from Amazon" to your agent as a test shopper.
2. Check that product cards arrive, each with a name, a price, and a picture, and that the agent's reply is one short line that does not list the products again.
3. Tap a card, or send its title. Check that the cart holds that product and the chat ran no new search.
4. Wait more than 15 minutes, tap a card from the old search, and check that the agent searches again instead of adding a product.

The sandbox searches real stores and stops before payment, so no order is placed. In the sandbox, a connected shopper needs the Vault code `111111`, as "Adding a card for a new shopper" shows.


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