Carts API

A cart is a long-lived (~30 days) shopping list. It's the lightweight half of the cart→checkout split (Q1 design decision).

A cart has:

  • token — opaque URL handle (returned at create)
  • market_id — pinned currency and locale
  • customer_id — optional (NULL for anonymous)
  • line_items — variant + qty + custom_attributes
  • expires_at — auto-extended on every PATCH

The cart never reserves inventory by default. Reservation happens at checkout creation (default commerce.checkout.reserve_at = checkout_enter).

Create a cart

curl -X POST "$SF/carts.json" \
  -H "X-Storefront-Key: $SK" \
  -H "Content-Type: application/json" \
  -d '{
    "market_id": 308...,
    "customer_id": null,
    "utm": {"source":"newsletter","campaign":"may2026"},
    "referrer": "https://google.com"
  }'
{
  "cart": {
    "id": "308...",
    "token": "8H3xa9...long-opaque",
    "currency": "CZK",
    "status": "active",
    "line_items": [],
    "totals": {
      "currency": "CZK",
      "subtotal_with_tax_cents": "0",
      "total_cents": "0",
      "line_count": 0,
      "item_count": 0
    },
    "expires_at": "2026-05-31T..."
  }
}

The token is your handle for all subsequent calls. Store it in localStorage / a cookie so the basket survives page reloads.

Add a line

curl -X POST "$SF/carts/{token}/lines.json" \
  -H "X-Storefront-Key: $SK" -H "Content-Type: application/json" \
  -d '{
    "variant_id": 308...,
    "quantity": 2,
    "custom_attributes": {"gift_wrap": true, "message": "Happy birthday"}
  }'

The cart service:

  1. Resolves variant and variant_pricing(variant, cart.market_id)
  2. Rejects if pricing currency doesn't match cart currency (409)
  3. Tries to merge with an existing line (same variant + same custom_attributes_hash) — quantity adds up
  4. Otherwise creates a new cart_line_items row with snapshot prices
  5. Recomputes totals

Update qty

curl -X PATCH "$SF/carts/{token}/lines/{line_id}.json" \
  -H "X-Storefront-Key: $SK" -H "Content-Type: application/json" \
  -d '{"quantity": 5}'

Refreshes the price snapshot from the current variant_pricing row — the customer always sees today's prices, not whatever was stored when they first added the item.

Remove a line

curl -X DELETE "$SF/carts/{token}/lines/{line_id}.json" \
  -H "X-Storefront-Key: $SK"

Pricing recomputation

Cart totals are computed live on every read — no stored total_cents on the cart row. The cart-level totals are subtotal-only (no shipping, no order-level discounts; those are checkout-phase concerns).

{
  "totals": {
    "currency": "CZK",
    "subtotal_with_tax_cents": "24200",
    "subtotal_without_tax_cents": "20000",
    "tax_cents": "4200",
    "total_cents": "24200",
    "line_count": 1,
    "item_count": 2
  }
}

Customer association

A cart can attach a customer at create time, or be promoted later when the user logs in. When two carts meet (anonymous browser cart + existing customer cart), use merge_into (service-level) — the source cart becomes status=merged, lines move to the target.

Endpoints

POST   /storefront/2026-01/carts.json
GET    /storefront/2026-01/carts/{token}.json
POST   /storefront/2026-01/carts/{token}/lines.json
PATCH  /storefront/2026-01/carts/{token}/lines/{line_id}.json
DELETE /storefront/2026-01/carts/{token}/lines/{line_id}.json

Next: Checkouts API.