# Create an order token

> Exchange your cart for the order token that Bolt Checkout requires.

Source: https://help.boltapp.com/custom-cart/create-order-token

Bolt Checkout will not open without an order token. Your backend creates one by sending the current cart to `POST /v1/merchant/orders`, then returns only the token to your web or mobile client.

<Warning>
  This request is server-side only because it uses your secret API key. Never call the Bolt endpoint directly from browser or mobile application code.
</Warning>

If your backend uses Python, the beta [Bolt Python SDK](https://github.com/BoltApp/Bolt-Python-SDK/blob/main/docs/sdks/orders/README.md#create_order_token) wraps this endpoint as `orders.create_order_token()`. Pin the package version. The TypeScript, Go, and C# generated SDKs target `/v3/orders`, which does not create this Custom Cart token; use the REST request below for those languages.

## Request [#request]

Authenticate with your API key in the `X-API-Key` header. The TypeScript and JavaScript examples run in a server-side Node.js environment. Amounts are integers in the currency's minor units, so `41200` is $412.00.

<CodeGroup>
  <CodeBlockTabs defaultValue="TypeScript">
    <CodeBlockTabsList>
      <CodeBlockTabsTrigger value="TypeScript">
        TypeScript
      </CodeBlockTabsTrigger>

      <CodeBlockTabsTrigger value="JavaScript">
        JavaScript
      </CodeBlockTabsTrigger>

      <CodeBlockTabsTrigger value="cURL">
        cURL
      </CodeBlockTabsTrigger>
    </CodeBlockTabsList>

    <CodeBlockTab value="TypeScript">
      ```typescript
      type OrderTokenResponse = { token: string };

      const baseUrl = process.env.BOLT_API_BASE_URL ?? "https://api-sandbox.boltapp.com";
      const apiKey = process.env.BOLT_API_KEY;
      if (!apiKey) throw new Error("Missing BOLT_API_KEY");

      const response = await fetch(`${baseUrl}/v1/merchant/orders`, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          "X-API-Key": apiKey,
        },
        body: JSON.stringify({
          cart: {
            order_reference: "order_100",
            display_id: "displayid_100",
            currency: "USD",
            total_amount: 41200,
            tax_amount: 900,
            items: [
              {
                name: "Bolt Coffee Mug",
                reference: "sku-mug-01",
                sku: "MUG-01",
                unit_price: 20150,
                total_amount: 40300,
                quantity: 2,
                image_url: "https://example.com/img/mug.png",
              },
            ],
          },
          channel: "browser",
        }),
      });

      if (!response.ok) {
        throw new Error(`Order token request failed: ${response.status} ${await response.text()}`);
      }

      const { token } = (await response.json()) as OrderTokenResponse;
      ```
    </CodeBlockTab>

    <CodeBlockTab value="JavaScript">
      ```javascript
      const baseUrl = process.env.BOLT_API_BASE_URL ?? "https://api-sandbox.boltapp.com";
      const apiKey = process.env.BOLT_API_KEY;
      if (!apiKey) throw new Error("Missing BOLT_API_KEY");

      const response = await fetch(`${baseUrl}/v1/merchant/orders`, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          "X-API-Key": apiKey,
        },
        body: JSON.stringify({
          cart: {
            order_reference: "order_100",
            display_id: "displayid_100",
            currency: "USD",
            total_amount: 41200,
            tax_amount: 900,
            items: [
              {
                name: "Bolt Coffee Mug",
                reference: "sku-mug-01",
                sku: "MUG-01",
                unit_price: 20150,
                total_amount: 40300,
                quantity: 2,
                image_url: "https://example.com/img/mug.png",
              },
            ],
          },
          channel: "browser",
        }),
      });

      if (!response.ok) {
        throw new Error(`Order token request failed: ${response.status} ${await response.text()}`);
      }

      const { token } = await response.json();
      ```
    </CodeBlockTab>

    <CodeBlockTab value="cURL">
      ```bash
      curl -X POST https://api-sandbox.boltapp.com/v1/merchant/orders \
        -H "Content-Type: application/json" \
        -H "X-API-Key: $BOLT_API_KEY" \
        -d '{
          "cart": {
            "order_reference": "order_100",
            "display_id": "displayid_100",
            "currency": "USD",
            "total_amount": 41200,
            "tax_amount": 900,
            "items": [
              {
                "name": "Bolt Coffee Mug",
                "reference": "sku-mug-01",
                "sku": "MUG-01",
                "unit_price": 20150,
                "total_amount": 40300,
                "quantity": 2,
                "image_url": "https://example.com/img/mug.png"
              }
            ]
          },
          "channel": "browser"
        }'
      ```
    </CodeBlockTab>
  </CodeBlockTabs>
</CodeGroup>

`cart.total_amount` is the amount the shopper will be charged. Bolt does not derive it for you, and it must match the sum of the other cart amounts, which [How the amounts are validated](#how-the-amounts-are-validated) sets out exactly. In this example, the items total `40300` and tax is `900`, so the cart total is `41200`.

### Required on every request [#required-on-every-request]

| Field                  | Notes                                                                    |
| ---------------------- | ------------------------------------------------------------------------ |
| `channel`              | Where the order originated: `browser` or `mobile_app`                    |
| `cart.order_reference` | Your identifier for the order. Must be unique per successful transaction |
| `cart.currency`        | Three letter ISO code, for example `USD`                                 |
| `cart.total_amount`    | Total to charge, in minor units                                          |

`cart.display_id` is optional. If you omit it, Bolt uses `cart.order_reference` as the display ID. Send a separate value only when the order identifier shown to shoppers or support teams should differ from your order reference.

When you send `cart.items`, send these fields on each item:

| Field                                 | Notes                                                            |
| ------------------------------------- | ---------------------------------------------------------------- |
| `cart.items[].reference`              | Your identifier for the line item                                |
| `cart.items[].name`                   | Shopper-facing item name                                         |
| `cart.items[].sku`                    | Your stock identifier for the item                               |
| `cart.items[].total_amount`           | The line total. This is the figure that has to add up, see below |
| `cart.items[].unit_price`, `quantity` | Price per unit and quantity. Not checked against `total_amount`  |

The API contract marks all six required. Today only a missing `name` is actually rejected, so the others can be omitted without an error, but do not build on that: they are required by the contract and the other consumers described below read them.

### How the amounts are validated [#how-the-amounts-are-validated]

Bolt checks the cart total against the sum of everything you sent, and for **line items** it uses each item's `total_amount`. It never multiplies a line item's `unit_price` by its `quantity`.

So there is one arithmetic rule to satisfy:

```text title="Validation rule"
  sum(items[].total_amount)
+ sum(items[].gift_option.cost)
+ shipments + in_store_cart_shipments
+ fees
+ tax
- discounts
- gift cards
== cart.total_amount
```

`cart.fees[]` are the exception to the no-multiplication rule: each one contributes `(unit_price + unit_tax_amount) * quantity`, because a fee carries no `total_amount` of its own.

Get that wrong and the request is rejected before any money moves: HTTP `422` with error code `26` on field `cart.total_amount`, whose message ends with `cart provided amount X does not match computed amount Y`. It can arrive alongside other validation errors in the same `errors` array, so read the array rather than only its first entry.

There is one case, applying to every merchant, where the sum is allowed not to match. A cart whose discounts or gift cards come to more than its contents may send `cart.total_amount` of `0`, even though the arithmetic above produces a negative number. A fully gift-carded order is the usual way to reach this.

A line item whose `unit_price` times `quantity` does not equal its `total_amount` is accepted without error or warning, and the shopper is charged your `cart.total_amount` either way.

<Warning>
  Do not read that as permission to leave `unit_price` inconsistent. Bolt forwards it to several consumers that do their own arithmetic, Klarna and the PayPal breakdown among them, and none of their failures points back at your cart. Two are worth knowing about in detail.

  **Level 3 card data recalculates the line total** as `unit_price` times `quantity`. An inconsistent `unit_price` therefore makes the Level 3 breakdown disagree with the amount actually charged, and because item tax and discount amounts are apportioned from that same figure, they inherit the error.

  **PayPal line item breakdowns** are patched only when the discrepancy is under 100 minor units, where Bolt inserts a synthetic adjustment to make PayPal's own check pass. At or above 100 minor units the discrepancy is passed through untouched, and PayPal may reject the order at payment time.
</Warning>

**For an amount that is not really a unit price**, such as a prorated charge, a computed fee, or anything without a per-unit figure behind it, send `quantity: 1` with `unit_price` equal to `total_amount`, and describe the real calculation in `description`. That keeps every downstream consumer consistent. For example, two memberships at `18900` prorated to 17 of 30 days is a single line of `21420`, not `2 x 18900`.

Two more things worth knowing, because neither is rejected at creation time:

* `quantity` is only bounded above, at `15000`. A `0` or negative quantity is accepted, but it is not harmless: it zeroes or drops the line in every consumer described in the warning above, and a zero quantity can corrupt the `unit_price` those consumers compute.
* `unit_price` defaults to `0` when omitted, silently. It is not treated as absent.

Two fields are commonly described as required but are not: `description` and `image_url`. Omitting them is valid, and omitting `image_url` is the simplest way to work around the restriction below during local development.

For digital goods, set `cart.items[].type` to `digital` explicitly. The contract also accepts `physical`, `bundled`, and `unknown`. Do not rely on an undocumented default to choose the checkout experience.

<Warning>
  Every URL in the cart, including `image_url`, must be resolvable from the public internet. A cart pointing at `localhost` is rejected at the edge before it reaches the API, and the response is an HTML error page rather than JSON, so clients that expect JSON fail with a parse error instead of a Bolt error code. In local development, either omit `image_url` or serve images through a tunnel.
</Warning>

## Response [#response]

The token is what you need. `cart` echoes back what Bolt stored.

```json title="Response"
{
  "token": "9c8e1f3a2b7d4e5f6a0b1c2d3e4f5a6b",
  "cart": {
    "order_reference": "order_100",
    "display_id": "displayid_100",
    "currency": { "currency": "USD", "currency_symbol": "$" },
    "total_amount": { "amount": 41200, "currency": "USD", "currency_symbol": "$" },
    "tax_amount": { "amount": 900, "currency": "USD", "currency_symbol": "$" },
    "items": [
      {
        "name": "Bolt Coffee Mug",
        "reference": "sku-mug-01",
        "sku": "MUG-01",
        "unit_price": { "amount": 20150, "currency": "USD", "currency_symbol": "$" },
        "total_amount": { "amount": 40300, "currency": "USD", "currency_symbol": "$" },
        "quantity": 2
      }
    ]
  }
}
```

<Note>
  The response is not the request. Amounts you sent as integers come back as objects of `amount`, `currency`, and `currency_symbol`, and the `currency` string comes back as an object too. A single type reused for both directions will fail to deserialize the response.
</Note>

The same split applies to the Merchant Callback events, where which form you receive depends on the event. See [Merchant Callback conventions](/api-reference/merchant-callback/conventions#amounts-and-currency).

## Next [#next]

Return `token` to the web or mobile client. Next, [choose and build the Merchant Callback events](/api-reference/merchant-callback/overview) that your checkout needs for order creation, shipping, tax, and discounts.
