# Create a checkout session

> The checkout.sessions.create operation: POST /api/v1/checkout/sessions in the REST API and the checkout_sessions_create tool in the MCP server.

Creates a hosted checkout and returns its `url`: send the customer there. The page collects the customer's
details, quotes the tax (spec §9), takes the payment and shows the receipt; the sale is recorded only when the
processor confirms it (webhook).

- **`payment`:** one-time prices and credit packs.
- **`subscription`:** recurring prices (spec §16.2). The first invoice is paid in the checkout and the card is
  saved for renewals; with a trial (`subscriptionData.trialDays`, default the prices' own) the card is only
  saved, or not asked for at all with `subscriptionData.trialRequiresPaymentMethod: false`. Lines can let the
  customer change the quantity (`adjustableQuantity`), and `allowPromotionCodes` adds a promotion code field.
- **`topup`:** credits for an existing customer's wallet (spec §14.3, `customer` required): one credit pack,
  a fixed pack or a custom amount within the pack's bounds. The buyer must use the customer's email; a customer
  who has paid before is taxed as an existing customer. The wallet gets the purchased credits (the amount before
  tax, or the amount paid under the brand's `gross` credit basis) and the pack's bonus credits.
- **`setup`:** no line items: the customer saves a card for a customer (`customer` required).
- **Line items:** 1 to 20 prices of one currency, by ID or lookup key, with a quantity (1 to 1,000) or, for a
  custom-amount price, the amount in integer micros. Prices become immutable once a session uses them.
- **Customer:** an existing customer, or an email to pre-fill. New buyers become customers when they're quoted.
- **Expiry:** 24 hours by default (30 minutes to 24 hours with `expiresAt`). Test sessions show a test banner.

The URL carries a session key: treat it like the session itself and give it only to the customer.

## At a glance

- **REST:** `POST /api/v1/checkout/sessions`
- **MCP tool:** `checkout_sessions_create`
- **Operation ID:** `checkout.sessions.create`
- **Scope:** Brand
- **Permission:** `checkout:write`
- **Risk tier:** `write`
- **Dry run:** Supported (send `Ownrecord-Dry-Run: true`)
- **Idempotency:** `Idempotency-Key` required
- **Modes:** Test and live
- **Authentication:** API key with the `checkout:write` scope
- **Since:** M4

## Request

### Body

A JSON object (`Content-Type: application/json`):

| Field                                         | Type                                               | Required | Description                                                                                                                                                                                                                                                                            |
| --------------------------------------------- | -------------------------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `mode`                                        | one of `payment`, `subscription`, `topup`, `setup` | No       | `payment`: one-time prices and credit packs; `subscription`: recurring prices, starting a subscription; `topup`: one credit pack (a fixed pack or a custom amount) added to an existing customer's wallet; `setup`: save a payment method of an existing customer, nothing is charged. |
| `lineItems`                                   | array of object                                    | No       | What the customer buys: 1 to 20 prices of one currency (none in `setup` mode).                                                                                                                                                                                                         |
| `lineItems[].price`                           | `price_…` ID                                       | No       | The price's ID (`price_…`). Give `price` or `lookupKey`.                                                                                                                                                                                                                               |
| `lineItems[].lookupKey`                       | string                                             | No       | The price's lookup key in this brand, e.g. `credits_100`.                                                                                                                                                                                                                              |
| `lineItems[].quantity`                        | integer (1 to 1000)                                | No       | How many (1 to 1000); custom-amount prices are sold one at a time.                                                                                                                                                                                                                     |
| `lineItems[].customAmount`                    | object                                             | No       | For a custom-amount price: the amount the customer pays, within the price's bounds.                                                                                                                                                                                                    |
| `lineItems[].customAmount.amount`             | string                                             | Yes      | Integer micros as a string (12500000 = 12.50), zero or more.                                                                                                                                                                                                                           |
| `lineItems[].customAmount.currency`           | string                                             | Yes      | ISO 4217 currency code; every line of a quote uses the same one.                                                                                                                                                                                                                       |
| `lineItems[].adjustableQuantity`              | object                                             | No       | Subscription sessions: let the customer change the quantity (seats) within these bounds (spec §16.6).                                                                                                                                                                                  |
| `lineItems[].adjustableQuantity.min`          | integer (1 to 1000)                                | Yes      |                                                                                                                                                                                                                                                                                        |
| `lineItems[].adjustableQuantity.max`          | integer (1 to 1000)                                | Yes      |                                                                                                                                                                                                                                                                                        |
| `customer`                                    | `cus_…` ID                                         | No       | An existing customer of the brand (`cus_…`); required in `setup` and `topup` mode.                                                                                                                                                                                                     |
| `customerEmail`                               | string                                             | No       | Pre-fills the email on the hosted page (ignored with `customer`).                                                                                                                                                                                                                      |
| `successUrl`                                  | string                                             | No       | Where the customer goes after paying; `?cs_id={session ID}` is added. Required except in `setup` mode (#435, #474).                                                                                                                                                                    |
| `cancelUrl`                                   | string                                             | No       | Where the hosted page's back link goes.                                                                                                                                                                                                                                                |
| `locale`                                      | string                                             | No       | The customer's language (invoices use it when supported).                                                                                                                                                                                                                              |
| `collectBusinessDetails`                      | one of `auto`, `required`, `never`                 | No       | `auto`: offer “Buying for a business?”; `required`: business customers only; `never`: consumers only.                                                                                                                                                                                  |
| `expiresAt`                                   | date-time                                          | No       | When the session expires: 30 minutes to 24 hours from now (default 24 hours).                                                                                                                                                                                                          |
| `allowPromotionCodes`                         | boolean                                            | No       | Subscription sessions: offer a promotion code field on the hosted page.                                                                                                                                                                                                                |
| `promotionCode`                               | string                                             | No       | Subscription sessions: a promotion code applied for the customer.                                                                                                                                                                                                                      |
| `subscriptionData`                            | object                                             | No       | Subscription sessions: the trial and the new subscription's metadata.                                                                                                                                                                                                                  |
| `subscriptionData.trialDays`                  | integer (1 to 730)                                 | No       | Trial days (default: the shortest trial of the prices).                                                                                                                                                                                                                                |
| `subscriptionData.trialRequiresPaymentMethod` | boolean                                            | No       | Collect a card for a trial (default true); without one, the trial starts without it.                                                                                                                                                                                                   |
| `subscriptionData.metadata`                   | object                                             | No       | Your own key-value pairs, returned with the session and in its events (e.g. your order ID).                                                                                                                                                                                            |
| `currency`                                    | string                                             | No       | Setup sessions: the currency the saved method will pay in (default: the entity's).                                                                                                                                                                                                     |
| `metadata`                                    | object                                             | No       | Your own key-value pairs, returned with the session and in its events (e.g. your order ID).                                                                                                                                                                                            |

## Response

`201 Created` with a JSON object:

| Field                            | Type                                                                                                                                                      | Description                                                                                                                                                     |
| -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`                             | `cs_…` ID                                                                                                                                                 |                                                                                                                                                                 |
| `object`                         | `"checkout.session"`                                                                                                                                      |                                                                                                                                                                 |
| `livemode`                       | boolean                                                                                                                                                   |                                                                                                                                                                 |
| `entityId`                       | `ent_…` ID                                                                                                                                                |                                                                                                                                                                 |
| `brandId`                        | `brd_…` ID                                                                                                                                                |                                                                                                                                                                 |
| `mode`                           | one of `payment`, `subscription`, `topup`, `setup`                                                                                                        |                                                                                                                                                                 |
| `status`                         | one of `open`, `processing`, `complete`, `expired`, `blocked`                                                                                             | `open`, `blocked` (the sale is paused for the customer's location), `processing`, `complete` or `expired`.                                                      |
| `url`                            | string or null                                                                                                                                            | The hosted page (with its session key); null once complete or expired.                                                                                          |
| `lineItems`                      | array of object                                                                                                                                           |                                                                                                                                                                 |
| `lineItems[].id`                 | string                                                                                                                                                    | The line's ID in the session's quotes and its invoice.                                                                                                          |
| `lineItems[].priceId`            | `price_…` ID                                                                                                                                              |                                                                                                                                                                 |
| `lineItems[].productId`          | `prod_…` ID                                                                                                                                               |                                                                                                                                                                 |
| `lineItems[].priceType`          | one of `one_time`, `credit_pack`, `recurring`                                                                                                             |                                                                                                                                                                 |
| `lineItems[].recurring`          | object or null                                                                                                                                            | Recurring prices (subscription sessions): the billing interval and whether usage is metered.                                                                    |
| `lineItems[].adjustableQuantity` | object or null                                                                                                                                            | The customer may change the quantity on the hosted page within these bounds.                                                                                    |
| `lineItems[].discount`           | money or null                                                                                                                                             | Subscription sessions: what the promotion code takes off this line on the first invoice.                                                                        |
| `lineItems[].description`        | string                                                                                                                                                    | The product's name when the session was created.                                                                                                                |
| `lineItems[].quantity`           | integer                                                                                                                                                   |                                                                                                                                                                 |
| `lineItems[].customAmount`       | money or null                                                                                                                                             |                                                                                                                                                                 |
| `lineItems[].amount`             | money                                                                                                                                                     | Before tax and discounts: gross for tax-inclusive prices, net for exclusive ones (a recurring price's first full period; zero for metered usage, billed later). |
| `lineItems[].taxBehavior`        | one of `inclusive`, `exclusive`                                                                                                                           |                                                                                                                                                                 |
| `currency`                       | string                                                                                                                                                    |                                                                                                                                                                 |
| `customerId`                     | `cus_…` ID or null                                                                                                                                        |                                                                                                                                                                 |
| `customerEmail`                  | string or null                                                                                                                                            |                                                                                                                                                                 |
| `successUrl`                     | string or null                                                                                                                                            |                                                                                                                                                                 |
| `cancelUrl`                      | string or null                                                                                                                                            |                                                                                                                                                                 |
| `locale`                         | string or null                                                                                                                                            |                                                                                                                                                                 |
| `collectBusinessDetails`         | one of `auto`, `required`, `never`                                                                                                                        |                                                                                                                                                                 |
| `allowPromotionCodes`            | boolean                                                                                                                                                   | The hosted page offers a promotion code field.                                                                                                                  |
| `promotionCodeId`                | `promo_…` ID or null                                                                                                                                      | The promotion code applied to the session.                                                                                                                      |
| `subscriptionData`               | object or null                                                                                                                                            | Subscription sessions: the trial and the subscription's metadata.                                                                                               |
| `taxQuoteId`                     | `tq_…` ID or null                                                                                                                                         | The current quote (`tax.quotes.get` explains it).                                                                                                               |
| `totals`                         | object or null                                                                                                                                            | The current quote's totals; null before the customer's details.                                                                                                 |
| `blockedReason`                  | one of `evidence_conflict`, `unsupported_tax_category`, `registration_required`, `rate_unavailable`, `jurisdiction_blocked`, `tax_id_unavailable` or null |                                                                                                                                                                 |
| `waitlistEntryId`                | `wl_…` ID or null                                                                                                                                         |                                                                                                                                                                 |
| `paymentId`                      | `pay_…` ID or null                                                                                                                                        |                                                                                                                                                                 |
| `invoiceId`                      | `inv_…` ID or null                                                                                                                                        |                                                                                                                                                                 |
| `subscriptionId`                 | `sub_…` ID or null                                                                                                                                        | Subscription sessions: the subscription created; setup sessions: the one the method is for.                                                                     |
| `paymentMethodId`                | `pm_…` ID or null                                                                                                                                         | The payment method saved (subscription and setup sessions).                                                                                                     |
| `expiresAt`                      | date-time                                                                                                                                                 |                                                                                                                                                                 |
| `completedAt`                    | date-time or null                                                                                                                                         |                                                                                                                                                                 |
| `createdAt`                      | date-time                                                                                                                                                 |                                                                                                                                                                 |
| `metadata`                       | object                                                                                                                                                    |                                                                                                                                                                 |

## Examples

### A credit pack by lookup key

The customer's email is pre-filled on the hosted page.

```http
POST /api/v1/checkout/sessions
Authorization: Bearer or_test_…
Idempotency-Key: 3f1c9a52-7d0e-4b8f-9a61-0c2d5e8b7f14
Content-Type: application/json

{
  "lineItems": [
    {
      "lookupKey": "credits_100",
      "quantity": 1
    }
  ],
  "customerEmail": "anna@example.com",
  "successUrl": "https://shop.example/thanks",
  "cancelUrl": "https://shop.example/cart",
  "metadata": {
    "order": "ord_1042"
  }
}
```

Response (`201`):

```json
{
  "id": "cs_01m4tx5nj8emwbyf7y9vcf48aa",
  "object": "checkout.session",
  "livemode": false,
  "entityId": "ent_01m4tx5nj8emwbyf7y9vcf47x2",
  "brandId": "brd_01m4tx5nj8emwbyf7y9vcf47x4",
  "mode": "payment",
  "status": "open",
  "url": "https://ownrecord.io/c/genno/cs_01m4tx5nj8emwbyf7y9vcf48aa?key=Zk3tq8Vb2xQ9mN4rP7sL1yH6cJ0wE5aD-uT_fG8hK2o",
  "lineItems": [
    {
      "id": "1",
      "priceId": "price_01m4tx5nj8emwbyf7y9vcf48ad",
      "productId": "prod_01m4tx5nj8emwbyf7y9vcf48ae",
      "priceType": "credit_pack",
      "recurring": null,
      "adjustableQuantity": null,
      "discount": null,
      "description": "Genno credits",
      "quantity": 1,
      "customAmount": null,
      "amount": {
        "amount": "100000000",
        "currency": "EUR"
      },
      "taxBehavior": "inclusive"
    }
  ],
  "currency": "EUR",
  "customerId": null,
  "customerEmail": "anna@example.com",
  "successUrl": "https://shop.example/thanks",
  "cancelUrl": "https://shop.example/cart",
  "locale": null,
  "collectBusinessDetails": "auto",
  "allowPromotionCodes": false,
  "promotionCodeId": null,
  "subscriptionData": null,
  "taxQuoteId": null,
  "totals": null,
  "blockedReason": null,
  "waitlistEntryId": null,
  "paymentId": null,
  "invoiceId": null,
  "subscriptionId": null,
  "paymentMethodId": null,
  "expiresAt": "2026-10-15T10:00:00.000Z",
  "completedAt": null,
  "createdAt": "2026-10-14T10:00:00.000Z",
  "metadata": {
    "order": "ord_1042"
  }
}
```

### A top-up of a customer's wallet

€40 of a custom-amount credit pack for an existing customer (spec §14.3).

```http
POST /api/v1/checkout/sessions
Authorization: Bearer or_test_…
Idempotency-Key: 3f1c9a52-7d0e-4b8f-9a61-0c2d5e8b7f14
Content-Type: application/json

{
  "mode": "topup",
  "customer": "cus_01m4tx5nj8emwbyf7y9vcf48ac",
  "lineItems": [
    {
      "lookupKey": "credits_custom",
      "customAmount": {
        "amount": "40000000",
        "currency": "EUR"
      }
    }
  ],
  "successUrl": "https://shop.example/wallet"
}
```

Response (`201`):

```json
{
  "id": "cs_01m4tx5nj8emwbyf7y9vcf48aa",
  "object": "checkout.session",
  "livemode": false,
  "entityId": "ent_01m4tx5nj8emwbyf7y9vcf47x2",
  "brandId": "brd_01m4tx5nj8emwbyf7y9vcf47x4",
  "mode": "topup",
  "status": "open",
  "url": "https://ownrecord.io/c/genno/cs_01m4tx5nj8emwbyf7y9vcf48aa?key=Zk3tq8Vb2xQ9mN4rP7sL1yH6cJ0wE5aD-uT_fG8hK2o",
  "lineItems": [
    {
      "id": "1",
      "priceId": "price_01m4tx5nj8emwbyf7y9vcf48ad",
      "productId": "prod_01m4tx5nj8emwbyf7y9vcf48ae",
      "priceType": "credit_pack",
      "recurring": null,
      "adjustableQuantity": null,
      "discount": null,
      "description": "Genno credits",
      "quantity": 1,
      "customAmount": {
        "amount": "40000000",
        "currency": "EUR"
      },
      "amount": {
        "amount": "40000000",
        "currency": "EUR"
      },
      "taxBehavior": "inclusive"
    }
  ],
  "currency": "EUR",
  "customerId": "cus_01m4tx5nj8emwbyf7y9vcf48ac",
  "customerEmail": null,
  "successUrl": "https://shop.example/wallet",
  "cancelUrl": null,
  "locale": null,
  "collectBusinessDetails": "auto",
  "allowPromotionCodes": false,
  "promotionCodeId": null,
  "subscriptionData": null,
  "taxQuoteId": null,
  "totals": null,
  "blockedReason": null,
  "waitlistEntryId": null,
  "paymentId": null,
  "invoiceId": null,
  "subscriptionId": null,
  "paymentMethodId": null,
  "expiresAt": "2026-10-15T10:00:00.000Z",
  "completedAt": null,
  "createdAt": "2026-10-14T10:00:00.000Z",
  "metadata": {}
}
```

## Errors

| Code                                                                                                                       | HTTP status | When                                                                                                                                                               | What to do                                                                                                                                                                                                                  |
| -------------------------------------------------------------------------------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [`brand_not_live`](https://ownrecord.io/docs/reference/errors.md#brand_not_live)                                           | 409         | A live-mode session for a brand that isn't live yet (its go-live checklist).                                                                                       | Use test mode, or complete the brand's go-live checklist first.                                                                                                                                                             |
| [`organization_closing`](https://ownrecord.io/docs/reference/errors.md#organization_closing)                               | 409         | A live-mode session while the organization is closing or closed.                                                                                                   | Existing subscriptions, refunds and filings continue until the organization is closed. Contact the seller for anything else.                                                                                                |
| [`checkout_mode_not_supported`](https://ownrecord.io/docs/reference/errors.md#checkout_mode_not_supported)                 | 400         | The mode isn't sold yet (`details.available_from` names the milestone). Every mode is sold since M6.                                                               | Create the session with `mode: payment` (one-time prices and credit packs), `subscription` (recurring prices) or `setup` (save a payment method). Top-ups arrive with milestone M6 (`details.available_from`).              |
| [`resource_not_found`](https://ownrecord.io/docs/reference/errors.md#resource_not_found)                                   | 404         | A line item's price or lookup key, or the customer, doesn't exist in this brand and mode.                                                                          | Check the ID and that your credentials are scoped to the right organization, entity, brand and mode.                                                                                                                        |
| [`price_archived`](https://ownrecord.io/docs/reference/errors.md#price_archived)                                           | 409         | A line item's price or its product is archived.                                                                                                                    | Reactivate the price first (`active: true`), or use an active price.                                                                                                                                                        |
| [`checkout_price_not_sellable`](https://ownrecord.io/docs/reference/errors.md#checkout_price_not_sellable)                 | 400         | A line item's price doesn't fit the mode: recurring in a `payment` session, one-time in a `subscription` session, anything but a credit pack in a `topup` session. | Sell one-time prices and credit packs in `payment` mode and recurring prices in `subscription` mode. `param` names the line item.                                                                                           |
| [`subscription_price_invalid`](https://ownrecord.io/docs/reference/errors.md#subscription_price_invalid)                   | 400         | `subscription`: the prices mix currencies or intervals that aren't whole multiples of each other, or repeat a price.                                               | Use active recurring prices of one currency whose intervals are whole multiples of each other (monthly with yearly, weekly with 2-weekly). `details.reason` says what's wrong and `param` names the item.                   |
| [`checkout_promotion_code_not_allowed`](https://ownrecord.io/docs/reference/errors.md#checkout_promotion_code_not_allowed) | 400         | `allowPromotionCodes` or `promotionCode` on a session that isn't a `subscription` session.                                                                         | Create the session with `allowPromotionCodes: true`, or with `promotionCode` to apply one for the customer. Promotion codes apply to `subscription` sessions.                                                               |
| [`promotion_code_not_redeemable`](https://ownrecord.io/docs/reference/errors.md#promotion_code_not_redeemable)             | 409         | `promotionCode` is inactive, expired, used up or not valid for this customer (`details.reason`).                                                                   | Check `details.reason` and tell the customer: the code may have expired, be used up, belong to another customer, need a larger purchase or be for first-time customers only.                                                |
| [`checkout_amount_invalid`](https://ownrecord.io/docs/reference/errors.md#checkout_amount_invalid)                         | 400         | A quantity is outside 1–1,000, or a custom amount is missing, outside the price's bounds or not whole minor units.                                                 | Give a quantity from 1 to 1,000 for fixed prices; for a custom-amount price give `customAmount` within the price's minimum and maximum, in whole minor units of its currency, with quantity 1. `param` names the line item. |
| [`checkout_currency_mismatch`](https://ownrecord.io/docs/reference/errors.md#checkout_currency_mismatch)                   | 400         | The line items' prices use more than one currency.                                                                                                                 | Put prices of one currency in a session; create one session per currency.                                                                                                                                                   |
| [`checkout_session_expiry_invalid`](https://ownrecord.io/docs/reference/errors.md#checkout_session_expiry_invalid)         | 400         | `expiresAt` is less than 30 minutes or more than 24 hours away.                                                                                                    | Set `expiresAt` between 30 minutes and 24 hours from now, or leave it out for 24 hours.                                                                                                                                     |
| [`customer_deleted`](https://ownrecord.io/docs/reference/errors.md#customer_deleted)                                       | 400         | The given customer was deleted.                                                                                                                                    | Create a new customer for this buyer. Deleted customers stay readable for the records they belong to, but can't be changed.                                                                                                 |
| [`validation_failed`](https://ownrecord.io/docs/reference/errors.md#validation_failed)                                     | 400         | A field is missing or malformed (`param` names it); a `topup` session without `customer` or with more than one line item.                                          | Fix the fields listed in `details.issues` and send the request again.                                                                                                                                                       |
| [`entitlement_required`](https://ownrecord.io/docs/reference/errors.md#entitlement_required)                               | 403         | The plan doesn't include live mode (live sessions), subscriptions (`subscription`) or usage billing (`topup`).                                                     | Upgrade the organization's plan, or remove something to get back under the limit.                                                                                                                                           |
| [`org_suspended`](https://ownrecord.io/docs/reference/errors.md#org_suspended)                                             | 403         | Live mode, while the organization is suspended (§37.5).                                                                                                            | If this is your organization, pay the open Ownrecord invoice in Organization → Billing; it is active again as soon as it is paid. Renewals, refunds, the customer portal and filings keep working.                          |

It can also return these [common errors](https://ownrecord.io/docs/reference.md#common-errors): [`api_version_unsupported`](https://ownrecord.io/docs/reference/errors.md#api_version_unsupported), [`invalid_json`](https://ownrecord.io/docs/reference/errors.md#invalid_json), [`unsupported_media_type`](https://ownrecord.io/docs/reference/errors.md#unsupported_media_type), [`authentication_required`](https://ownrecord.io/docs/reference/errors.md#authentication_required), [`api_key_invalid`](https://ownrecord.io/docs/reference/errors.md#api_key_invalid), [`permission_denied`](https://ownrecord.io/docs/reference/errors.md#permission_denied), [`scope_required`](https://ownrecord.io/docs/reference/errors.md#scope_required), [`idempotency_key_required`](https://ownrecord.io/docs/reference/errors.md#idempotency_key_required), [`idempotency_key_reused`](https://ownrecord.io/docs/reference/errors.md#idempotency_key_reused), [`idempotency_in_progress`](https://ownrecord.io/docs/reference/errors.md#idempotency_in_progress), [`approval_required`](https://ownrecord.io/docs/reference/errors.md#approval_required), [`approval_rejected`](https://ownrecord.io/docs/reference/errors.md#approval_rejected), [`approval_expired`](https://ownrecord.io/docs/reference/errors.md#approval_expired), [`stale_approval`](https://ownrecord.io/docs/reference/errors.md#stale_approval), [`approval_policy_denied`](https://ownrecord.io/docs/reference/errors.md#approval_policy_denied), [`rate_limited`](https://ownrecord.io/docs/reference/errors.md#rate_limited), [`internal_error`](https://ownrecord.io/docs/reference/errors.md#internal_error).
