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

# Your first order in sandbox

> Run the whole /buy loop with no real money: a real conversation, a real cart with a hash, and the confirm sandbox stops by design.

By the end of this page you'll have run the whole [Purchase API](/purchase/purchase-api) loop against a real merchant: a real conversation, a real cart with an exact total and a hash, and the hash-bound confirm. The one thing sandbox refuses to do is the money: the final confirm comes back `declined` by design, because sandbox test cards can never pay a real merchant. That refusal is the last lesson of this page, and it's the only difference from production, where the same confirm places the order.

## Prerequisites

* A sandbox API key (`sk_test_`) from the [dashboard](https://app.agentcard.sh) Credentials page
* `curl`, Node 18+, or Python 3.8+ with `requests`, whichever you prefer

<Steps>
  <Step title="Create a cardholder">
    A cardholder is the end user your agent buys for. In sandbox you can invent one:

    <CodeGroup>
      ```bash cURL theme={null}
      curl -X POST https://api.agentcard.sh/api/v1/cardholders \
        -H "Authorization: Bearer $SANDBOX_API_KEY" \
        -H "Content-Type: application/json" \
        -d '{"firstName": "Ada", "lastName": "Lovelace", "email": "ada@example.com"}'
      ```

      ```javascript Node theme={null}
      const res = await fetch("https://api.agentcard.sh/api/v1/cardholders", {
        method: "POST",
        headers: {
          Authorization: `Bearer ${sandboxApiKey}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify({ firstName: "Ada", lastName: "Lovelace", email: "ada@example.com" }),
      });
      const cardholder = await res.json();
      ```

      ```python Python theme={null}
      import requests

      res = requests.post(
          "https://api.agentcard.sh/api/v1/cardholders",
          headers={"Authorization": f"Bearer {sandbox_api_key}"},
          json={"firstName": "Ada", "lastName": "Lovelace", "email": "ada@example.com"},
      )
      cardholder = res.json()
      ```
    </CodeGroup>

    The API returns the new cardholder. Save the `id`; the next step needs it.

    ```json Output theme={null}
    {
      "id": "cmemw41xk0003cs01x9d2h4qf",
      "firstName": "Ada",
      "lastName": "Lovelace",
      "email": "ada@example.com",
      ...
    }
    ```
  </Step>

  <Step title="Mint a buy token">
    The buy token is the bearer your agent uses. It lasts 30 days and only works as this one user:

    <CodeGroup>
      ```bash cURL theme={null}
      curl -X POST https://api.agentcard.sh/api/v1/cardholders/CARDHOLDER_ID/buy_token \
        -H "Authorization: Bearer $SANDBOX_API_KEY"
      ```

      ```javascript Node theme={null}
      const res = await fetch(
        `https://api.agentcard.sh/api/v1/cardholders/${cardholder.id}/buy_token`,
        { method: "POST", headers: { Authorization: `Bearer ${sandboxApiKey}` } },
      );
      const { buy_token: buyToken } = await res.json();
      ```

      ```python Python theme={null}
      res = requests.post(
          f"https://api.agentcard.sh/api/v1/cardholders/{cardholder['id']}/buy_token",
          headers={"Authorization": f"Bearer {sandbox_api_key}"},
      )
      buy_token = res.json()["buy_token"]
      ```
    </CodeGroup>

    The token comes back with its expiry:

    ```json Output theme={null}
    {
      "agentcard_user_id": "cmemw41xk0003cs01x9d2h4qf",
      "buy_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
      "expires_at": "2026-09-16T18:04:11.000Z"
    }
    ```

    If you're following along in a shell, export it so the next calls can use it:

    ```bash theme={null}
    export BUY_TOKEN="<the buy_token from the response>"
    ```
  </Step>

  <Step title="Ask for something">
    Send the request in plain language. The agent searches the merchant, may ask you to choose between options, and stops each turn at `needs_input` until it has enough to build a cart:

    <CodeGroup>
      ```bash cURL theme={null}
      curl -X POST https://api.agentcard.sh/buy \
        -H "Authorization: Bearer $BUY_TOKEN" \
        -H "Content-Type: application/json" \
        -d '{"ask": "a phone charger from Amazon, ship it to 548 Market St, San Francisco, CA 94104"}'
      ```

      ```javascript Node theme={null}
      const res = await fetch("https://api.agentcard.sh/buy", {
        method: "POST",
        headers: {
          Authorization: `Bearer ${buyToken}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify({ ask: "a phone charger from Amazon, ship it to 548 Market St, San Francisco, CA 94104" }),
      });
      const turn = await res.json();
      ```

      ```python Python theme={null}
      res = requests.post(
          "https://api.agentcard.sh/buy",
          headers={"Authorization": f"Bearer {buy_token}"},
          json={"ask": "a phone charger from Amazon, ship it to 548 Market St, San Francisco, CA 94104"},
      )
      turn = res.json()
      ```
    </CodeGroup>

    The first turn usually asks a question. Yours will differ; sandbox conversations are live, not scripted:

    ```json Output theme={null}
    {
      "conversation_id": "cmemw5k2p00b7",
      "status": "needs_input",
      "reply": "I found a few options at Amazon. The Anker 30W USB-C charger is $15.99 and can arrive tomorrow. Want that one, or should I list alternatives?",
      "cart": null,
      ...
    }
    ```

    Answer by sending your reply back with the `conversation_id`:

    <CodeGroup>
      ```bash cURL theme={null}
      curl -X POST https://api.agentcard.sh/buy \
        -H "Authorization: Bearer $BUY_TOKEN" \
        -H "Content-Type: application/json" \
        -d '{"conversation_id": "CONVERSATION_ID", "ask": "the first one is fine"}'
      ```

      ```javascript Node theme={null}
      const res = await fetch("https://api.agentcard.sh/buy", {
        method: "POST",
        headers: {
          Authorization: `Bearer ${buyToken}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify({ conversation_id: turn.conversation_id, ask: "the first one is fine" }),
      });
      const next = await res.json();
      ```

      ```python Python theme={null}
      res = requests.post(
          "https://api.agentcard.sh/buy",
          headers={"Authorization": f"Bearer {buy_token}"},
          json={"conversation_id": turn["conversation_id"], "ask": "the first one is fine"},
      )
      turn = res.json()
      ```
    </CodeGroup>

    Keep going until the response carries a `cart` with a `totalCents` and a `hash`. That's the agent showing you the exact price before anything happens:

    ```json Output theme={null}
    {
      "conversation_id": "cmemw5k2p00b7",
      "status": "needs_input",
      "reply": "Anker 30W USB-C charger, $15.99. With the $0.40 service fee the total is $16.39, shipping to 548 Market St. Want me to place it?",
      "cart": {
        "merchant": "retail",
        "merchant_name": "Amazon",
        "items": [
          { "name": "Anker 30W USB-C Charger", "qty": 1, "priceCents": 1599 }
        ],
        "serviceFeesCents": 40,
        "tipCents": 0,
        "totalCents": 1639,
        "hash": "c47a91e02b3d5f68"
      },
      "error_code": null
    }
    ```
  </Step>

  <Step title="Confirm, and watch sandbox stop the money">
    Send the cart's `hash` back as `confirm`. Echoing the hash, not the word "yes", is what authorizes exactly this cart at exactly this price:

    <CodeGroup>
      ```bash cURL theme={null}
      curl -X POST https://api.agentcard.sh/buy \
        -H "Authorization: Bearer $BUY_TOKEN" \
        -H "Content-Type: application/json" \
        -d '{"conversation_id": "CONVERSATION_ID", "confirm": "HASH_FROM_THE_CART"}'
      ```

      ```javascript Node theme={null}
      const res = await fetch("https://api.agentcard.sh/buy", {
        method: "POST",
        headers: {
          Authorization: `Bearer ${buyToken}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify({ conversation_id: turn.conversation_id, confirm: turn.cart.hash }),
      });
      const receipt = await res.json();
      ```

      ```python Python theme={null}
      res = requests.post(
          "https://api.agentcard.sh/buy",
          headers={"Authorization": f"Bearer {buy_token}"},
          json={"conversation_id": turn["conversation_id"], "confirm": turn["cart"]["hash"]},
      )
      receipt = res.json()
      ```
    </CodeGroup>

    The hash verifies, the confirm gate passes, and then sandbox refuses the one thing it exists to refuse. Before any money is reserved or any card is created, the checkout denies with the sandbox wall:

    ```json Output theme={null}
    {
      "conversation_id": "cmemw5k2p00b7",
      "status": "declined",
      "reply": "This connection is in sandbox mode, and sandbox test cards cannot pay real merchants, so a live Amazon order can't be placed. To place real orders, connect with a production-mode client.",
      "cart": null,
      ...
    }
    ```

    <Check>
      That `declined` is the finish line, not a failure. You exercised the entire conversational contract: an ask, a follow-up, a cart with an exact total, and a hash-bound confirm that verified before anything moved. On a production connection, this exact same call is the one that places the order and returns the receipt.
    </Check>
  </Step>
</Steps>

## What you just exercised

Everything up to the money: a conversation that built a real cart at a real merchant, a total shown before any charge, and a hash-bound confirmation that authorized exactly that cart. Sandbox stops at the payment on purpose, because its test cards can never charge a real merchant, so no sandbox call can ever place an order. In production the same confirm reserves against the user's budget, issues a one-time card, pays the merchant, and returns the receipt.

## Where to go next

* [Purchase API](/purchase/purchase-api): the full envelope, multi-merchant confirms, and the payment layer.
* [POST /buy reference](/companies/api/reference/buy): the exact request and response contract.
* [Test in sandbox](/ship/test-in-sandbox): every sandbox knob, from test cards and simulated KYC to `test_charge` and webhook rehearsal.
