# Orange Wine — API Part 04: Cart and Checkout

Version 1.0 — 28 Sep 2026 · 16 endpoints · Base URL `/api/v1` · Read with Part 00 (conventions, error catalog, permissions).

## 1. Before you start

- Base URL `/api/v1` (webhooks: `/webhooks/...`). JSON, `snake_case` keys, prefixed string IDs, ISO 8601 UTC timestamps.
- Success: `{ data, request_id, correlation_id }`. Error: `{ error: { code, message, details, retryable } }`. Gated feature: `data.enabled = false`, `status = "pending_configuration"`.
- Auth via HttpOnly cookies (`access_token`, `refresh_token`); every non-GET browser request sends `X-XSRF-TOKEN`.
- Money is always `{ amount_minor, currency }`. Percentages are basis points.
- Commands marked Idempotency = Required need an `Idempotency-Key` header (UUID v4).
- Status never changes through PATCH — use the named command endpoints.
- Full conventions, error catalog, permission catalog and open items: Part 00.

### Error codes used in this part

| Code | HTTP | Meaning |
|---|---|---|
| `ADULT_SIGNATURE_REQUIRED` | 422 | Shipment must use adult-signature service. |
| `CHECKOUT_EXPIRED` | 409 | Checkout session expired; start a new session. |
| `FORBIDDEN` | 403 | Authenticated, but missing permission, location scope, manager approval, or a valid webhook signature. |
| `FULFILLMENT_NOT_ELIGIBLE` | 422 | Selected fulfillment mode isn't allowed for this cart/location/time. |
| `IDEMPOTENCY_KEY_REUSED` | 409 | Same `Idempotency-Key` sent with a different request body. |
| `INVENTORY_CONFLICT` | 409 | Requested quantity no longer available at the fulfilling location. |
| `PAYMENT_ALREADY_CAPTURED` | 409 | The payment/order was already captured; returns the existing result where possible. |
| `PAYMENT_FAILED` | 402 | Provider declined or failed the payment. |
| `PAYMENT_PENDING` | 202 | Provider hasn't confirmed the payment yet; poll the payment status. |
| `PAYMENT_RECONCILIATION_REQUIRED` | 409 | Payment state is ambiguous; staff reconciliation is required. |
| `PRE_ARRIVAL_TERMS_MISSING` | 422 | Pre-arrival terms not accepted for this checkout. |
| `PROMOTION_RULE_CONFLICT` | 409 | Promotion conflicts with another active non-stackable rule. |
| `PROVIDER_UNAVAILABLE` | 503 | External provider unreachable or not configured; safe to retry later. |
| `QUOTE_STALE` | 409 | Cart, prices, promotions or fulfillment changed since the last quote; re-quote. |
| `RESOURCE_NOT_FOUND` | 404 | Resource doesn't exist or isn't visible to the caller (never reveals existence). |
| `SHIPPING_DESTINATION_NOT_ELIGIBLE` | 422 | Destination state/address isn't eligible for shipping. |
| `VALIDATION_ERROR` | 422 | Request failed validation. `details.fields` maps field → messages. |

### Endpoints requiring Idempotency-Key

| Method | Path | Title |
|---|---|---|
| POST | `/cart/merge` | Merge guest cart after sign-in |
| POST | `/checkout/sessions` | Start checkout |
| POST | `/checkout/sessions/{checkoutSessionId}/quote` | Re-quote |
| POST | `/checkout/sessions/{checkoutSessionId}/fulfillment` | Set fulfillment and address |
| POST | `/checkout/sessions/{checkoutSessionId}/pre-arrival-terms` | Accept Pre-Arrival Terms |
| POST | `/checkout/sessions/{checkoutSessionId}/payment-intent` | Create payment |
| POST | `/checkout/sessions/{checkoutSessionId}/place-order` | Place order |

### PROPOSED and gated endpoints in this part

| Status | Method | Path |
|---|---|---|
| CONFIRMED (provider CONFIGURATION GATE) | POST | `/checkout/sessions/{checkoutSessionId}/payment-intent` |

## 2. Cart

One singleton cart per customer/guest session with two sub-carts, `normal` and `pre_arrival` (D-03). Every cart response returns the whole cart so the UI re-renders from the server.

| # | Method | Path | Title | Status |
|---|---|---|---|---|
| 2.1 | GET | `/cart` | Get cart | CONFIRMED |
| 2.2 | POST | `/cart/items` | Add item | CONFIRMED |
| 2.3 | PATCH | `/cart/items/{itemId}` | Change quantity / fulfillment | CONFIRMED |
| 2.4 | DELETE | `/cart/items/{itemId}` | Remove item | CONFIRMED |
| 2.5 | DELETE | `/cart` | Clear cart | CONFIRMED |
| 2.6 | POST | `/cart/merge` | Merge guest cart after sign-in | CONFIRMED |
| 2.7 | POST | `/cart/coupon` | Apply coupon | CONFIRMED |
| 2.8 | DELETE | `/cart/coupon` | Remove coupon | CONFIRMED |

### 2.1 `GET /cart`

**Get cart**

| Property | Value |
|---|---|
| Endpoint | `GET /api/v1/cart` |
| Auth | Guest session or customer |
| Status | CONFIRMED |
| CSRF header | Not required |
| Idempotency-Key | Not used |
| Rate limited | No |

**Request example**

```http
GET /api/v1/cart HTTP/1.1
Cookie: access_token=<jwt>; refresh_token=<opaque>; XSRF-TOKEN=<token>
```

**Response — success (200)**

```http
HTTP/1.1 200 OK

{
  "data": {
    "cart_id": "cart_session_01",
    "carts": {
      "normal": {
        "items": [
          {
            "item_id": "ci_01",
            "product_id": "prod_201",
            "name": "Rapid Ship Product B",
            "sku": "RS-B-750",
            "quantity": 1,
            "flags": {
              "rapid_ship_eligible": true,
              "ship_free_12_eligible": false
            },
            "fulfillment": {
              "mode": "shipping",
              "location_id": "loc_albany"
            },
            "unit_price": {
              "amount_minor": 3000,
              "currency": "USD"
            },
            "line_total": {
              "amount_minor": 3000,
              "currency": "USD"
            },
            "availability": {
              "state": "available_now",
              "available": true
            }
          },
          {
            "item_id": "ci_02",
            "product_id": "prod_202",
            "name": "12 Ship Free Product C",
            "sku": "SF-C-750",
            "quantity": 12,
            "flags": {
              "rapid_ship_eligible": false,
              "ship_free_12_eligible": true
            },
            "fulfillment": {
              "mode": "shipping",
              "location_id": "loc_albany"
            },
            "unit_price": {
              "amount_minor": 2000,
              "currency": "USD"
            },
            "line_total": {
              "amount_minor": 24000,
              "currency": "USD"
            },
            "availability": {
              "state": "available_now",
              "available": true
            }
          }
        ],
        "coupon": null,
        "item_count": 13,
        "subtotal": {
          "amount_minor": 27000,
          "currency": "USD"
        },
        "ship_free_12_preview": {
          "qualified": false,
          "reason_code": "NON_QUALIFYING_PRODUCT_IN_CART",
          "blocking_product_ids": [
            "prod_201"
          ]
        }
      },
      "pre_arrival": {
        "items": [
          {
            "item_id": "ci_03",
            "product_id": "prod_500",
            "name": "Pre-Arrival Product A",
            "quantity": 6,
            "expected_lead_time_days": 75,
            "estimated_delivery_if_ordered_today": "2026-12-08",
            "unit_price": {
              "amount_minor": 4500,
              "currency": "USD"
            },
            "line_total": {
              "amount_minor": 27000,
              "currency": "USD"
            }
          }
        ],
        "coupon": null,
        "item_count": 6,
        "subtotal": {
          "amount_minor": 27000,
          "currency": "USD"
        }
      }
    },
    "updated_at": "2026-09-17T16:00:00Z"
  },
  "request_id": "req_01J9Z8",
  "correlation_id": "cor_01J9Z8"
}
```

**Response keys**

| Key | Type / allowed values | Description |
|---|---|---|
| cart_id | string | Opaque; one cart per customer/guest session (D-03). There is no `/carts/{id}`. |
| carts.normal / carts.pre_arrival | object | Two sub-carts. The server places each product by its `pre_arrival` flag. |
| carts.*.items[].item_id | string | Use in PATCH/DELETE /cart/items/{itemId}. |
| carts.*.items[].quantity | integer ≥ 1 |  |
| carts.normal.items[].flags.rapid_ship_eligible | boolean | Product flag; Rapid Ship still requires stock at the fulfillment location. |
| carts.normal.items[].flags.ship_free_12_eligible | boolean |  |
| carts.*.items[].fulfillment.mode | enum | `shipping` \| `pickup` \| `curbside` \| `local_delivery` (gate). |
| carts.*.items[].unit_price / line_total | Money object `{ amount_minor: integer, currency: "USD" }` | Advisory until checkout quote. |
| carts.*.items[].availability.state | enum | `available_now` \| `pre_arrival` \| `backordered` \| `unavailable`. |
| carts.pre_arrival.items[].estimated_delivery_if_ordered_today | YYYY-MM-DD | `today + expected_lead_time_days`. |
| carts.*.coupon | object \| null | `{ code, discount: Money, valid: boolean, message }`. |
| carts.normal.ship_free_12_preview.qualified | boolean | Advisory — checkout quote decides (D-11). |
| carts.normal.ship_free_12_preview.reason_code | enum | `QUALIFIED` \| `NON_QUALIFYING_PRODUCT_IN_CART` \| `QUANTITY_NOT_MULTIPLE_OF_12` \| `PROGRAM_DISABLED` \| `DESTINATION_NOT_ELIGIBLE` \| `EMPTY_CART`. |
| carts.normal.ship_free_12_preview.blocking_product_ids | string[] | Products preventing qualification (PROPOSED field). |

---

### 2.2 `POST /cart/items`

**Add item**

| Property | Value |
|---|---|
| Endpoint | `POST /api/v1/cart/items` |
| Purpose | Adds a product. The server routes it to `normal` or `pre_arrival`. Adding an existing product increases its quantity. |
| Auth | Guest session or customer |
| Status | CONFIRMED |
| CSRF header | Required |
| Idempotency-Key | Not used |
| Rate limited | No |

**Request example**

```http
POST /api/v1/cart/items HTTP/1.1
Content-Type: application/json
Cookie: access_token=<jwt>; refresh_token=<opaque>; XSRF-TOKEN=<token>
X-XSRF-TOKEN: <token from XSRF-TOKEN cookie>

{
  "product_id": "prod_101",
  "quantity": 2,
  "fulfillment": {
    "mode": "shipping",
    "location_id": "loc_albany"
  }
}
```

Do not send `cart_type` — the server decides.

**Request keys**

| Key | Type / allowed values | Description |
|---|---|---|
| product_id | string · required | Flat product ID (no variant). |
| quantity | integer · required · ≥ 1 |  |
| fulfillment.mode | enum · optional | `shipping` \| `pickup` \| `curbside`. Defaults to session choice. |
| fulfillment.location_id | string · optional | Defaults to selected store. |

**Response — success (200)**

```http
HTTP/1.1 200 OK

{
  "data": {
    "cart_id": "cart_session_01",
    "carts": {
      "normal": {
        "items": [
          {
            "item_id": "ci_01",
            "product_id": "prod_201",
            "name": "Rapid Ship Product B",
            "sku": "RS-B-750",
            "quantity": 1,
            "flags": {
              "rapid_ship_eligible": true,
              "ship_free_12_eligible": false
            },
            "fulfillment": {
              "mode": "shipping",
              "location_id": "loc_albany"
            },
            "unit_price": {
              "amount_minor": 3000,
              "currency": "USD"
            },
            "line_total": {
              "amount_minor": 3000,
              "currency": "USD"
            },
            "availability": {
              "state": "available_now",
              "available": true
            }
          },
          {
            "item_id": "ci_02",
            "product_id": "prod_202",
            "name": "12 Ship Free Product C",
            "sku": "SF-C-750",
            "quantity": 12,
            "flags": {
              "rapid_ship_eligible": false,
              "ship_free_12_eligible": true
            },
            "fulfillment": {
              "mode": "shipping",
              "location_id": "loc_albany"
            },
            "unit_price": {
              "amount_minor": 2000,
              "currency": "USD"
            },
            "line_total": {
              "amount_minor": 24000,
              "currency": "USD"
            },
            "availability": {
              "state": "available_now",
              "available": true
            }
          }
        ],
        "coupon": null,
        "item_count": 13,
        "subtotal": {
          "amount_minor": 27000,
          "currency": "USD"
        },
        "ship_free_12_preview": {
          "qualified": false,
          "reason_code": "NON_QUALIFYING_PRODUCT_IN_CART",
          "blocking_product_ids": [
            "prod_201"
          ]
        }
      },
      "pre_arrival": {
        "items": [
          {
            "item_id": "ci_03",
            "product_id": "prod_500",
            "name": "Pre-Arrival Product A",
            "quantity": 6,
            "expected_lead_time_days": 75,
            "estimated_delivery_if_ordered_today": "2026-12-08",
            "unit_price": {
              "amount_minor": 4500,
              "currency": "USD"
            },
            "line_total": {
              "amount_minor": 27000,
              "currency": "USD"
            }
          }
        ],
        "coupon": null,
        "item_count": 6,
        "subtotal": {
          "amount_minor": 27000,
          "currency": "USD"
        }
      }
    },
    "updated_at": "2026-09-17T16:00:00Z"
  },
  "request_id": "req_01J9Z8",
  "correlation_id": "cor_01J9Z8"
}
```

**Response keys**

| Key | Type / allowed values | Description |
|---|---|---|
| (all) | Cart | Full cart, see GET /cart. |

**Errors**

| Code | HTTP | When |
|---|---|---|
| `RESOURCE_NOT_FOUND` | 404 | Unknown/unpublished product |
| `INVENTORY_CONFLICT` | 409 | Not enough available stock |
| `FULFILLMENT_NOT_ELIGIBLE` | 422 | Mode not offered for this product/location |
| `VALIDATION_ERROR` | 422 | Bad quantity |

**Error example — INVENTORY_CONFLICT**

```http
HTTP/1.1 409 Conflict

{
  "error": {
    "code": "INVENTORY_CONFLICT",
    "message": "One or more products are no longer available.",
    "details": {
      "items": [
        {
          "product_id": "prod_101",
          "requested": 2,
          "available": 1
        }
      ]
    },
    "retryable": false
  },
  "request_id": "req_01J9Z8",
  "correlation_id": "cor_01J9Z8"
}
```

---

### 2.3 `PATCH /cart/items/{itemId}`

**Change quantity / fulfillment**

| Property | Value |
|---|---|
| Endpoint | `PATCH /api/v1/cart/items/{itemId}` |
| Auth | Guest session or customer |
| Status | CONFIRMED |
| CSRF header | Required |
| Idempotency-Key | Not used |
| Rate limited | No |

**Path parameters**

| Key | Type / allowed values | Description |
|---|---|---|
| itemId | string |  |

**Request example**

```http
PATCH /api/v1/cart/items/{itemId} HTTP/1.1
Content-Type: application/json
Cookie: access_token=<jwt>; refresh_token=<opaque>; XSRF-TOKEN=<token>
X-XSRF-TOKEN: <token from XSRF-TOKEN cookie>

{
  "quantity": 3
}
```

**Request keys**

| Key | Type / allowed values | Description |
|---|---|---|
| quantity | integer · optional · ≥ 1 |  |
| fulfillment | object · optional | Same as add. |

**Response — success (200)**

```http
HTTP/1.1 200 OK

{
  "data": {
    "cart_id": "cart_session_01",
    "carts": {
      "normal": {
        "items": [
          {
            "item_id": "ci_01",
            "product_id": "prod_201",
            "name": "Rapid Ship Product B",
            "sku": "RS-B-750",
            "quantity": 1,
            "flags": {
              "rapid_ship_eligible": true,
              "ship_free_12_eligible": false
            },
            "fulfillment": {
              "mode": "shipping",
              "location_id": "loc_albany"
            },
            "unit_price": {
              "amount_minor": 3000,
              "currency": "USD"
            },
            "line_total": {
              "amount_minor": 3000,
              "currency": "USD"
            },
            "availability": {
              "state": "available_now",
              "available": true
            }
          },
          {
            "item_id": "ci_02",
            "product_id": "prod_202",
            "name": "12 Ship Free Product C",
            "sku": "SF-C-750",
            "quantity": 12,
            "flags": {
              "rapid_ship_eligible": false,
              "ship_free_12_eligible": true
            },
            "fulfillment": {
              "mode": "shipping",
              "location_id": "loc_albany"
            },
            "unit_price": {
              "amount_minor": 2000,
              "currency": "USD"
            },
            "line_total": {
              "amount_minor": 24000,
              "currency": "USD"
            },
            "availability": {
              "state": "available_now",
              "available": true
            }
          }
        ],
        "coupon": null,
        "item_count": 13,
        "subtotal": {
          "amount_minor": 27000,
          "currency": "USD"
        },
        "ship_free_12_preview": {
          "qualified": false,
          "reason_code": "NON_QUALIFYING_PRODUCT_IN_CART",
          "blocking_product_ids": [
            "prod_201"
          ]
        }
      },
      "pre_arrival": {
        "items": [
          {
            "item_id": "ci_03",
            "product_id": "prod_500",
            "name": "Pre-Arrival Product A",
            "quantity": 6,
            "expected_lead_time_days": 75,
            "estimated_delivery_if_ordered_today": "2026-12-08",
            "unit_price": {
              "amount_minor": 4500,
              "currency": "USD"
            },
            "line_total": {
              "amount_minor": 27000,
              "currency": "USD"
            }
          }
        ],
        "coupon": null,
        "item_count": 6,
        "subtotal": {
          "amount_minor": 27000,
          "currency": "USD"
        }
      }
    },
    "updated_at": "2026-09-17T16:00:00Z"
  },
  "request_id": "req_01J9Z8",
  "correlation_id": "cor_01J9Z8"
}
```

**Errors**

| Code | HTTP | When |
|---|---|---|
| `RESOURCE_NOT_FOUND` | 404 | Item not in cart |
| `INVENTORY_CONFLICT` | 409 | Requested quantity no longer available at the fulfilling location. |
| `VALIDATION_ERROR` | 422 | Request failed validation. `details.fields` maps field → messages. |

---

### 2.4 `DELETE /cart/items/{itemId}`

**Remove item**

| Property | Value |
|---|---|
| Endpoint | `DELETE /api/v1/cart/items/{itemId}` |
| Auth | Guest session or customer |
| Status | CONFIRMED |
| CSRF header | Required |
| Idempotency-Key | Not used |
| Rate limited | No |

**Path parameters**

| Key | Type / allowed values | Description |
|---|---|---|
| itemId | string |  |

**Request example**

```http
DELETE /api/v1/cart/items/{itemId} HTTP/1.1
Cookie: access_token=<jwt>; refresh_token=<opaque>; XSRF-TOKEN=<token>
X-XSRF-TOKEN: <token from XSRF-TOKEN cookie>
```

**Response — success (200)**

```http
HTTP/1.1 200 OK

{
  "data": {
    "cart_id": "cart_session_01",
    "carts": {
      "normal": {
        "items": [
          {
            "item_id": "ci_01",
            "product_id": "prod_201",
            "name": "Rapid Ship Product B",
            "sku": "RS-B-750",
            "quantity": 1,
            "flags": {
              "rapid_ship_eligible": true,
              "ship_free_12_eligible": false
            },
            "fulfillment": {
              "mode": "shipping",
              "location_id": "loc_albany"
            },
            "unit_price": {
              "amount_minor": 3000,
              "currency": "USD"
            },
            "line_total": {
              "amount_minor": 3000,
              "currency": "USD"
            },
            "availability": {
              "state": "available_now",
              "available": true
            }
          },
          {
            "item_id": "ci_02",
            "product_id": "prod_202",
            "name": "12 Ship Free Product C",
            "sku": "SF-C-750",
            "quantity": 12,
            "flags": {
              "rapid_ship_eligible": false,
              "ship_free_12_eligible": true
            },
            "fulfillment": {
              "mode": "shipping",
              "location_id": "loc_albany"
            },
            "unit_price": {
              "amount_minor": 2000,
              "currency": "USD"
            },
            "line_total": {
              "amount_minor": 24000,
              "currency": "USD"
            },
            "availability": {
              "state": "available_now",
              "available": true
            }
          }
        ],
        "coupon": null,
        "item_count": 13,
        "subtotal": {
          "amount_minor": 27000,
          "currency": "USD"
        },
        "ship_free_12_preview": {
          "qualified": false,
          "reason_code": "NON_QUALIFYING_PRODUCT_IN_CART",
          "blocking_product_ids": [
            "prod_201"
          ]
        }
      },
      "pre_arrival": {
        "items": [
          {
            "item_id": "ci_03",
            "product_id": "prod_500",
            "name": "Pre-Arrival Product A",
            "quantity": 6,
            "expected_lead_time_days": 75,
            "estimated_delivery_if_ordered_today": "2026-12-08",
            "unit_price": {
              "amount_minor": 4500,
              "currency": "USD"
            },
            "line_total": {
              "amount_minor": 27000,
              "currency": "USD"
            }
          }
        ],
        "coupon": null,
        "item_count": 6,
        "subtotal": {
          "amount_minor": 27000,
          "currency": "USD"
        }
      }
    },
    "updated_at": "2026-09-17T16:00:00Z"
  },
  "request_id": "req_01J9Z8",
  "correlation_id": "cor_01J9Z8"
}
```

**Errors**

| Code | HTTP | When |
|---|---|---|
| `RESOURCE_NOT_FOUND` | 404 | Resource doesn't exist or isn't visible to the caller (never reveals existence). |

---

### 2.5 `DELETE /cart`

**Clear cart**

| Property | Value |
|---|---|
| Endpoint | `DELETE /api/v1/cart` |
| Auth | Guest session or customer |
| Status | CONFIRMED |
| CSRF header | Required |
| Idempotency-Key | Not used |
| Rate limited | No |

**Query parameters**

| Key | Type / allowed values | Description |
|---|---|---|
| cart_type | enum · optional | `normal` \| `pre_arrival`. Omit to clear both. |

**Request example**

```http
DELETE /api/v1/cart HTTP/1.1
Cookie: access_token=<jwt>; refresh_token=<opaque>; XSRF-TOKEN=<token>
X-XSRF-TOKEN: <token from XSRF-TOKEN cookie>
```

**Response — success (200)**

```http
HTTP/1.1 200 OK

{
  "data": {
    "cart_id": "cart_session_01",
    "carts": {
      "normal": {
        "items": [],
        "item_count": 0
      },
      "pre_arrival": {
        "items": [],
        "item_count": 0
      }
    },
    "updated_at": "2026-09-17T16:00:00Z"
  },
  "request_id": "req_01J9Z8",
  "correlation_id": "cor_01J9Z8"
}
```

---

### 2.6 `POST /cart/merge`

**Merge guest cart after sign-in**

| Property | Value |
|---|---|
| Endpoint | `POST /api/v1/cart/merge` |
| Auth | Customer |
| Status | CONFIRMED |
| CSRF header | Required |
| Idempotency-Key | Required |
| Rate limited | No |

**Request example**

```http
POST /api/v1/cart/merge HTTP/1.1
Content-Type: application/json
Cookie: access_token=<jwt>; refresh_token=<opaque>; XSRF-TOKEN=<token>
X-XSRF-TOKEN: <token from XSRF-TOKEN cookie>
Idempotency-Key: 6f1c2a4e-8d3b-4f6a-9c1e-2b7d5e8f0a13

{
  "guest_items": [
    {
      "product_id": "prod_101",
      "quantity": 2
    },
    {
      "product_id": "prod_500",
      "quantity": 1
    }
  ]
}
```

**Request keys**

| Key | Type / allowed values | Description |
|---|---|---|
| guest_items[].product_id | string · required |  |
| guest_items[].quantity | integer · required |  |

**Response — success (200)**

```http
HTTP/1.1 200 OK

{
  "data": {
    "merged_items": [
      {
        "product_id": "prod_101",
        "requested_quantity": 2,
        "accepted_quantity": 1,
        "status": "partially_accepted",
        "cart_type": "normal"
      },
      {
        "product_id": "prod_500",
        "requested_quantity": 1,
        "accepted_quantity": 1,
        "status": "accepted",
        "cart_type": "pre_arrival"
      }
    ],
    "cart": "(full cart)"
  },
  "request_id": "req_01J9Z8",
  "correlation_id": "cor_01J9Z8"
}
```

**Response keys**

| Key | Type / allowed values | Description |
|---|---|---|
| merged_items[].status | enum | `accepted` \| `partially_accepted` \| `rejected`. Never silently truncated. |
| merged_items[].cart_type | enum | `normal` \| `pre_arrival` — where it landed. |
| cart | Cart | Full cart. |

**Errors**

| Code | HTTP | When |
|---|---|---|
| `IDEMPOTENCY_KEY_REUSED` | 409 | Same `Idempotency-Key` sent with a different request body. |
| `VALIDATION_ERROR` | 422 | Request failed validation. `details.fields` maps field → messages. |

---

### 2.7 `POST /cart/coupon`

**Apply coupon**

| Property | Value |
|---|---|
| Endpoint | `POST /api/v1/cart/coupon` |
| Auth | Guest session or customer |
| Status | CONFIRMED |
| CSRF header | Required |
| Idempotency-Key | Not used |
| Rate limited | No |

**Request example**

```http
POST /api/v1/cart/coupon HTTP/1.1
Content-Type: application/json
Cookie: access_token=<jwt>; refresh_token=<opaque>; XSRF-TOKEN=<token>
X-XSRF-TOKEN: <token from XSRF-TOKEN cookie>

{
  "code": "WELCOME10",
  "cart_type": "normal"
}
```

**Request keys**

| Key | Type / allowed values | Description |
|---|---|---|
| code | string · required |  |
| cart_type | enum · optional · default normal | `normal` \| `pre_arrival`. |

**Response — success (200)**

```http
HTTP/1.1 200 OK

{
  "data": {
    "cart": "(full cart)",
    "coupon": {
      "code": "WELCOME10",
      "valid": true,
      "discount": {
        "amount_minor": 1000,
        "currency": "USD"
      },
      "message": "10% off applied"
    }
  },
  "request_id": "req_01J9Z8",
  "correlation_id": "cor_01J9Z8"
}
```

**Response keys**

| Key | Type / allowed values | Description |
|---|---|---|
| coupon.valid | boolean | `false` with a message when the coupon is not applicable (still 200). |

**Errors**

| Code | HTTP | When |
|---|---|---|
| `RESOURCE_NOT_FOUND` | 404 | Unknown code |
| `PROMOTION_RULE_CONFLICT` | 409 | Cannot combine with active promotion |

---

### 2.8 `DELETE /cart/coupon`

**Remove coupon**

| Property | Value |
|---|---|
| Endpoint | `DELETE /api/v1/cart/coupon` |
| Auth | Guest session or customer |
| Status | CONFIRMED |
| CSRF header | Required |
| Idempotency-Key | Not used |
| Rate limited | No |

**Query parameters**

| Key | Type / allowed values | Description |
|---|---|---|
| cart_type | enum · optional · default normal |  |

**Request example**

```http
DELETE /api/v1/cart/coupon HTTP/1.1
Cookie: access_token=<jwt>; refresh_token=<opaque>; XSRF-TOKEN=<token>
X-XSRF-TOKEN: <token from XSRF-TOKEN cookie>
```

**Response — success (200)**

```http
HTTP/1.1 200 OK

{
  "data": {
    "cart_id": "cart_session_01",
    "carts": {
      "normal": {
        "items": [
          {
            "item_id": "ci_01",
            "product_id": "prod_201",
            "name": "Rapid Ship Product B",
            "sku": "RS-B-750",
            "quantity": 1,
            "flags": {
              "rapid_ship_eligible": true,
              "ship_free_12_eligible": false
            },
            "fulfillment": {
              "mode": "shipping",
              "location_id": "loc_albany"
            },
            "unit_price": {
              "amount_minor": 3000,
              "currency": "USD"
            },
            "line_total": {
              "amount_minor": 3000,
              "currency": "USD"
            },
            "availability": {
              "state": "available_now",
              "available": true
            }
          },
          {
            "item_id": "ci_02",
            "product_id": "prod_202",
            "name": "12 Ship Free Product C",
            "sku": "SF-C-750",
            "quantity": 12,
            "flags": {
              "rapid_ship_eligible": false,
              "ship_free_12_eligible": true
            },
            "fulfillment": {
              "mode": "shipping",
              "location_id": "loc_albany"
            },
            "unit_price": {
              "amount_minor": 2000,
              "currency": "USD"
            },
            "line_total": {
              "amount_minor": 24000,
              "currency": "USD"
            },
            "availability": {
              "state": "available_now",
              "available": true
            }
          }
        ],
        "coupon": null,
        "item_count": 13,
        "subtotal": {
          "amount_minor": 27000,
          "currency": "USD"
        },
        "ship_free_12_preview": {
          "qualified": false,
          "reason_code": "NON_QUALIFYING_PRODUCT_IN_CART",
          "blocking_product_ids": [
            "prod_201"
          ]
        }
      },
      "pre_arrival": {
        "items": [
          {
            "item_id": "ci_03",
            "product_id": "prod_500",
            "name": "Pre-Arrival Product A",
            "quantity": 6,
            "expected_lead_time_days": 75,
            "estimated_delivery_if_ordered_today": "2026-12-08",
            "unit_price": {
              "amount_minor": 4500,
              "currency": "USD"
            },
            "line_total": {
              "amount_minor": 27000,
              "currency": "USD"
            }
          }
        ],
        "coupon": null,
        "item_count": 6,
        "subtotal": {
          "amount_minor": 27000,
          "currency": "USD"
        }
      }
    },
    "updated_at": "2026-09-17T16:00:00Z"
  },
  "request_id": "req_01J9Z8",
  "correlation_id": "cor_01J9Z8"
}
```

---

## 3. Checkout and Payments

Resource-based checkout sessions (D-01). Address is part of fulfillment (D-02). Each sub-cart checks out separately (`cart_type`). All mutating steps require `Idempotency-Key`. A browser redirect never proves payment.

| # | Method | Path | Title | Status |
|---|---|---|---|---|
| 3.1 | POST | `/checkout/sessions` | Start checkout | CONFIRMED |
| 3.2 | GET | `/checkout/sessions/{checkoutSessionId}` | Get checkout session | CONFIRMED |
| 3.3 | POST | `/checkout/sessions/{checkoutSessionId}/quote` | Re-quote | CONFIRMED |
| 3.4 | POST | `/checkout/sessions/{checkoutSessionId}/fulfillment` | Set fulfillment and address | CONFIRMED |
| 3.5 | POST | `/checkout/sessions/{checkoutSessionId}/pre-arrival-terms` | Accept Pre-Arrival Terms | CONFIRMED |
| 3.6 | POST | `/checkout/sessions/{checkoutSessionId}/payment-intent` | Create payment | CONFIRMED (provider CONFIGURATION GATE) |
| 3.7 | GET | `/payments/{paymentReference}` | Poll payment | CONFIRMED |
| 3.8 | POST | `/checkout/sessions/{checkoutSessionId}/place-order` | Place order | CONFIRMED |

### 3.1 `POST /checkout/sessions`

**Start checkout**

| Property | Value |
|---|---|
| Endpoint | `POST /api/v1/checkout/sessions` |
| Purpose | Creates a session for one sub-cart, reserves stock (normal cart only) and returns the first quote. |
| Auth | Guest session or customer |
| Status | CONFIRMED |
| CSRF header | Required |
| Idempotency-Key | Required |
| Rate limited | No |

**Request example**

```http
POST /api/v1/checkout/sessions HTTP/1.1
Content-Type: application/json
Cookie: access_token=<jwt>; refresh_token=<opaque>; XSRF-TOKEN=<token>
X-XSRF-TOKEN: <token from XSRF-TOKEN cookie>
Idempotency-Key: 6f1c2a4e-8d3b-4f6a-9c1e-2b7d5e8f0a13

{
  "cart_type": "normal",
  "fulfillment": {
    "mode": "shipping",
    "location_id": "loc_albany",
    "address": {
      "line1": "100 Main Street",
      "line2": "Suite 4",
      "city": "Albany",
      "state": "NY",
      "postal_code": "12201",
      "country": "US"
    }
  },
  "coupon_code": "WELCOME10",
  "contact": {
    "email": "guest@example.com",
    "phone": "+15185550100"
  }
}
```

**Request keys**

| Key | Type / allowed values | Description |
|---|---|---|
| cart_type | enum · required | `normal` \| `pre_arrival`. |
| fulfillment | object · optional | May be set now or with /fulfillment. |
| coupon_code | string · optional |  |
| contact.email | string · required for guests | Used for guest lookup (D-18). |
| contact.phone | string · optional | E.164; also usable for guest lookup. |

**Response — created (201)**

```http
HTTP/1.1 201 Created

{
  "data": {
    "checkout_session_id": "chk_9001",
    "cart_type": "normal",
    "status": "open",
    "expires_at": "2026-09-17T12:30:00Z",
    "reservation": {
      "id": "res_7001",
      "status": "active",
      "expires_at": "2026-09-17T12:30:00Z"
    },
    "fulfillment": {
      "mode": "shipping",
      "location_id": "loc_albany",
      "address": {
        "line1": "100 Main Street",
        "line2": "Suite 4",
        "city": "Albany",
        "state": "NY",
        "postal_code": "12201",
        "country": "US"
      },
      "shipping_method_id": "ship_ground"
    },
    "quote": {
      "quote_version": "quote_04",
      "subtotal": {
        "amount_minor": 27000,
        "currency": "USD"
      },
      "discount": {
        "amount_minor": 1000,
        "currency": "USD"
      },
      "tax": {
        "amount_minor": 2080,
        "currency": "USD"
      },
      "shipping": {
        "amount_minor": 1800,
        "currency": "USD"
      },
      "total": {
        "amount_minor": 29880,
        "currency": "USD"
      },
      "lines": [
        {
          "product_id": "prod_201",
          "quantity": 1,
          "unit_price": {
            "amount_minor": 3000,
            "currency": "USD"
          },
          "discount": {
            "amount_minor": 0,
            "currency": "USD"
          },
          "tax": {
            "amount_minor": 240,
            "currency": "USD"
          },
          "line_total": {
            "amount_minor": 3000,
            "currency": "USD"
          }
        }
      ],
      "ship_free_12": {
        "qualified": false,
        "reason_code": "NON_QUALIFYING_PRODUCT_IN_CART"
      },
      "rapid_ship": {
        "enabled": true,
        "eligible_product_ids": [
          "prod_201"
        ],
        "cutoff_local_time": "12:00",
        "location_timezone": "America/New_York",
        "dispatch_promise": "same_day",
        "delivery_target_days": 2
      },
      "promotions_applied": [
        {
          "promotion_id": "promo_5",
          "version": 3,
          "name": "Welcome 10%",
          "discount": {
            "amount_minor": 1000,
            "currency": "USD"
          }
        }
      ]
    },
    "fulfillment_groups": [
      {
        "kind": "rapid_ship",
        "location_id": "loc_albany",
        "product_ids": [
          "prod_201"
        ]
      },
      {
        "kind": "standard",
        "location_id": "loc_albany",
        "product_ids": [
          "prod_202"
        ]
      }
    ],
    "compliance": {
      "age_gate_required": true,
      "adult_signature_required": true,
      "pre_arrival_terms_required": false,
      "pre_arrival_terms_accepted": false
    },
    "payment": {
      "payment_reference": null,
      "status": null
    },
    "next_allowed_actions": [
      "set_fulfillment",
      "requote",
      "create_payment_intent"
    ]
  },
  "request_id": "req_01J9Z8",
  "correlation_id": "cor_01J9Z8"
}
```

**Response keys**

| Key | Type / allowed values | Description |
|---|---|---|
| checkout_session_id | string |  |
| cart_type | enum | `normal` \| `pre_arrival`. |
| status | enum | `open` \| `payment_pending` \| `completed` \| `expired`. |
| expires_at | ISO 8601 UTC | After this → CHECKOUT_EXPIRED; reservation released. |
| reservation | object \| null | `{ id, status: active\|committed\|released\|expired, expires_at }`. Null for pre-arrival sessions (no physical stock). |
| fulfillment.mode | enum | `shipping` \| `pickup` \| `curbside` \| `local_delivery` (gate). |
| fulfillment_groups[].kind | enum | `rapid_ship` \| `standard` \| `pre_arrival`. Informational; the customer still pays one shipping charge. |
| compliance.age_gate_required | boolean |  |
| compliance.adult_signature_required | boolean | Shipping of alcohol (D-35). |
| compliance.pre_arrival_terms_required / accepted | boolean | Pre-arrival sessions only. |
| payment.status | enum \| null | Payment status (see GET /payments/{ref}). |
| next_allowed_actions[] | enum[] | `set_fulfillment` \| `accept_pre_arrival_terms` \| `requote` \| `create_payment_intent` \| `place_order` \| `restart`. |
| quote.quote_version | string | Send back on place-order. Any change creates a new version. |
| quote.subtotal / discount / tax / shipping / total | Money object `{ amount_minor: integer, currency: "USD" }` | Authoritative. `shipping` is one combined charge for the order (D-12). |
| quote.lines[] | object[] | Per-line price, discount, tax, total. |
| quote.ship_free_12.qualified | boolean | Whole-cart result (D-11). |
| quote.ship_free_12.reason_code | enum | `QUALIFIED` \| `NON_QUALIFYING_PRODUCT_IN_CART` \| `QUANTITY_NOT_MULTIPLE_OF_12` \| `PROGRAM_DISABLED` \| `DESTINATION_NOT_ELIGIBLE`. |
| quote.rapid_ship.enabled | boolean | `false` with `reason_code: RAPID_SHIP_UNAVAILABLE` when disabled (e.g. weekend fulfillment off); lines ship normally. |
| quote.rapid_ship.eligible_product_ids | string[] | Lines getting Rapid Ship. |
| quote.rapid_ship.dispatch_promise | enum | `same_day` (before cutoff) \| `next_day` (after cutoff). |
| quote.rapid_ship.delivery_target_days | integer | `2`. |
| quote.promotions_applied[] | object[] | Promotion ID + version snapshotted on the order. |

**Errors**

| Code | HTTP | When |
|---|---|---|
| `VALIDATION_ERROR` | 422 | Missing cart_type or empty sub-cart |
| `INVENTORY_CONFLICT` | 409 | Stock changed |
| `SHIPPING_DESTINATION_NOT_ELIGIBLE` | 422 | Destination blocked (D-21) |
| `FULFILLMENT_NOT_ELIGIBLE` | 422 | Selected fulfillment mode isn't allowed for this cart/location/time. |
| `IDEMPOTENCY_KEY_REUSED` | 409 | Same `Idempotency-Key` sent with a different request body. |

**Notes**

- Pre-arrival session: `reservation` is null, `compliance.pre_arrival_terms_required = true`, quote includes `pre_arrival: { estimated_delivery: "2026-12-08", terms_version: "pa-terms-2026-09" }` and no ship_free_12 / rapid_ship blocks.

---

### 3.2 `GET /checkout/sessions/{checkoutSessionId}`

**Get checkout session**

| Property | Value |
|---|---|
| Endpoint | `GET /api/v1/checkout/sessions/{checkoutSessionId}` |
| Purpose | Authoritative state; used to recover after a browser close or timeout. |
| Auth | Session owner (customer or guest proof) |
| Status | CONFIRMED |
| CSRF header | Not required |
| Idempotency-Key | Not used |
| Rate limited | No |

**Path parameters**

| Key | Type / allowed values | Description |
|---|---|---|
| checkoutSessionId | string |  |

**Request example**

```http
GET /api/v1/checkout/sessions/{checkoutSessionId} HTTP/1.1
Cookie: access_token=<jwt>; refresh_token=<opaque>; XSRF-TOKEN=<token>
```

**Response — success (200)**

```http
HTTP/1.1 200 OK

{
  "data": {
    "checkout_session_id": "chk_9001",
    "cart_type": "normal",
    "status": "open",
    "expires_at": "2026-09-17T12:30:00Z",
    "reservation": {
      "id": "res_7001",
      "status": "active",
      "expires_at": "2026-09-17T12:30:00Z"
    },
    "fulfillment": {
      "mode": "shipping",
      "location_id": "loc_albany",
      "address": {
        "line1": "100 Main Street",
        "line2": "Suite 4",
        "city": "Albany",
        "state": "NY",
        "postal_code": "12201",
        "country": "US"
      },
      "shipping_method_id": "ship_ground"
    },
    "quote": {
      "quote_version": "quote_04",
      "subtotal": {
        "amount_minor": 27000,
        "currency": "USD"
      },
      "discount": {
        "amount_minor": 1000,
        "currency": "USD"
      },
      "tax": {
        "amount_minor": 2080,
        "currency": "USD"
      },
      "shipping": {
        "amount_minor": 1800,
        "currency": "USD"
      },
      "total": {
        "amount_minor": 29880,
        "currency": "USD"
      },
      "lines": [
        {
          "product_id": "prod_201",
          "quantity": 1,
          "unit_price": {
            "amount_minor": 3000,
            "currency": "USD"
          },
          "discount": {
            "amount_minor": 0,
            "currency": "USD"
          },
          "tax": {
            "amount_minor": 240,
            "currency": "USD"
          },
          "line_total": {
            "amount_minor": 3000,
            "currency": "USD"
          }
        }
      ],
      "ship_free_12": {
        "qualified": false,
        "reason_code": "NON_QUALIFYING_PRODUCT_IN_CART"
      },
      "rapid_ship": {
        "enabled": true,
        "eligible_product_ids": [
          "prod_201"
        ],
        "cutoff_local_time": "12:00",
        "location_timezone": "America/New_York",
        "dispatch_promise": "same_day",
        "delivery_target_days": 2
      },
      "promotions_applied": [
        {
          "promotion_id": "promo_5",
          "version": 3,
          "name": "Welcome 10%",
          "discount": {
            "amount_minor": 1000,
            "currency": "USD"
          }
        }
      ]
    },
    "fulfillment_groups": [
      {
        "kind": "rapid_ship",
        "location_id": "loc_albany",
        "product_ids": [
          "prod_201"
        ]
      },
      {
        "kind": "standard",
        "location_id": "loc_albany",
        "product_ids": [
          "prod_202"
        ]
      }
    ],
    "compliance": {
      "age_gate_required": true,
      "adult_signature_required": true,
      "pre_arrival_terms_required": false,
      "pre_arrival_terms_accepted": false
    },
    "payment": {
      "payment_reference": null,
      "status": null
    },
    "next_allowed_actions": [
      "set_fulfillment",
      "requote",
      "create_payment_intent"
    ]
  },
  "request_id": "req_01J9Z8",
  "correlation_id": "cor_01J9Z8"
}
```

**Response keys**

| Key | Type / allowed values | Description |
|---|---|---|
| (all) | CheckoutSession | See POST /checkout/sessions. |

**Errors**

| Code | HTTP | When |
|---|---|---|
| `FORBIDDEN` | 403 | Not the owner |
| `RESOURCE_NOT_FOUND` | 404 | Resource doesn't exist or isn't visible to the caller (never reveals existence). |

---

### 3.3 `POST /checkout/sessions/{checkoutSessionId}/quote`

**Re-quote**

| Property | Value |
|---|---|
| Endpoint | `POST /api/v1/checkout/sessions/{checkoutSessionId}/quote` |
| Auth | Session owner |
| Status | CONFIRMED |
| CSRF header | Required |
| Idempotency-Key | Required |
| Rate limited | No |

**Path parameters**

| Key | Type / allowed values | Description |
|---|---|---|
| checkoutSessionId | string |  |

**Request example**

```http
POST /api/v1/checkout/sessions/{checkoutSessionId}/quote HTTP/1.1
Cookie: access_token=<jwt>; refresh_token=<opaque>; XSRF-TOKEN=<token>
X-XSRF-TOKEN: <token from XSRF-TOKEN cookie>
Idempotency-Key: 6f1c2a4e-8d3b-4f6a-9c1e-2b7d5e8f0a13
```

**Response — success (200)**

```http
HTTP/1.1 200 OK

{
  "data": {
    "checkout_session_id": "chk_9001",
    "quote": {
      "quote_version": "quote_05",
      "subtotal": {
        "amount_minor": 27000,
        "currency": "USD"
      },
      "discount": {
        "amount_minor": 1000,
        "currency": "USD"
      },
      "tax": {
        "amount_minor": 2080,
        "currency": "USD"
      },
      "shipping": {
        "amount_minor": 1800,
        "currency": "USD"
      },
      "total": {
        "amount_minor": 29880,
        "currency": "USD"
      },
      "lines": [
        {
          "product_id": "prod_201",
          "quantity": 1,
          "unit_price": {
            "amount_minor": 3000,
            "currency": "USD"
          },
          "discount": {
            "amount_minor": 0,
            "currency": "USD"
          },
          "tax": {
            "amount_minor": 240,
            "currency": "USD"
          },
          "line_total": {
            "amount_minor": 3000,
            "currency": "USD"
          }
        }
      ],
      "ship_free_12": {
        "qualified": false,
        "reason_code": "NON_QUALIFYING_PRODUCT_IN_CART"
      },
      "rapid_ship": {
        "enabled": true,
        "eligible_product_ids": [
          "prod_201"
        ],
        "cutoff_local_time": "12:00",
        "location_timezone": "America/New_York",
        "dispatch_promise": "same_day",
        "delivery_target_days": 2
      },
      "promotions_applied": [
        {
          "promotion_id": "promo_5",
          "version": 3,
          "name": "Welcome 10%",
          "discount": {
            "amount_minor": 1000,
            "currency": "USD"
          }
        }
      ]
    }
  },
  "request_id": "req_01J9Z8",
  "correlation_id": "cor_01J9Z8"
}
```

**Response keys**

| Key | Type / allowed values | Description |
|---|---|---|
| quote.quote_version | string | Send back on place-order. Any change creates a new version. |
| quote.subtotal / discount / tax / shipping / total | Money object `{ amount_minor: integer, currency: "USD" }` | Authoritative. `shipping` is one combined charge for the order (D-12). |
| quote.lines[] | object[] | Per-line price, discount, tax, total. |
| quote.ship_free_12.qualified | boolean | Whole-cart result (D-11). |
| quote.ship_free_12.reason_code | enum | `QUALIFIED` \| `NON_QUALIFYING_PRODUCT_IN_CART` \| `QUANTITY_NOT_MULTIPLE_OF_12` \| `PROGRAM_DISABLED` \| `DESTINATION_NOT_ELIGIBLE`. |
| quote.rapid_ship.enabled | boolean | `false` with `reason_code: RAPID_SHIP_UNAVAILABLE` when disabled (e.g. weekend fulfillment off); lines ship normally. |
| quote.rapid_ship.eligible_product_ids | string[] | Lines getting Rapid Ship. |
| quote.rapid_ship.dispatch_promise | enum | `same_day` (before cutoff) \| `next_day` (after cutoff). |
| quote.rapid_ship.delivery_target_days | integer | `2`. |
| quote.promotions_applied[] | object[] | Promotion ID + version snapshotted on the order. |

**Errors**

| Code | HTTP | When |
|---|---|---|
| `CHECKOUT_EXPIRED` | 409 | Checkout session expired; start a new session. |
| `INVENTORY_CONFLICT` | 409 | Requested quantity no longer available at the fulfilling location. |

---

### 3.4 `POST /checkout/sessions/{checkoutSessionId}/fulfillment`

**Set fulfillment and address**

| Property | Value |
|---|---|
| Endpoint | `POST /api/v1/checkout/sessions/{checkoutSessionId}/fulfillment` |
| Auth | Session owner |
| Status | CONFIRMED |
| CSRF header | Required |
| Idempotency-Key | Required |
| Rate limited | No |

**Path parameters**

| Key | Type / allowed values | Description |
|---|---|---|
| checkoutSessionId | string |  |

**Request example**

```http
POST /api/v1/checkout/sessions/{checkoutSessionId}/fulfillment HTTP/1.1
Content-Type: application/json
Cookie: access_token=<jwt>; refresh_token=<opaque>; XSRF-TOKEN=<token>
X-XSRF-TOKEN: <token from XSRF-TOKEN cookie>
Idempotency-Key: 6f1c2a4e-8d3b-4f6a-9c1e-2b7d5e8f0a13

{
  "mode": "shipping",
  "location_id": "loc_albany",
  "address": {
    "line1": "100 Main Street",
    "line2": "Suite 4",
    "city": "Albany",
    "state": "NY",
    "postal_code": "12201",
    "country": "US"
  },
  "shipping_method_id": "ship_ground"
}
```

Pickup example: `{ "mode": "pickup", "location_id": "loc_albany", "pickup_window_id": "pw_1001" }`.

**Request keys**

| Key | Type / allowed values | Description |
|---|---|---|
| mode | enum · required | `shipping` \| `pickup` \| `curbside` \| `local_delivery` (gate → disabled response). |
| location_id | string · required | Fulfilling store. |
| address | Address · required for shipping |  |
| line1 | string · required | Street address. |
| line2 | string · optional | Apartment, suite, unit. |
| city | string · required |  |
| state | string · required | 2-letter US state code, e.g. `NY`. |
| postal_code | string · required | 5-digit or ZIP+4. |
| country | string · required | ISO 3166-1 alpha-2. `US` only in V1. |
| shipping_method_id | string · required for shipping | From GET /shipping/methods. |
| pickup_window_id | string · required for pickup/curbside | From GET /pickup/windows. |
| vehicle | object · optional (curbside) | `{ description, plate }`. |

**Response — success (200)**

```http
HTTP/1.1 200 OK

{
  "data": {
    "checkout_session_id": "chk_9001",
    "cart_type": "normal",
    "status": "open",
    "expires_at": "2026-09-17T12:30:00Z",
    "reservation": {
      "id": "res_7001",
      "status": "active",
      "expires_at": "2026-09-17T12:30:00Z"
    },
    "fulfillment": {
      "mode": "shipping",
      "location_id": "loc_albany",
      "address": {
        "line1": "100 Main Street",
        "line2": "Suite 4",
        "city": "Albany",
        "state": "NY",
        "postal_code": "12201",
        "country": "US"
      },
      "shipping_method_id": "ship_ground"
    },
    "quote": {
      "quote_version": "quote_04",
      "subtotal": {
        "amount_minor": 27000,
        "currency": "USD"
      },
      "discount": {
        "amount_minor": 1000,
        "currency": "USD"
      },
      "tax": {
        "amount_minor": 2080,
        "currency": "USD"
      },
      "shipping": {
        "amount_minor": 1800,
        "currency": "USD"
      },
      "total": {
        "amount_minor": 29880,
        "currency": "USD"
      },
      "lines": [
        {
          "product_id": "prod_201",
          "quantity": 1,
          "unit_price": {
            "amount_minor": 3000,
            "currency": "USD"
          },
          "discount": {
            "amount_minor": 0,
            "currency": "USD"
          },
          "tax": {
            "amount_minor": 240,
            "currency": "USD"
          },
          "line_total": {
            "amount_minor": 3000,
            "currency": "USD"
          }
        }
      ],
      "ship_free_12": {
        "qualified": false,
        "reason_code": "NON_QUALIFYING_PRODUCT_IN_CART"
      },
      "rapid_ship": {
        "enabled": true,
        "eligible_product_ids": [
          "prod_201"
        ],
        "cutoff_local_time": "12:00",
        "location_timezone": "America/New_York",
        "dispatch_promise": "same_day",
        "delivery_target_days": 2
      },
      "promotions_applied": [
        {
          "promotion_id": "promo_5",
          "version": 3,
          "name": "Welcome 10%",
          "discount": {
            "amount_minor": 1000,
            "currency": "USD"
          }
        }
      ]
    },
    "fulfillment_groups": [
      {
        "kind": "rapid_ship",
        "location_id": "loc_albany",
        "product_ids": [
          "prod_201"
        ]
      },
      {
        "kind": "standard",
        "location_id": "loc_albany",
        "product_ids": [
          "prod_202"
        ]
      }
    ],
    "compliance": {
      "age_gate_required": true,
      "adult_signature_required": true,
      "pre_arrival_terms_required": false,
      "pre_arrival_terms_accepted": false
    },
    "payment": {
      "payment_reference": null,
      "status": null
    },
    "next_allowed_actions": [
      "set_fulfillment",
      "requote",
      "create_payment_intent"
    ]
  },
  "request_id": "req_01J9Z8",
  "correlation_id": "cor_01J9Z8"
}
```

**Response keys**

| Key | Type / allowed values | Description |
|---|---|---|
| (all) | CheckoutSession | Updated quote and fulfillment groups. |

**Errors**

| Code | HTTP | When |
|---|---|---|
| `SHIPPING_DESTINATION_NOT_ELIGIBLE` | 422 | Destination state/address isn't eligible for shipping. |
| `ADULT_SIGNATURE_REQUIRED` | 422 | Method lacks adult signature |
| `FULFILLMENT_NOT_ELIGIBLE` | 422 | Selected fulfillment mode isn't allowed for this cart/location/time. |
| `VALIDATION_ERROR` | 422 | Request failed validation. `details.fields` maps field → messages. |
| `CHECKOUT_EXPIRED` | 409 | Checkout session expired; start a new session. |
| `PROVIDER_UNAVAILABLE` | 503 | Rates unavailable |

---

### 3.5 `POST /checkout/sessions/{checkoutSessionId}/pre-arrival-terms`

**Accept Pre-Arrival Terms**

| Property | Value |
|---|---|
| Endpoint | `POST /api/v1/checkout/sessions/{checkoutSessionId}/pre-arrival-terms` |
| Purpose | Records explicit acceptance (terms version + timestamp). Required before place-order on pre-arrival sessions (D-23). |
| Auth | Session owner |
| Status | CONFIRMED |
| CSRF header | Required |
| Idempotency-Key | Required |
| Rate limited | No |

**Path parameters**

| Key | Type / allowed values | Description |
|---|---|---|
| checkoutSessionId | string |  |

**Request example**

```http
POST /api/v1/checkout/sessions/{checkoutSessionId}/pre-arrival-terms HTTP/1.1
Content-Type: application/json
Cookie: access_token=<jwt>; refresh_token=<opaque>; XSRF-TOKEN=<token>
X-XSRF-TOKEN: <token from XSRF-TOKEN cookie>
Idempotency-Key: 6f1c2a4e-8d3b-4f6a-9c1e-2b7d5e8f0a13

{
  "accepted": true,
  "terms_version": "pa-terms-2026-09"
}
```

**Request keys**

| Key | Type / allowed values | Description |
|---|---|---|
| accepted | boolean · required | Must be `true`. |
| terms_version | string · required | Version shown to the customer; must match the current version. |

**Response — success (200)**

```http
HTTP/1.1 200 OK

{
  "data": {
    "checkout_session_id": "chk_9101",
    "pre_arrival_terms_accepted": true,
    "terms_version": "pa-terms-2026-09",
    "accepted_at": "2026-09-17T16:10:00Z"
  },
  "request_id": "req_01J9Z8",
  "correlation_id": "cor_01J9Z8"
}
```

**Response keys**

| Key | Type / allowed values | Description |
|---|---|---|
| accepted_at | ISO 8601 UTC | Stored with session and copied to the order. |

**Errors**

| Code | HTTP | When |
|---|---|---|
| `VALIDATION_ERROR` | 422 | Normal session, `accepted` false, or stale version |
| `CHECKOUT_EXPIRED` | 409 | Checkout session expired; start a new session. |

---

### 3.6 `POST /checkout/sessions/{checkoutSessionId}/payment-intent`

**Create payment**

| Property | Value |
|---|---|
| Endpoint | `POST /api/v1/checkout/sessions/{checkoutSessionId}/payment-intent` |
| Auth | Session owner |
| Status | CONFIRMED (provider CONFIGURATION GATE) |
| CSRF header | Required |
| Idempotency-Key | Required |
| Rate limited | No |

**Path parameters**

| Key | Type / allowed values | Description |
|---|---|---|
| checkoutSessionId | string |  |

**Request example**

```http
POST /api/v1/checkout/sessions/{checkoutSessionId}/payment-intent HTTP/1.1
Content-Type: application/json
Cookie: access_token=<jwt>; refresh_token=<opaque>; XSRF-TOKEN=<token>
X-XSRF-TOKEN: <token from XSRF-TOKEN cookie>
Idempotency-Key: 6f1c2a4e-8d3b-4f6a-9c1e-2b7d5e8f0a13

{
  "payment_method_token": "provider_token_from_hosted_fields",
  "payment_method_type": "card",
  "amount": {
    "amount_minor": 29880,
    "currency": "USD"
  },
  "gift_card_codes": [],
  "apply_store_credit": {
    "amount_minor": 0,
    "currency": "USD"
  }
}
```

**Request keys**

| Key | Type / allowed values | Description |
|---|---|---|
| payment_method_token | string · required for card | Token from the provider's hosted fields. Raw card data is never sent. |
| payment_method_type | enum · required | `card` \| `gift_card` \| `store_credit` (split allowed). |
| amount | Money object `{ amount_minor: integer, currency: "USD" }` · required | Must equal the current quote total, else QUOTE_STALE. |
| gift_card_codes[] | string[] · optional |  |
| apply_store_credit | Money · optional | Signed-in customers only. |

**Response — success (200)**

```http
HTTP/1.1 200 OK

{
  "data": {
    "payment_reference": "pay_9001",
    "status": "pending",
    "next_action": "poll",
    "amount": {
      "amount_minor": 29880,
      "currency": "USD"
    }
  },
  "request_id": "req_01J9Z8",
  "correlation_id": "cor_01J9Z8"
}
```

**Response keys**

| Key | Type / allowed values | Description |
|---|---|---|
| status | enum | `pending` \| `authorized` \| `captured` \| `failed`. |
| next_action | enum | `poll` \| `redirect` (3-D Secure) \| `none`. |
| redirect_url | string · when next_action=redirect | Provider challenge page. |

**Errors**

| Code | HTTP | When |
|---|---|---|
| `PAYMENT_FAILED` | 402 | Provider declined or failed the payment. |
| `PAYMENT_PENDING` | 202 | Provider hasn't confirmed the payment yet; poll the payment status. |
| `PAYMENT_RECONCILIATION_REQUIRED` | 409 | Payment state is ambiguous; staff reconciliation is required. |
| `QUOTE_STALE` | 409 | Cart, prices, promotions or fulfillment changed since the last quote; re-quote. |
| `PROVIDER_UNAVAILABLE` | 503 | External provider unreachable or not configured; safe to retry later. |
| `CHECKOUT_EXPIRED` | 409 | Checkout session expired; start a new session. |

---

### 3.7 `GET /payments/{paymentReference}`

**Poll payment**

| Property | Value |
|---|---|
| Endpoint | `GET /api/v1/payments/{paymentReference}` |
| Auth | Session owner |
| Status | CONFIRMED |
| CSRF header | Not required |
| Idempotency-Key | Not used |
| Rate limited | No |

**Path parameters**

| Key | Type / allowed values | Description |
|---|---|---|
| paymentReference | string |  |

**Request example**

```http
GET /api/v1/payments/{paymentReference} HTTP/1.1
Cookie: access_token=<jwt>; refresh_token=<opaque>; XSRF-TOKEN=<token>
```

**Response — success (200)**

```http
HTTP/1.1 200 OK

{
  "data": {
    "payment_reference": "pay_9001",
    "status": "captured",
    "order_id": "ord_5001",
    "amount": {
      "amount_minor": 29880,
      "currency": "USD"
    }
  },
  "request_id": "req_01J9Z8",
  "correlation_id": "cor_01J9Z8"
}
```

**Response keys**

| Key | Type / allowed values | Description |
|---|---|---|
| status | enum | `created` \| `pending` \| `authorized` \| `captured` \| `failed` \| `reversed` \| `partially_refunded` \| `refunded` \| `disputed`. |
| order_id | string \| null | Set once the order exists. |

**Errors**

| Code | HTTP | When |
|---|---|---|
| `FORBIDDEN` | 403 | Authenticated, but missing permission, location scope, manager approval, or a valid webhook signature. |
| `RESOURCE_NOT_FOUND` | 404 | Resource doesn't exist or isn't visible to the caller (never reveals existence). |

---

### 3.8 `POST /checkout/sessions/{checkoutSessionId}/place-order`

**Place order**

| Property | Value |
|---|---|
| Endpoint | `POST /api/v1/checkout/sessions/{checkoutSessionId}/place-order` |
| Auth | Session owner |
| Status | CONFIRMED |
| CSRF header | Required |
| Idempotency-Key | Required |
| Rate limited | No |

**Path parameters**

| Key | Type / allowed values | Description |
|---|---|---|
| checkoutSessionId | string |  |

**Request example**

```http
POST /api/v1/checkout/sessions/{checkoutSessionId}/place-order HTTP/1.1
Content-Type: application/json
Cookie: access_token=<jwt>; refresh_token=<opaque>; XSRF-TOKEN=<token>
X-XSRF-TOKEN: <token from XSRF-TOKEN cookie>
Idempotency-Key: 6f1c2a4e-8d3b-4f6a-9c1e-2b7d5e8f0a13

{
  "payment_reference": "pay_9001",
  "quote_version": "quote_04",
  "customer_note": "Please leave at the front desk."
}
```

**Request keys**

| Key | Type / allowed values | Description |
|---|---|---|
| payment_reference | string · required |  |
| quote_version | string · required | Must be the latest version. |
| customer_note | string · optional · max 500 |  |

**Response — created (201)**

```http
HTTP/1.1 201 Created

{
  "data": {
    "order_id": "ord_5001",
    "confirmation_number": "OW-5001",
    "order_type": "normal",
    "status": "paid",
    "payment_status": "captured",
    "fulfillment_status": "unassigned",
    "total": {
      "amount_minor": 29880,
      "currency": "USD"
    },
    "cancellation_eligible_until": "2026-09-18T16:00:00Z"
  },
  "request_id": "req_01J9Z8",
  "correlation_id": "cor_01J9Z8"
}
```

**Response keys**

| Key | Type / allowed values | Description |
|---|---|---|
| status | enum | `paid` when captured; `pending_payment` when still pending (stock stays reserved, allocated only on capture). |
| order_type | enum | `normal` \| `pre_arrival`. |
| cancellation_eligible_until | ISO 8601 UTC \| null | Normal orders: placed_at + 24h (still void once picked). Null for pre-arrival. |

**Errors**

| Code | HTTP | When |
|---|---|---|
| `PRE_ARRIVAL_TERMS_MISSING` | 422 | Pre-arrival session without accepted terms |
| `PAYMENT_FAILED` | 402 | Provider declined or failed the payment. |
| `PAYMENT_RECONCILIATION_REQUIRED` | 409 | Payment state is ambiguous; staff reconciliation is required. |
| `INVENTORY_CONFLICT` | 409 | Requested quantity no longer available at the fulfilling location. |
| `CHECKOUT_EXPIRED` | 409 | Checkout session expired; start a new session. |
| `QUOTE_STALE` | 409 | Cart, prices, promotions or fulfillment changed since the last quote; re-quote. |
| `PAYMENT_ALREADY_CAPTURED` | 409 | Order already exists for this payment — load it |

**Error example — PRE_ARRIVAL_TERMS_MISSING**

```http
HTTP/1.1 422 Unprocessable Entity

{
  "error": {
    "code": "PRE_ARRIVAL_TERMS_MISSING",
    "message": "Pre-Arrival Terms must be accepted before placing this order.",
    "details": {
      "checkout_session_id": "chk_9101",
      "terms_version": "pa-terms-2026-09"
    },
    "retryable": false
  },
  "request_id": "req_01J9Z8",
  "correlation_id": "cor_01J9Z8"
}
```

---
