Orders — customer reads + transfers
The authenticated customer's order surface: list, detail (items +
fulfillments with tracking + addresses) and order transfers. There is no
anonymous order read — every endpoint on this page requires a customer
session (authorization: Bearer <jwt>, obtained via the passwordless code
flow — see auth.md); a guest's only order handle is the
completeCart() response (checkout.md). Missing/invalid
JWT → 401 {code: "unauthenticated"} — demonstrated executably below;
happy-path shapes are shown as jsonc (a customer session is admin-owned
state on the shared tenant; the authenticated paths are proven by
tests/store/customer-accounts-ownership.test.ts and the transfer suite).
Auth for every endpoint on this page: x-client-id + Bearer JWT.
SDK module: @barter/storefront/api/orders.
GET /api/store/orders/display/:displayId — my order by its human number
- Purpose — resolve "order #42" (the number customers see in emails and confirmations) to the full order detail; account order pages and support links use this instead of the opaque id.
- Auth — Bearer JWT required, same ownership scope as the id read:
another customer's number is
404 not_found.display_idis a guessable autoincrement — that is exactly why there is no anonymous lookup. - Request —
GET, no query. A numeric segment matches the autoincrementdisplay_idOR a purely-numericcustom_display_id(custom wins ties — it is the number the store actually showed); any other segment matchescustom_display_idonly. - Response — identical
{order}detail shape toGET /api/store/orders/:idabove. - Errors — 401
unauthenticated; 404not_found. - SDK —
orders.retrieveOrderByDisplayId(client, displayId). - Components — order-confirmation deep links, account order detail.
- Settings —
custom_display_idis set by merchants/imports (admin surface); absent it, only the autoincrement resolves.
STATUS=$(curl -s -o /dev/null -w '%{http_code}' \
"$BASE/api/store/orders/display/42" -H "x-client-id: $CLIENT_ID")
test "$STATUS" = 401GET /api/store/orders — list my orders
- Purpose — the account "My orders" list, newest first.
- Auth — Bearer JWT required. Scoped to the session customer AND the tenant — another customer's orders are invisible (not 403).
- Request —
GET ?limit=&offset=&status=—limit1–200 (default 20),offset≥ 0 (default 0),statusexact-match filter (pending,completed,canceled, …). - Response — plain order rows (NO embeds — fetch the detail for items/tracking):
{
"orders": [{
"id": "order_…",
"display_id": 42, // human-facing autoincrement
"status": "pending",
"email": "jane@example.com",
"currency_code": "eur",
"customer_id": "cus_…",
"sales_channel_id": null,
"region_id": "reg_…",
"metadata": {}, // internal staff notes NEVER appear here
"created_at": "2026-07-20T…", "updated_at": "2026-07-20T…"
}],
"count": 1, "offset": 0, "limit": 20
}- Errors — 401
unauthenticated; 400validation_failed(bad limit/offset). - SDK —
orders.listOrders(client, {limit, offset, status}). - Components — account order-history page.
- Settings — none (ownership is structural).
# Executable auth contract: no Bearer → 401 unauthenticated.
STATUS=$(curl -s -o /dev/null -w '%{http_code}' \
"$BASE/api/store/orders" -H "x-client-id: $CLIENT_ID")
test "$STATUS" = 401GET /api/store/orders/:id — my order, in full
- Purpose — the order-detail page: items, fulfillment lifecycle + tracking, both addresses.
- Auth — Bearer JWT required. Ownership is part of the lookup: an
unknown id and another customer's order are BOTH
404 not_found(indistinguishable — no existence oracle). - Request —
GET, no query. - Response — a store-safe subset of the admin order select (no customer row, no payment internals, no internal timeline comments):
{
"order": {
"id": "order_…", "display_id": 42, "status": "pending",
"email": "jane@example.com", "currency_code": "eur",
"items": [{ // version pivot + embedded line item
"id": "oi_…", "order_id": "order_…", "quantity": 1,
"line_item": {
"id": "oli_…", "title": "M", "product_title": "Linen Shirt",
"product_handle": "linen-shirt", "thumbnail": "https://…",
"variant_id": "variant_…", "variant_sku": "LIN-SHIRT-M",
"unit_price": 45, "metadata": null
}
}],
"fulfillments": [{ // couriers surface
"fulfillment": {
"id": "ful_…",
"packed_at": "2026-07-20T…", "shipped_at": null,
"delivered_at": null, "canceled_at": null,
"labels": [{ "tracking_number": "…", "tracking_url": "https://…" }]
}
}],
"shipping_address": { "first_name": "Jane", "city": "Sofia", "…": "order_addresses row" },
"billing_address": { "…": "order_addresses row" }
}
}- Errors — 401
unauthenticated; 404not_found. - SDK —
orders.retrieveOrder(client, orderId). - Components — order-detail page, tracking widget.
- Settings — fulfillment/tracking data appears as the merchant packs and ships (admin fulfillment flow + carrier labels).
STATUS=$(curl -s -o /dev/null -w '%{http_code}' \
"$BASE/api/store/orders/order_any" -H "x-client-id: $CLIENT_ID")
test "$STATUS" = 401Order transfers — claim a (guest) order into my account
Flow: the claiming customer calls request on an order that is not
theirs → a one-time token is stored for the order's email holder (email
dispatch pending server-side) → the claimer, whose account email must
EQUAL the order's email, calls accept with the token. decline (the
recipient rejects, token required) or cancel (the requester withdraws)
end a pending transfer. One pending transfer per order — a duplicate
request acknowledges with the same {transfer: {requested: true}} shape
without creating another. The token never crosses this surface — it is
email-delivered only (pinned by
tests/store/order-transfer-token-leak.test.ts).
POST /api/store/orders/:id/transfer/request
- Request —
{description?}. - Response —
200 {"order":{"id":"order_…","transfer":{"requested":true}}}(or the existing pending action object on a duplicate request). - Errors — 401
unauthenticated; 404not_found; 400already_owned(the order already belongs to the caller). - SDK —
orders.requestOrderTransfer(client, orderId, {description}).
POST /api/store/orders/:id/transfer/accept
- Request —
{token}(≥16 chars, from the transfer email). - Response —
200 {order}— the full updated order row, now owned by the caller. - Errors — 401
unauthenticated; 404not_found(order / no pending transfer); 403invalid_token|email_mismatch(the caller's email must match the order's original email — a leaked token alone is not enough); 400validation_failed. - SDK —
orders.acceptOrderTransfer(client, orderId, {token}).
POST /api/store/orders/:id/transfer/decline
- Request —
{token}. - Response —
200 {"order":{"id":"order_…","transfer":{"declined":true}}}. - Errors — 401
unauthenticated; 404not_found; 403invalid_token; 400validation_failed. - SDK —
orders.declineOrderTransfer(client, orderId, {token}).
POST /api/store/orders/:id/transfer/cancel
- Request — no body.
- Response —
200 {"order":{"id":"order_…","transfer":{"canceled":true}}}. - Errors — 401
unauthenticated; 404not_found; 403forbidden(only the requester may cancel). - SDK —
orders.cancelOrderTransfer(client, orderId). - Components — account "claim this order" flow + the transfer email's accept/decline landing page.
# Executable auth contract for the transfer family (all four Bearer-gated).
# Note the ordering nuance: accept/decline validate the BODY before auth,
# so a missing token 400s even for guests; request/cancel hit auth first.
for EP in transfer/request transfer/cancel; do
STATUS=$(curl -s -o /dev/null -w '%{http_code}' -X POST \
"$BASE/api/store/orders/order_any/$EP" \
-H "x-client-id: $CLIENT_ID" -H "content-type: application/json" -d '{}')
test "$STATUS" = 401
done
for EP in transfer/accept transfer/decline; do
RES=$(curl -s -X POST "$BASE/api/store/orders/order_any/$EP" \
-H "x-client-id: $CLIENT_ID" -H "content-type: application/json" -d '{}')
echo "$RES" | grep -q '"code":"validation_failed"'
STATUS=$(curl -s -o /dev/null -w '%{http_code}' -X POST \
"$BASE/api/store/orders/order_any/$EP" \
-H "x-client-id: $CLIENT_ID" -H "content-type: application/json" \
-d '{"token":"0123456789abcdef0123456789abcdef"}')
test "$STATUS" = 401
doneCleanup / accretion note
This page creates nothing — every executable block is a read/auth-contract probe against nonexistent ids.