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

# Quickstart

> Install the Commerce SDK, give your agent the buy tool, store cards and make payments online.

Give your agent one tool that buys for your shoppers at real stores. The shopper tells your agent what they want, Agentcard finds it and builds the cart, and Agentcard places the order once the shopper approves it. Your server hears about each step, so your agent can tell the shopper in its own voice.

This page takes you from install to a sandbox run, then to live purchases. You write four things: one client, one tool for each shopper, one turn for each message, and one place to hear from Agentcard.

What you end up with: a shopper texts your agent "two packs of AA batteries from Amazon", sees product cards, picks one, adds a card with a link, approves the payment with a passkey, and reads "order placed" in the same chat, with nothing else to type.

## Install the SDK

```bash theme={null}
npm install @agent-cards/commerce-sdk
```

The package runs on Node 18 or later and has no dependencies.

## Get sandbox credentials

Open the Agentcard dashboard, go to Commerce SDK, and choose Start implementing. Step 1 shows your sandbox client ID and secret, labeled Sandbox. Save them as environment variables on your server:

```text .env.local theme={null}
AGENTCARD_CLIENT_ID=your_client_id
AGENTCARD_CLIENT_SECRET=your_client_secret
```

Read both from your app's secret store, and never hard-code them, log them, or commit them. Sandbox credentials run the whole flow against real stores and stop before payment, so no order is placed and nobody is charged. In the sandbox, the Vault code that connects a test shopper is always `111111`.

> [!NOTE]
>
> If you work with a coding agent such as Claude Code, Cursor, or Codex, step 2 of Start implementing gives you instructions to paste into it. The agent installs the SDK, wires the buy tool into your agent, and checks the result in the sandbox.

## Give your agent the buy tool

Build one `AgentcardClient` when your server starts and keep it. Build one buy tool for each shopper, name the shopper by your own id for them, and keep that tool too. Open one turn for each message the shopper sends, and give the turn to your model in place of the tool.

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

// One client for the whole server: every shopper's tool shares its access token.
export const agentcard = new AgentcardClient({
  clientId: process.env.AGENTCARD_CLIENT_ID ?? '',
  clientSecret: process.env.AGENTCARD_CLIENT_SECRET ?? '',
});

const tools = new Map<string, BuyTool>();

/** The buy tool for one shopper, built on the first message and kept for the rest. */
export function buyFor(shopperId: string): BuyTool {
  let buy = tools.get(shopperId);
  if (!buy) {
    buy = buyTool({ agentcard, externalUserId: shopperId }); // your own id for the shopper
    tools.set(shopperId, buy);
  }
  return buy;
}
```

Give each shopper their own `externalUserId`. Agentcard refuses a call when two of your shoppers share one id.

Each turn needs a way to deliver links to the shopper. Pass `sendLink`, and the turn hands it every Vault, approval, store sign-in, product, and store link, so your model never reads or writes one. "Send each link" shows how to render each kind.

The tab for your model API shows the same turn in each shape. Pick the one your agent already uses.

**Anthropic**

```ts chat.ts theme={null}
import Anthropic from '@anthropic-ai/sdk';
import { buyFor } from './agentcard';

const anthropic = new Anthropic();
const histories = new Map<string, any[]>(); // each shopper's earlier messages

export async function answer(shopperId: string, message: string) {
  const turn = buyFor(shopperId).turn(message, {
    sendLink: (link) => deliverLink(shopperId, link), // your code
  });
  const runner = anthropic.beta.messages.toolRunner({
    model: 'claude-opus-5-5',
    max_tokens: 16000,
    tools: [turn],
    messages: turn.messages(histories.get(shopperId)),
  });
  const final = await runner;
  histories.set(shopperId, runner.params.messages); // the next turn starts from here
  return final;
}
```

**Vercel AI SDK**

```ts chat.ts theme={null}
import { generateText, isStepCount } from 'ai';
import { buyFor } from './agentcard';

const histories = new Map<string, any[]>(); // each shopper's earlier messages

export async function answer(shopperId: string, message: string) {
  const turn = buyFor(shopperId).turn(message, {
    sendLink: (link) => deliverLink(shopperId, link), // your code
  });
  const messages = turn.messages(histories.get(shopperId));
  const result = await generateText({
    model: yourModel, // the model you already use
    tools: { buy: turn },
    stopWhen: isStepCount(10), // lets the model read the tool's answer and reply
    messages,
  });
  histories.set(shopperId, [...messages, ...result.responseMessages]); // the next turn starts from here
  return result;
}
```

**OpenAI or OpenRouter**

```ts chat.ts theme={null}
import OpenAI from 'openai';
import { buyFor } from './agentcard';

const openrouter = new OpenAI({ baseURL: 'https://openrouter.ai/api/v1', apiKey: process.env.OPENROUTER_API_KEY });
const histories = new Map<string, OpenAI.ChatCompletionMessageParam[]>(); // each shopper's earlier messages

export async function answer(shopperId: string, message: string) {
  const turn = buyFor(shopperId).turn(message, {
    sendLink: (link) => deliverLink(shopperId, link), // your code
  });
  const messages = turn.messages(histories.get(shopperId)) as OpenAI.ChatCompletionMessageParam[];
  const send = async () =>
    (await openrouter.chat.completions.create({ model: 'anthropic/claude-opus-5.5', messages, tools: [turn.openai] })).choices[0].message;

  let reply = await send();
  while (reply.tool_calls?.length) {
    messages.push(reply);
    for (const call of reply.tool_calls) {
      if (call.type !== 'function') continue;
      // turn.handle returns the JSON the model reads, with every link taken out.
      const content = await turn.handle(call.function.arguments).catch((error) => error.message);
      messages.push({ role: 'tool', tool_call_id: call.id, content });
    }
    reply = await send();
  }
  histories.set(shopperId, [...messages, reply]); // the next turn starts from here
  return reply.content; // the model's answer for the shopper
}
```

`deliverLink` and `yourModel` stand for your own code. The model calls `buy` with the shopper's words, shows the shopper the cart, and confirms the cart only after the shopper says yes. OpenRouter's agent SDK takes the tool through `toOpenRouterTool(turn, { tool, z })`, as the [`buyTool` reference](https://docs.agentcard.com/api-reference/commerce-sdk/buy-tool) shows.

Pass `deliveryAddress` to `buyTool` when you already hold the shopper's address. The order goes there, and Agentcard never asks the shopper for it. The model can change neither the address nor the shopper.

## Check it in the sandbox

Run the scripted walkthrough with your sandbox credentials. It connects a test shopper with the code `111111`, asks for a restaurant table, picks one, and confirms it, with no model in the loop.

```bash theme={null}
AGENTCARD_CLIENT_ID=your_client_id AGENTCARD_CLIENT_SECRET=your_client_secret node node_modules/@agent-cards/commerce-sdk/examples/walkthrough.mjs
```

The sandbox refuses the booking itself, so nothing is booked or charged. The last answer is `declined` with `decline_code: sandbox_unavailable`, and the run ends with:

```text theme={null}
walkthrough passed
```

The walkthrough then asks as a shopper nobody connected, twice, and checks that Agentcard returns the same Vault link both times. It refuses live credentials before it connects anybody.

Then try your own agent with a test shopper. Send "two packs of AA batteries from Amazon" and watch for product cards, a short reply, and a cart. Start implementing, step 3, shows the first search the moment it arrives.

The package also ships `examples/anthropic.mjs` and `examples/openrouter.mjs`, which run the buy tool as a chat in your terminal and print the links your app would send. Each guide in this section ends with a check you run in the sandbox.

## Hear from Agentcard

Agentcard tells your server about every moment a shopper should hear about, even when the shopper is not chatting: a card saved, an approval about to expire, an order placed, a delivery on its way. The SDK hands you each one as a message with `text` ready to post into that shopper's chat. Pick the way your host runs.

### Listen from a long-running server

Call `listen` once when your server starts, on the same `AgentcardClient` your tools use. Your server opens one connection to Agentcard, so you host no public URL and check no signature.

```ts server.ts theme={null}
import { agentcard } from './agentcard';

agentcard.listen({
  onMessage: (shopperId, message) => postToChat(shopperId, message.text), // your code
  onEvent: (event) => console.log(event.type, event.data),
});
```

`onMessage` gets each message under the `externalUserId` you gave the buy tool. `onEvent` gets every other event Agentcard sends your company, such as `order.placed`. The listener reconnects after a dropped connection and resumes after the last event it handled. Run one listener for each company and mode. The [`listen` reference](https://docs.agentcard.com/api-reference/commerce-sdk/listen) lists the options, including a `cursor` store that resumes a restarted server where the last process stopped.

### Take events at a webhook endpoint on serverless

A host that ends each request after a few seconds cannot keep a connection open. Mount `agentcardWebhook` on a route instead. The handler checks each delivery's signature, skips an event it already handled, and calls `onMessage` with your own id for the shopper.

```ts app/api/agentcard/route.ts theme={null}
import { agentcardWebhook, stripLinks } from '@agent-cards/commerce-sdk';

const webhook = agentcardWebhook({
  secret: process.env.AGENTCARD_WEBHOOK_SECRET ?? '', // returned once, when you create the endpoint
  onMessage: async (shopperId, message) => {
    await postToChat(shopperId, message.text); // your code
    await saveAssistantMessage(shopperId, stripLinks(message.text)); // your code: the model reads it from the history
  },
});

export const POST = webhook.fetch;
```

Create the webhook endpoint with [`POST /api/v2/webhook_endpoints`](https://docs.agentcard.com/api-reference/webhook-endpoints/create), and subscribe it to `purchase.message`, `order.confirmed`, and `order.updated` by name. A catch-all `*` alone does not turn `purchase.message` on. The handler answers `200` once `onMessage` returns and `500` when it throws, and Agentcard then delivers the event again, so a failed post is not lost. In an Express route, mount the same handler with `express.raw({ type: 'application/json' })` before any `express.json()`. The [webhook handler reference](https://docs.agentcard.com/api-reference/commerce-sdk/webhook-handler.md) shows both.

### Let your model read what the shopper read

Between turns, Agentcard posts messages such as "Your Visa ending 3132 is saved" into the shopper's chat. Your model has to read them too, or it answers "ok lets checkout" with "did you save your card?". Pass `turn.messages(history)` as the model's messages, as the code above does, and the turn adds each message the history does not hold yet. "Take the next step once the shopper finishes outside the chat" shows how your agent moves on without waiting for the shopper to say "ok".

The client keeps each message in the process that received it, so a server that runs `listen` beside your model hands them to the next turn on its own. A webhook endpoint on serverless answers in another process than your chat route, so write each message into the shopper's history as an assistant message, as the handler above does, and your model reads it from there. Post the message's own text to the shopper, and save `stripLinks(message.text)` for the model, so the model never repeats a link that has since expired. The buy tool hands the shopper a current one.

The order events your company receives are documented in [Order events](https://docs.agentcard.com/webhooks/orders/overview.md): `order.placed`, `order.confirmed`, `order.failed`, and `order.updated`.

## See what your agent can buy

The buy tool covers four kinds of purchase, and the model picks the right one from the shopper's words.

| The shopper asks for | What happens |
| - | - |
| Products from online stores | Agentcard searches the stores, shows product cards, and builds the cart for the one the shopper picks. |
| Food delivery | Agentcard shows the stores near the shopper, then the store's menu, and the shopper signs in to the store's own account when the store needs it. |
| A table reservation | Agentcard finds open tables and books the one the shopper picks. A table costs nothing. |
| A flight | Agentcard shows each flight as a card with no page to open, and books the one the shopper picks. |

[Agentcard's Commerce SDK](https://docs.agentcard.com/vault/integrations/ecommerce-apis/purchase-api.md) lists the stores. Each guide in this section covers one moment of the shopper's experience, from the product cards to the approval.

## Go live

Switch from sandbox to live in two steps. The code stays the same.

1. Open Settings, Developers, Credentials in the dashboard, switch to Live, and register your production app the first time. Copy the production client ID and secret into `AGENTCARD_CLIENT_ID` and `AGENTCARD_CLIENT_SECRET`. A webhook endpoint belongs to a mode, so create a live endpoint for the live credentials and use its secret.
2. Open Commerce SDK, Balance in the dashboard and choose how to pay the fee. Agentcard takes 1% of each live purchase. With prefund, you add money to a balance and each fee comes out of it. With pay as you go, you save a card and each fee is charged to it as the purchase completes. Fees too small to charge wait and are charged together with the next one.

Choose before your agent makes its first live purchase. A prefunded balance that reaches zero, or a card that declines, pauses live purchases until you add funds or a card that works. Sandbox purchases are never billed.

Live credentials place real orders and charge real cards. Test the full flow with your own card first, and keep the sandbox credentials for your tests.

## Keep going

> **[Showing products and carting the one the shopper picks](https://docs.agentcard.com/commerce-sdk/guides/showing-products.md)**
>
> Render product cards and cart the exact product the shopper taps.

> **[Sending each link: approval, Vault and store sign-in](https://docs.agentcard.com/commerce-sdk/guides/sending-each-link.md)**
>
> Deliver every link to the shopper while the model never reads one.

> **[Approving a purchase](https://docs.agentcard.com/commerce-sdk/guides/approving-a-purchase.md)**
>
> Send the approval link and place the order only after the shopper says yes.

> **[Commerce SDK reference](https://docs.agentcard.com/api-reference/commerce-sdk/overview.md)**
>
> Every export, option, and message type.


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