Skip to Content
REST APIOrders

Orders

Sales, newest first, with line items included on every row.

Status and payment are two questions

The single most important thing on this page. An order can be paid and not yet shipped, so status and paymentStatus are separate fields and answering one does not answer the other.

  • status is where the order is in fulfilmentnew, confirmed, shipped, completed, cancelled, refunded.
  • paymentStatus is where the money stands — unpaid, pending, paid, refunded, disputed.

A consumer that reads status: "new" as “not paid” will hold back fulfilment on every card sale, because a card order lands paid and new in the same instant. A consumer that reads paymentStatus: "paid" as “done” will tell a customer their parcel is on its way before anybody has packed it.

The two filters are separate for the same reason, and combine:

curl "https://api.sailo.store/api/v1/orders?status=confirmed&payment_status=paid" \
  -H "Authorization: Bearer sailo_sk_…"

paymentStatus: "refunded" is set when the whole order was refunded. A partial refund leaves it paid and moves the refunded money object instead — so compare refunded against total rather than reading the status alone.

Finding one customer’s orders

?email= matches the order’s customer email exactly, case-insensitively. It is the fastest way to answer “everything this person has bought” without paging the whole shop.

curl "https://api.sailo.store/api/v1/orders?email=ada@example.com" \
  -H "Authorization: Bearer sailo_sk_…"

Note that this matches the email on the order, which is what the buyer typed at checkout. A customer who used two addresses is two answers. If you want everything tied to one person on the seller’s list, read GET /contacts/{id} for their id and match orders on customer.clientId.

Line items are always included

Both the list and the detail endpoint return items in full — title, variant, SKU, quantity and per-line money. There is no ?expand= and no separate /orders/{id}/items.

That is a deliberate asymmetry with products, where variants are not expanded in the list. An order has a handful of lines and they are the substance of the order; a page of twenty-five products with every variant inline is a large response nobody asked for.

After a webhook

order.paid and its siblings carry the complete order in data — the same body GET /orders/{id} returns, built by the same code. A consumer that receives an event and then immediately fetches the order is making a call it already had the answer to.

The detail endpoint is for the consumer that stored only the id, and for the backfill of orders that predate the integration.

The object

The full shape — including customer, address, delivery, booking and the line items — is on the order object reference.

GET /orders

Keyset-paged, newest first, with line items included on every row. The three filters are the questions an integration actually asks: where the order is in fulfilment, whether the money arrived, and everything one customer has bought. An order can be paid and not yet shipped, so status and payment_status are separate questions and answering one does not answer the other.

Parameters

Parameters for GET /orders
ParameterWhat it does
status query · stringFulfilment stage. One of new, confirmed, shipped, completed, cancelled, refunded.
payment_status query · stringWhere the money stands. One of unpaid, pending, paid, refunded, disputed.
email query · string (email)Exact customer email, matched case-insensitively.
limit query · integerHow many to return. Defaults to 25, capped at 100 — asking for more is clamped, not refused.
cursor query · stringThe next_cursor from the previous page. Omit for the first page. A cursor we did not issue is a 400, not an empty page.

Request

curl "https://api.sailo.store/api/v1/orders?limit=50&payment_status=paid" \
  -H "Authorization: Bearer sailo_sk_…"

200 — a page of results

{
  "data": [
    {
      "id": "8f2b41d6-0c93-4f77-a1e5-9b6d2c4a7e01",
      "object": "order",
      "status": "confirmed",
      "paymentStatus": "paid",
      "total": { "cents": 5498, "amount": "54.98", "currency": "GBP" },
      "customer": { "clientId": "c1d2…", "name": "Ada Lovelace", "email": "ada@example.com", "phone": null },
      "items": [ /* … */ ]
      /* … every field GET /orders/{id} returns … */
    }
  ],
  "has_more": true,
  "next_cursor": "MjAyNi0wOC0xMlQwOTo0MTowNy4yMjFafDhmMmI"
}

Failures

Failure modes for GET /orders
codeWhen it happens
invalid_request HTTP 400cursor is not one we issued.
unauthorized HTTP 401No Authorization header, or a key we do not recognise.
forbidden HTTP 403A real key, but the shop's plan does not include the API.
rate_limited HTTP 429Too many calls on this key. Slow down and retry.
server_error HTTP 500Our fault. The body says nothing about the cause; retry.

GET /orders/{id}

The follow-up a webhook makes possible: order.paid arrives carrying the order id, and this is how a consumer that stored only the id fetches the rest. The body is identical to the webhook's data, so one field map works against both.

Parameters

Parameters for GET /orders/{id}
ParameterWhat it does
id path · string (uuid) · requiredThe order id.

Request

curl https://api.sailo.store/api/v1/orders/8f2b41d6-0c93-4f77-a1e5-9b6d2c4a7e01 \
  -H "Authorization: Bearer sailo_sk_…"

200

{
  "data": {
    "id": "8f2b41d6-0c93-4f77-a1e5-9b6d2c4a7e01",
    "object": "order",
    "status": "confirmed",
    "paymentStatus": "paid",
    "paymentMethod": "card",
    "currency": "GBP",
    "subtotal":    { "cents": 4999, "amount": "49.99", "currency": "GBP" },
    "discount":    { "cents": 0,    "amount": "0.00",  "currency": "GBP" },
    "deliveryFee": { "cents": 499,  "amount": "4.99",  "currency": "GBP" },
    "tax":         { "cents": 0,    "amount": "0.00",  "currency": "GBP" },
    "total":       { "cents": 5498, "amount": "54.98", "currency": "GBP" },
    "refunded":    { "cents": 0,    "amount": "0.00",  "currency": "GBP" },
    "itemCount": 1,
    "customer": {
      "clientId": "c1d2e3f4-5a6b-7c8d-9e0f-1a2b3c4d5e6f",
      "name": "Ada Lovelace",
      "email": "ada@example.com",
      "phone": null
    },
    "address": {
      "line1": "12 Dean Street", "line2": null, "city": "London",
      "region": null, "postalCode": "W1D 3RN", "country": "GB"
    },
    "delivery": {
      "method": "shipping", "label": "Standard", "pickupLocation": null,
      "trackingCarrier": null, "trackingNumber": null, "trackingUrl": null,
      "shippedAt": null
    },
    "booking": null,
    "coupon": { "code": "LAUNCH10" },
    "affiliate": null,
    "note": null,
    "items": [
      {
        "id": "1b0c…", "productId": "9a7e…", "variantId": null,
        "title": "Sourdough loaf", "variantLabel": null, "sku": "SD-01",
        "kind": "physical", "quantity": 1,
        "unitPrice": { "cents": 4999, "amount": "49.99", "currency": "GBP" },
        "subtotal":  { "cents": 4999, "amount": "49.99", "currency": "GBP" }
      }
    ],
    "createdAt": "2026-08-12T09:41:07.221Z",
    "updatedAt": "2026-08-12T09:41:09.884Z"
  }
}

Failures

Failure modes for GET /orders/{id}
codeWhen it happens
not_found HTTP 404No order with that id in this shop.
unauthorized HTTP 401No Authorization header, or a key we do not recognise.
forbidden HTTP 403A real key, but the shop's plan does not include the API.
rate_limited HTTP 429Too many calls on this key. Slow down and retry.
server_error HTTP 500Our fault. The body says nothing about the cause; retry.
Last updated on