---
title: Create an order and take payment
description: Create an order from your server with the v2 API, make retries safe with external_id, and send the buyer to checkout.
---

Goal: your backend (a booking system, a marketplace, a sales tool) creates the order in Bolder, and the buyer pays in Bolder's checkout. Payment, taxes and fulfillment run from there.

You need a token with `orders.write` (see [Make your first API request](/guides/first-api-request)) and the ID of the shop the order belongs to.

## Create the order

`POST /v2/orders`. `shop_id` is required; line items reference a variant by `variant_id` or `variant_sku`.

```js
import { createBolder } from 'bolder-api-client'

const bolder = await createBolder({ token: process.env.BOLDER_TOKEN })

const order = await bolder.client.post('/orders', {
  shop_id: 1234,
  external_id: 'booking-8812',
  contact: { name: 'Ada Lovelace', email: 'ada@example.com' },
  line_items: [
    { variant_sku: 'TOUR-12', units: 2 },
    { variant_id: '101', units: 1, price: 4500 } // price overrides the product's, in cents
  ]
})

console.log(order.code, order.status, order.total)
```

Other useful fields: `address`, `tags`, `fulfillment_method` (`shipping`, `mailbox_pickup`, `local_pickup`), `courier_name` and `tracking_code`. See [Create an order](/api/v2) for the full body.

## Make retries safe

The API has no idempotency-key header: a retried `POST` always runs again. Use `external_id`, your own reference, unique per seller:

- A second create with the same `external_id` is rejected with `422` instead of creating a duplicate.
- If a create times out and you don't know whether it went through, look it up before retrying: `GET /v2/orders?external_id=booking-8812`.
- Or use `PUT /v2/orders/{id}` with `_upsert: true` and an `external_id` in the body: it finds or creates the order by `external_id`, so a retry converges.

The `id` field (a client-supplied order code) does **not** protect you: a collision silently gets a new random code.

## Send the buyer to checkout

Every order carries a `checkout_url`. Redirect the buyer there, or open it in the embedded checkout:

```js
// server: hand the URL to your frontend
res.json({ checkout_url: order.checkout_url })
```

```js
// browser
import { openCheckout } from 'bolder-checkout'

const session = openCheckout({ checkoutUrl: checkout_url })
session.on('success', () => showConfirmation())
```

[Use Bolder checkout with your own cart](/guides/checkout-only) covers both modes and how to detect the buyer coming back.

### Checkout sessions

Shops on the session-based checkout can also start a session for an order and set where the buyer returns after a gateway redirect:

```js
const session = await bolder.client.post(`/orders/${order.id}/checkout_session`, {
  return_url: 'https://mystore.com/order/confirmation'
})
// session.checkout_url, session.token, session.expires_at
```

The call is idempotent while the session is active. The `return_url` host must be the shop's own domain or one of its allowed embed origins, otherwise the API answers `422`; it also answers `422` for shops not on the session-based checkout.

## Next

- [Receive order webhooks](/guides/receive-webhooks) to learn when the order is paid, shipped or cancelled.
- [Orders reference](/api/v2): items, payments, refunds, documents.
