bolt.com is getting a new home → boltapp.com. Click here to transition smoothly to boltapp.com.

Skip to content

Create an order token

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

View As MarkdownMCP

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.

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.

If your backend uses Python, the beta Bolt Python SDK 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

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.

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;

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 sets out exactly. In this example, the items total 40300 and tax is 900, so the cart total is 41200.

Required on every request

FieldNotes
channelWhere the order originated: browser or mobile_app
cart.order_referenceYour identifier for the order. Must be unique per successful transaction
cart.currencyThree letter ISO code, for example USD
cart.total_amountTotal 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:

FieldNotes
cart.items[].referenceYour identifier for the line item
cart.items[].nameShopper-facing item name
cart.items[].skuYour stock identifier for the item
cart.items[].total_amountThe line total. This is the figure that has to add up, see below
cart.items[].unit_price, quantityPrice 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

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:

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.

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.

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.

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.

Response

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

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
      }
    ]
  }
}

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.

The same split applies to the Merchant Callback events, where which form you receive depends on the event. See Merchant Callback conventions.

Next

Return token to the web or mobile client. Next, choose and build the Merchant Callback events that your checkout needs for order creation, shipping, tax, and discounts.

On this page