---
name: kustom-checkout
description: Integrate Kustom Checkout (create order → render html_snippet → confirmation) against the Playground, using Playground API credentials (API username + API password) the user creates in the Kustom Portal.
---

# Kustom Checkout integration

Use when the user wants to add Kustom Checkout to a web shop, test the Checkout API, or debug an order payload.

## Before you start: get Playground API credentials

There are no throwaway credentials for agents. Before making any call, ask the user for a
Playground API username and API password, or for them to create them:

1. Sign up at https://portal.playground.kustom.co/onboarding (free, self-serve; a confirmation
   email links to the Playground Portal).
2. In the Playground Portal, **Developers → API → Generate new**. The API username is shown as
   Key ID (`<MID>-<suffix>`); the API password looks like `kco_test_api_...`, and a
   `kco_live_...` password returns `401` against the Playground.
3. Hand the API username and API password to the agent through the environment, never in
   a prompt or a committed file:

```bash
export KUSTOM_API_USERNAME=...                 # the Portal's Key ID
export KUSTOM_API_PASSWORD=kco_test_api_...
API=https://api.playground.kustom.co
DOCS=https://docs.kustom.co
curl -s -u "$KUSTOM_API_USERNAME:$KUSTOM_API_PASSWORD" $API/checkout/v3/orders/<order_id>
```

Send that HTTP Basic auth on every call to `$API`. Details: `$DOCS/api/authentication.md`.
The live demos on the docs pages use a temporary browser session; it is not meant for agents,
so don't script against `$DOCS/demo-api/session`.

For production, the user creates `kco_live_...` credentials in https://portal.kustom.co/ and calls
go to `https://api.kustom.co`.

## The flow

1. **Build the payload** — one order object: `order_lines[]` plus the totals that reconcile
   with them, `purchase_country`/`purchase_currency`/`locale` (fixed at creation, unchangeable
   after), `merchant_urls{terms,checkout,confirmation,push}` (HTTPS), and `options` for anything
   conditional (B2B, age verification, a separate shipping address). One line per cart item,
   never one aggregated total — see the rules below for the amounts that have to reconcile.
2. **Create order** — `POST $API/checkout/v3/orders` with Basic auth. Response `201` with
   `order_id`, `html_snippet`, `Location` header = order URL. `merchant_urls` is required and
   every URL must be `https://`, so a local shop needs a tunnel for the confirmation and push
   to reach it.
3. **Render snippet** — inject `html_snippet` into the page named in `merchant_urls.checkout`;
   when injecting client-side, re-create the `<script>` tags so they execute. While the shopper
   is still looking at it, wrap any update in `suspend()` / `POST <order URL>` / `resume()` —
   always `resume()` in a `finally`, so a failed update can't leave the checkout frozen.
4. **Validate (optional)** — only if stock, an expiring price or a fraud check can invalidate
   the order between creation and purchase. Register `merchant_urls.validation`; Kustom `POST`s
   the full order and waits up to ten seconds for a `200` (proceed) or a `303` with
   `Location` (send the shopper elsewhere instead). No reply in time is fail open by default —
   set `options.require_validate_callback_success: true` to fail closed instead.
5. **Confirm & push** — Kustom redirects the browser to `merchant_urls.confirmation` and
   separately `POST`s to `merchant_urls.push` about two minutes later; either can arrive alone.
   Both routes must do the same idempotent work: `GET <order URL>`, check
   `status.toLowerCase() == "checkout_complete"`, record it keyed on `order_id`, then
   acknowledge with `POST .../ordermanagement/v1/orders/{order_id}/acknowledge`.

## Rules that cause most 400s

- Amounts are integers in minor units (250.00 SEK is `25000`).
- `order_amount` equals the sum of every `order_lines[].total_amount`.
- `order_tax_amount` equals the sum of every `order_lines[].total_tax_amount`.
- Per line, `total_amount` equals `quantity` × `unit_price` − `total_discount_amount`.
- Per line, `total_tax_amount` sits within ±1 of `total_amount − total_amount × 10000 / (10000 + tax_rate)`.
- `tax_rate` carries two implicit decimals, so 25% is `2500`.
- Use the `Location` header for reads/updates; completed orders are read-only (`403 READ_ONLY_ORDER`).

## Reference

- Guide pages as Markdown: `$DOCS/checkout/integrate/the-order.md`, `$DOCS/checkout/integrate/create-order.md`, `$DOCS/checkout/integrate/merchant-urls.md`, `$DOCS/checkout/integrate/render-checkout-snippet.md`, `$DOCS/checkout/integrate/validation-callback.md`, `$DOCS/checkout/integrate/confirmation-and-push.md`, `$DOCS/checkout/shipping/shipping-assistant.md`
- Full index: `$DOCS/llms.txt`
