Skip to content
chiltepin

Generated from: “Document the public REST API for the orders service.

Orders REST API

Written by an agent from the skill, validated by chiltepin check, rendered by the renderer — shown as generated, 13 September 2026.

DOCUMENTAPI v1

Orders REST API

What a client can call on the orders service, what each call returns, and what to do when it fails.

The orders service exposes one public REST API at https://api.example.com/v1. Every request carries Authorization: Bearer <api_key> and every body is JSON. The version lives in the path; a breaking change ships as /v2 and /v1 stays up for twelve months after.

Every POST takes an Idempotency-Key header. The service stores the first response for a key for 24 hours. A retry with the same key and the same body gets that stored response. A retry with the same key and a different body gets 409. List calls page with an opaque cursor; a page never repeats or skips an order while you walk it.

An order's status is one of the states in order-lifecycle. The API never lets a client set status directly. POST /orders creates the order in PAYMENT_PENDING and POST /orders/{id}/cancel moves it to CANCELLED. Every other transition belongs to payment, warehouse, or carrier events.

SECTION 01 · Note

Assumptions

Note
Auth is a per-merchant API key, not OAuth. Amounts are integer minor units (cents) with an ISO 4217 currency. Payment is captured through the hosted gateway at checkout, so an order reaches PAID without a second API call. A client can cancel only while the order is BACKORDERED or RESERVED; after PACKED the parcel belongs to the carrier. The webhook feed is documented separately.

Create an order

SECTION 02 · API endpoint
POST/v1/ordersBearer api_key

Submit line items and a shipping address. The order starts in PAYMENT_PENDING and the gateway captures the payment asynchronously.

Parameters
NameInTypeDescription
Idempotency-Key requiredheaderstringUnique per attempt. Reuse it on retry to get the original response.
Request body
FieldTypeDescription
items requiredLineItem[]One or more of { sku, qty }. qty is 1..99.
shipping_address requiredAddressname, line1, line2, city, postal_code, country (ISO 3166-1 alpha-2).
currency requiredstringISO 4217 code, for example USD.
payment_method_id requiredstringToken from the hosted payment gateway, prefix pm_.
customer_referencestringFree text the client can search by later, max 64 chars.
Responses
StatusDescription
201Order created; status is PAYMENT_PENDING.
400Malformed JSON or a field fails validation.
401Missing or invalid API key.
409Idempotency-Key reused with a different body.
422A sku is unknown or has no stock (code sku_unknown or out_of_stock).
Example request
{
  "items": [{ "sku": "MUG-BLUE-12OZ", "qty": 2 }],
  "shipping_address": {
    "name": "Ada Lovelace", "line1": "12 Analytical St",
    "city": "London", "postal_code": "N1 9GU", "country": "GB"
  },
  "currency": "GBP",
  "payment_method_id": "pm_9f3k2",
  "customer_reference": "web-checkout-77120"
}
Example response
{
  "id": "ord_01J8Q2ZK4M",
  "status": "PAYMENT_PENDING",
  "currency": "GBP",
  "total_cents": 2400,
  "items": [{ "sku": "MUG-BLUE-12OZ", "qty": 2, "unit_price_cents": 1200 }],
  "created_at": "2026-09-13T10:02:11Z",
  "updated_at": "2026-09-13T10:02:11Z"
}

Fetch one order

SECTION 03 · API endpoint
GET/v1/orders/{order_id}Bearer api_key

Return the current order, including its status and the shipment tracking number once it is SHIPPED.

Parameters
NameInTypeDescription
order_id requiredpathstringOrder id, prefix ord_.
Responses
StatusDescription
200The order.
401Missing or invalid API key.
404No order with this id belongs to the calling merchant.
Example response
{
  "id": "ord_01J8Q2ZK4M",
  "status": "SHIPPED",
  "currency": "GBP",
  "total_cents": 2400,
  "items": [{ "sku": "MUG-BLUE-12OZ", "qty": 2, "unit_price_cents": 1200 }],
  "shipment": { "carrier": "Royal Mail", "tracking_number": "RM123456789GB" },
  "created_at": "2026-09-13T10:02:11Z",
  "updated_at": "2026-09-14T08:41:00Z"
}

List orders

SECTION 04 · API endpoint
GET/v1/ordersBearer api_key

Page through the calling merchant's orders, newest first. Filter by status or by the customer_reference given at creation.

Parameters
NameInTypeDescription
statusquerystringOne lifecycle status, for example RESERVED. Omit for all.
customer_referencequerystringExact match on the value given at creation.
limitqueryintegerPage size 1..100, default 20.
cursorquerystringOpaque token from the previous page's next_cursor.
Responses
StatusDescription
200A page of orders and next_cursor, null on the last page.
400Unknown status value, limit out of range, or a malformed cursor.
401Missing or invalid API key.
Example response
{
  "data": [
    { "id": "ord_01J8Q2ZK4M", "status": "SHIPPED", "total_cents": 2400, "currency": "GBP" },
    { "id": "ord_01J8PXV0TC", "status": "RESERVED", "total_cents": 5900, "currency": "GBP" }
  ],
  "next_cursor": "eyJpZCI6Im9yZF8wMUo4UFhWMFRDIn0"
}

Cancel an order

SECTION 05 · API endpoint
POST/v1/orders/{order_id}/cancelBearer api_key

Move a BACKORDERED or RESERVED order to CANCELLED. The refund runs on its own and the order reaches REFUNDED when it settles.

Parameters
NameInTypeDescription
order_id requiredpathstringOrder id, prefix ord_.
Idempotency-Key requiredheaderstringUnique per attempt.
Request body
FieldTypeDescription
reason requiredstringOne of customer_request, fraud, duplicate.
notestringFree text shown to support, max 500 chars.
Responses
StatusDescription
200Order is now CANCELLED.
401Missing or invalid API key.
404No order with this id belongs to the calling merchant.
409Status is not BACKORDERED or RESERVED (code invalid_transition); the response carries the current status.
Example request
{ "reason": "customer_request", "note": "Ordered the wrong colour" }
Example response
{
  "id": "ord_01J8PXV0TC",
  "status": "CANCELLED",
  "cancelled_at": "2026-09-13T11:15:42Z",
  "refund": { "amount_cents": 5900, "status": "pending" }
}

Change the shipping address

SECTION 06 · API endpoint
PATCH/v1/orders/{order_id}/shipping-addressBearer api_key

Replace the shipping address at any status before PACKED. Once PACKED the label is printed and the address is fixed.

Parameters
NameInTypeDescription
order_id requiredpathstringOrder id, prefix ord_.
Request body
FieldTypeDescription
shipping_address requiredAddressFull replacement; partial updates are not merged.
Responses
StatusDescription
200The order with the new address.
401Missing or invalid API key.
404No order with this id belongs to the calling merchant.
409Order is PACKED or later (code invalid_transition).
422Address fails validation, for example country not served (code address_invalid).
Example request
{
  "shipping_address": {
    "name": "Ada Lovelace", "line1": "4 Ockham Rd",
    "city": "Guildford", "postal_code": "GU1 3AB", "country": "GB"
  }
}

Create, confirm, cancel

The only ordering rule a client must respect is the one below: a retry of POST /orders must reuse the Idempotency-Key, or the customer is charged twice. A cancel is safe to send at any time; the 409 tells the client the order has already moved past the point of no return.

SECTION 07 · Sequence
SEQUENCE
Sequence diagram: 12 messages between 3 actorsClientOrders APIEXTPayment gatewayOPT[client timed out and retries]LOOP[until status leaves PAYMENT_PENDING]ALT[status is BACKORDERED or RESERVED][status is PACKED or later]1POST /v1/orders (Idempotency-Key k1)2authorize pm_9f3k2, 2400 GBP3201 PAYMENT_PENDING4POST /v1/orders (Idempotency-Key k1)5201 replayed, same order id6capture confirmed7GET /v1/orders/ord_01J8Q2ZK4M8200 PAID, then RESERVED9POST /v1/orders/ord_01J8Q2ZK4M/cancel10refund 2400 GBP11200 CANCELLED12409 invalid_transition
Legendcallresponseerrorthe answer the caller getsEXTexternal actorfragment (alt / opt / loop)active
Polling interval: 2 s, back off to 30 sCapture latency: p95 under 4 s

Error codes

Every non-2xx response has the same envelope: { "error": { "code": "...", "message": "...", "details": [] } }. The code is stable and is the value to branch on; message is for logs, not for logic.

SECTION 08 · Comparison
HTTPcodeMeaningClient action
400invalid_requestJSON does not parse or a field fails validation; details names the field.Fix the request. Do not retry as-is.
401unauthorizedAPI key missing, revoked, or for another environment.Check the key and the base URL. Do not retry.
403forbiddenKey is valid but the merchant has no orders scope.Ask the account owner for the scope.
404not_foundNo such order for this merchant.Treat as gone; check the id.
409idempotency_conflictIdempotency-Key reused with a different body.Generate a new key for a new request.
409invalid_transitionThe order's status does not allow this call; details carries the current status.Read the status and stop; the order has moved on.
422sku_unknownA sku in items does not exist.Remove or correct the item.
422out_of_stockA sku has no stock now; details lists the skus.Offer the customer a substitute or a wait.
422address_invalidShipping address fails validation or country is not served.Correct the address.
429rate_limitedMore than 100 requests per second for this key.Wait for Retry-After seconds, then retry.
500internalUnexpected server error; the request was not applied.Retry with the same Idempotency-Key after a backoff.
503unavailablePayment gateway or warehouse unreachable.Retry with the same Idempotency-Key after Retry-After.

Every 5xx is safe to retry with the same Idempotency-Key. No 4xx is.

Terms

SECTION 09 · Glossary
Order
one purchase with its items, address, payment, and a single status.
Line item
one sku and its qty inside an order; the price is fixed at creation.
Status
the order's current lifecycle state, set only by the service.
Idempotency-Key
client-chosen header that makes a POST safe to retry for 24 hours.
Cursor
opaque token that continues a list from where the last page ended.
Merchant
the account an API key belongs to; every order belongs to exactly one.
View the Markdown
```meta
title: Orders REST API
subtitle: What a client can call on the orders service, what each call returns, and what to do when it fails.
tag: API v1
```

The orders service exposes one public REST API at `https://api.example.com/v1`.
Every request carries `Authorization: Bearer <api_key>` and every body is JSON.
The version lives in the path; a breaking change ships as `/v2` and `/v1` stays
up for twelve months after.

Every `POST` takes an `Idempotency-Key` header. The service stores the first
response for a key for 24 hours. A retry with the same key and the same body
gets that stored response. A retry with the same key and a different body gets `409`.
List calls page with an opaque `cursor`; a page never repeats or skips an order
while you walk it.

An order's `status` is one of the states in [order-lifecycle](order-lifecycle.md).
The API never lets a client set `status` directly. `POST /orders` creates the
order in `PAYMENT_PENDING` and `POST /orders/{id}/cancel` moves it to `CANCELLED`.
Every other transition belongs to payment, warehouse, or carrier events.

```callout
tone: note
title: Assumptions
body: "Auth is a per-merchant API key, not OAuth. Amounts are integer minor units (cents) with an ISO 4217 currency. Payment is captured through the hosted gateway at checkout, so an order reaches PAID without a second API call. A client can cancel only while the order is BACKORDERED or RESERVED; after PACKED the parcel belongs to the carrier. The webhook feed is documented separately."
```

## Create an order

```endpoint
method: POST
path: /v1/orders
description: "Submit line items and a shipping address. The order starts in PAYMENT_PENDING and the gateway captures the payment asynchronously."
auth: Bearer api_key
params:
  - { name: Idempotency-Key, in: header, type: string, required: true, desc: "Unique per attempt. Reuse it on retry to get the original response." }
body:
  - { name: items, type: "LineItem[]", required: true, desc: "One or more of { sku, qty }. qty is 1..99." }
  - { name: shipping_address, type: Address, required: true, desc: "name, line1, line2, city, postal_code, country (ISO 3166-1 alpha-2)." }
  - { name: currency, type: string, required: true, desc: "ISO 4217 code, for example USD." }
  - { name: payment_method_id, type: string, required: true, desc: "Token from the hosted payment gateway, prefix pm_." }
  - { name: customer_reference, type: string, desc: "Free text the client can search by later, max 64 chars." }
responses:
  - { status: 201, desc: "Order created; status is PAYMENT_PENDING." }
  - { status: 400, desc: "Malformed JSON or a field fails validation." }
  - { status: 401, desc: "Missing or invalid API key." }
  - { status: 409, desc: "Idempotency-Key reused with a different body." }
  - { status: 422, desc: "A sku is unknown or has no stock (code sku_unknown or out_of_stock)." }
request: |
  {
    "items": [{ "sku": "MUG-BLUE-12OZ", "qty": 2 }],
    "shipping_address": {
      "name": "Ada Lovelace", "line1": "12 Analytical St",
      "city": "London", "postal_code": "N1 9GU", "country": "GB"
    },
    "currency": "GBP",
    "payment_method_id": "pm_9f3k2",
    "customer_reference": "web-checkout-77120"
  }
response: |
  {
    "id": "ord_01J8Q2ZK4M",
    "status": "PAYMENT_PENDING",
    "currency": "GBP",
    "total_cents": 2400,
    "items": [{ "sku": "MUG-BLUE-12OZ", "qty": 2, "unit_price_cents": 1200 }],
    "created_at": "2026-09-13T10:02:11Z",
    "updated_at": "2026-09-13T10:02:11Z"
  }
```

## Fetch one order

```endpoint
method: GET
path: "/v1/orders/{order_id}"
description: "Return the current order, including its status and the shipment tracking number once it is SHIPPED."
auth: Bearer api_key
params:
  - { name: order_id, in: path, type: string, required: true, desc: "Order id, prefix ord_." }
responses:
  - { status: 200, desc: "The order." }
  - { status: 401, desc: "Missing or invalid API key." }
  - { status: 404, desc: "No order with this id belongs to the calling merchant." }
response: |
  {
    "id": "ord_01J8Q2ZK4M",
    "status": "SHIPPED",
    "currency": "GBP",
    "total_cents": 2400,
    "items": [{ "sku": "MUG-BLUE-12OZ", "qty": 2, "unit_price_cents": 1200 }],
    "shipment": { "carrier": "Royal Mail", "tracking_number": "RM123456789GB" },
    "created_at": "2026-09-13T10:02:11Z",
    "updated_at": "2026-09-14T08:41:00Z"
  }
```

## List orders

```endpoint
method: GET
path: /v1/orders
description: "Page through the calling merchant's orders, newest first. Filter by status or by the customer_reference given at creation."
auth: Bearer api_key
params:
  - { name: status, in: query, type: string, desc: "One lifecycle status, for example RESERVED. Omit for all." }
  - { name: customer_reference, in: query, type: string, desc: "Exact match on the value given at creation." }
  - { name: limit, in: query, type: integer, desc: "Page size 1..100, default 20." }
  - { name: cursor, in: query, type: string, desc: "Opaque token from the previous page's next_cursor." }
responses:
  - { status: 200, desc: "A page of orders and next_cursor, null on the last page." }
  - { status: 400, desc: "Unknown status value, limit out of range, or a malformed cursor." }
  - { status: 401, desc: "Missing or invalid API key." }
response: |
  {
    "data": [
      { "id": "ord_01J8Q2ZK4M", "status": "SHIPPED", "total_cents": 2400, "currency": "GBP" },
      { "id": "ord_01J8PXV0TC", "status": "RESERVED", "total_cents": 5900, "currency": "GBP" }
    ],
    "next_cursor": "eyJpZCI6Im9yZF8wMUo4UFhWMFRDIn0"
  }
```

## Cancel an order

```endpoint
method: POST
path: "/v1/orders/{order_id}/cancel"
description: "Move a BACKORDERED or RESERVED order to CANCELLED. The refund runs on its own and the order reaches REFUNDED when it settles."
auth: Bearer api_key
params:
  - { name: order_id, in: path, type: string, required: true, desc: "Order id, prefix ord_." }
  - { name: Idempotency-Key, in: header, type: string, required: true, desc: "Unique per attempt." }
body:
  - { name: reason, type: string, required: true, desc: "One of customer_request, fraud, duplicate." }
  - { name: note, type: string, desc: "Free text shown to support, max 500 chars." }
responses:
  - { status: 200, desc: "Order is now CANCELLED." }
  - { status: 401, desc: "Missing or invalid API key." }
  - { status: 404, desc: "No order with this id belongs to the calling merchant." }
  - { status: 409, desc: "Status is not BACKORDERED or RESERVED (code invalid_transition); the response carries the current status." }
request: |
  { "reason": "customer_request", "note": "Ordered the wrong colour" }
response: |
  {
    "id": "ord_01J8PXV0TC",
    "status": "CANCELLED",
    "cancelled_at": "2026-09-13T11:15:42Z",
    "refund": { "amount_cents": 5900, "status": "pending" }
  }
```

## Change the shipping address

```endpoint
method: PATCH
path: "/v1/orders/{order_id}/shipping-address"
description: "Replace the shipping address at any status before PACKED. Once PACKED the label is printed and the address is fixed."
auth: Bearer api_key
params:
  - { name: order_id, in: path, type: string, required: true, desc: "Order id, prefix ord_." }
body:
  - { name: shipping_address, type: Address, required: true, desc: "Full replacement; partial updates are not merged." }
responses:
  - { status: 200, desc: "The order with the new address." }
  - { status: 401, desc: "Missing or invalid API key." }
  - { status: 404, desc: "No order with this id belongs to the calling merchant." }
  - { status: 409, desc: "Order is PACKED or later (code invalid_transition)." }
  - { status: 422, desc: "Address fails validation, for example country not served (code address_invalid)." }
request: |
  {
    "shipping_address": {
      "name": "Ada Lovelace", "line1": "4 Ockham Rd",
      "city": "Guildford", "postal_code": "GU1 3AB", "country": "GB"
    }
  }
```

## Create, confirm, cancel

The only ordering rule a client must respect is the one below: a retry of
`POST /orders` must reuse the `Idempotency-Key`, or the customer is charged
twice. A cancel is safe to send at any time; the `409` tells the client the
order has already moved past the point of no return.

```sequence
id: create-confirm-cancel
actors:
  - { id: Client, name: Client }
  - { id: API, name: Orders API }
  - { id: Gateway, name: Payment gateway, external: true }
messages:
  - Client -> +API: "POST /v1/orders (Idempotency-Key k1)"
  - API -> Gateway: "authorize pm_9f3k2, 2400 GBP"
  - API --> -Client: "201 PAYMENT_PENDING"
  - opt: client timed out and retries
  - Client -> +API: "POST /v1/orders (Idempotency-Key k1)"
  - API --> -Client: "201 replayed, same order id"
  - end
  - Gateway -> API: "capture confirmed"
  - loop: until status leaves PAYMENT_PENDING
  - Client -> +API: "GET /v1/orders/ord_01J8Q2ZK4M"
  - API --> -Client: "200 PAID, then RESERVED"
  - end
  - Client -> +API: "POST /v1/orders/ord_01J8Q2ZK4M/cancel"
  - alt: status is BACKORDERED or RESERVED
  - API -> Gateway: "refund 2400 GBP"
  - API --> -Client: "200 CANCELLED"
  - else: status is PACKED or later
  - API -x-> Client: "409 invalid_transition"
  - end
foot:
  - { label: Polling interval, value: "2 s, back off to 30 s" }
  - { label: Capture latency, value: "p95 under 4 s" }
```

## Error codes

Every non-2xx response has the same envelope:
`{ "error": { "code": "...", "message": "...", "details": [] } }`. The `code`
is stable and is the value to branch on; `message` is for logs, not for logic.

```table
id: error-codes
columns: [HTTP, code, Meaning, Client action]
rows:
  - [400, invalid_request, "JSON does not parse or a field fails validation; details names the field.", "Fix the request. Do not retry as-is."]
  - [401, unauthorized, "API key missing, revoked, or for another environment.", "Check the key and the base URL. Do not retry."]
  - [403, forbidden, "Key is valid but the merchant has no orders scope.", "Ask the account owner for the scope."]
  - [404, not_found, "No such order for this merchant.", "Treat as gone; check the id."]
  - [409, idempotency_conflict, "Idempotency-Key reused with a different body.", "Generate a new key for a new request."]
  - [409, invalid_transition, "The order's status does not allow this call; details carries the current status.", "Read the status and stop; the order has moved on."]
  - [422, sku_unknown, "A sku in items does not exist.", "Remove or correct the item."]
  - [422, out_of_stock, "A sku has no stock now; details lists the skus.", "Offer the customer a substitute or a wait."]
  - [422, address_invalid, "Shipping address fails validation or country is not served.", "Correct the address."]
  - [429, rate_limited, "More than 100 requests per second for this key.", "Wait for Retry-After seconds, then retry."]
  - [500, internal, "Unexpected server error; the request was not applied.", "Retry with the same Idempotency-Key after a backoff."]
  - [503, unavailable, "Payment gateway or warehouse unreachable.", "Retry with the same Idempotency-Key after Retry-After."]
note: "Every 5xx is safe to retry with the same Idempotency-Key. No 4xx is."
```

## Terms

```glossary
terms:
  - "Order — one purchase with its items, address, payment, and a single status."
  - "Line item — one sku and its qty inside an order; the price is fixed at creation."
  - "Status — the order's current lifecycle state, set only by the service."
  - "Idempotency-Key — client-chosen header that makes a POST safe to retry for 24 hours."
  - "Cursor — opaque token that continues a list from where the last page ended."
  - "Merchant — the account an API key belongs to; every order belongs to exactly one."
```