Skip to Content
ObjectsOrder

Order

The object GET /orders/{id} returns and the object every order.* webhook — and booking.confirmed — carries in data. The same shape, from the same code.

One shape for all of them rather than a slimmer one per event: a consumer writing against order.paid and later adding order.refunded should not have to remap every field, and what differs between those two is which one fired, not what an order is.

Fields on an order
FieldWhat it is
id stringThe order's id, a UUID. This is the value order.* webhooks carry and the one GET /orders/{id} takes.
object "order"Always the literal string.
status stringWhere the order is in fulfilment. One of new, confirmed, shipped, completed, cancelled, refunded. Separate from payment: an order can be paid and not yet shipped.
paymentStatus stringWhere the money stands. One of unpaid, pending, paid, refunded, disputed. disputed means a chargeback has been opened against it — see the dispute object.
paymentMethod stringThe rail the buyer chose: card, bank_transfer, cod, paypal, venmo, or one of the contact rails (whatsapp, telegram, instagram, email, phone). Only card and paypal confirm themselves; the rest are marked paid by the seller.
currency stringISO 4217. The same code every money object below carries.
subtotal moneyLine items before discount, delivery and tax.
discount moneyWhat the coupon or affiliate code took off. Zero rather than null when there was none.
deliveryFee moneyWhat was charged for delivery. Zero on a digital order.
tax moneyTax charged on this order, where the shop collects it.
total moneyWhat the buyer actually owes — subtotal minus discount, plus delivery and tax. This is the number to report.
refunded moneyHow much has been given back so far. A partial refund leaves total untouched and moves this, so compare the two rather than reading status alone.
itemCount numberTotal units across all lines, not the number of lines. Two of one product is 2.
customer objectWho bought it — see the customer table below. Null fields are normal: a digital order needs no phone.
address objectWhere it goes. Every field is null on an order that needs no delivery.
delivery objectHow it gets there, and the tracking details once it has shipped.
booking object | nullThe appointment, on an order for a service. Null — rather than an object of nulls — when there is none, so a consumer can branch on the object itself.
coupon object | null{ code } if a coupon was used, null otherwise.
affiliate object | null{ code } if the sale came through an affiliate link, null otherwise.
note string | nullWhat the buyer typed at checkout. Never the seller's own private note — that is not sent.
items object[]The lines, in the order they were added. See the line item table below.
createdAt string | nullISO 8601, when the order was placed.
updatedAt string | nullISO 8601, when it last changed.

Two independent statuses

status is fulfilment. paymentStatus is money. Neither answers the other’s question. A card order arrives status: "new" and paymentStatus: "paid" in the same instant; a bank-transfer order can sit confirmed and unpaid for days.

FieldValues
statusnew, confirmed, shipped, completed, cancelled, refunded
paymentStatusunpaid, pending, paid, refunded, disputed

paymentStatus: "disputed" means a chargeback has been opened — the dispute object has the case.

Money on an order

Six money objects, and they compose:

subtotal − discount + deliveryFee + tax = total

refunded sits outside that sum. It is how much has gone back so far, and a partial refund leaves total untouched. Compare the two rather than reading status alone — an order can be paid with a non-zero refunded.

Customer

Fields on an order's customer object
FieldWhat it is
clientId string | nullThe contact this order belongs to, readable with GET /contacts/{id}. Null on a guest checkout that matched nobody on the list.
name string | nullAs the buyer typed it.
email string | nullAs the buyer typed it. Not evidence of marketing consent — see the contact object for that.
phone string | nullNormalised before storage.

clientId is the join to the shop’s list — readable with GET /contacts/{id}. It is null on a guest checkout that matched nobody, which is normal rather than an error.

An order’s customer.email is not consent to email that person. It is an address somebody typed to receive a receipt. Marketing consent lives on the contact and nowhere else.

Address

Present on every order, with every field null where there is nothing to deliver.

Fields on an address object
FieldWhat it is
line1 string | nullStreet address.
line2 string | nullApartment, suite, or whatever the buyer added.
city string | nullCity or town.
region string | nullState, province or county, where the country has one.
postalCode string | nullPostal or ZIP code.
country string | nullISO 3166-1 alpha-2, uppercase.

Delivery

Fields on an order's delivery object
FieldWhat it is
method string | nullshipping or collection. Null on an order that needs no delivery at all — a digital download, a service.
label string | nullThe rate the buyer picked, in the seller's own words: Standard, Next day, Pickup.
pickupLocation string | nullWhere to collect from, on a collection order.
trackingCarrier string | nullCarrier name, as the seller entered it. Free text rather than an enumeration — sellers ship with local couriers Sailo has never heard of.
trackingNumber string | nullThe consignment number.
trackingUrl string | nullA link the buyer can follow, where the seller gave one.
shippedAt string | nullISO 8601. Set at the same moment order.shipped fires.

trackingCarrier is free text rather than an enumeration, because sellers ship with local couriers Sailo has never heard of. Do not switch on it; show it.

shippedAt is set at the same moment order.shipped fires, so a consumer that missed the event can still tell.

Booking

null on an order with no appointment — the whole object, not an object of nulls, so a consumer can branch on order.booking and get a truthy test that works.

Fields on an order's booking object
FieldWhat it is
scheduledFor string | nullISO 8601, in UTC. Render it in the shop's timeZone — an appointment at 09:00 local is not 09:00 to a consumer in another zone.
serviceMode string | nullin_person or online.
serviceLocation string | nullWhere it happens, or the joining details for an online booking.

Line items

Fields on an order line item
FieldWhat it is
id stringThe line's own id.
productId string | nullThe product, readable with GET /products/{id}. Null once the seller has deleted it — the line keeps its own title and price, because an order is a record of what was sold and not a join to a catalogue that has moved on.
variantId string | nullThe variant, on a product that has them.
title stringThe product's name as it was at the moment of sale.
variantLabel string | nullThe variant as it was — Large / Blue.
sku string | nullThe seller's own code for it.
kind stringWhat sort of thing it is. One of physical, digital, service, event, membership.
quantity numberHow many of this line.
unitPrice moneyPrice for one, at the moment of sale.
subtotal moneyunitPrice times quantity, before any order-level discount.

items is populated on both the list and the detail endpoint, and on every webhook. There is no ?expand=.

productId is null once the seller has deleted the product, and the line keeps its own title, sku and prices. That is deliberate: an order is a record of what was sold, not a join to a catalogue that has moved on. A consumer that resolves productId must handle the null rather than treating it as corrupt data.

itemCount is total units, not lines. Two of one product is 2.

Coupon and affiliate

Both are { code } or null.

They are the codes as used, not a join — the coupon may have been deleted or the affiliate deactivated since. If you need more than the code, match it in your own system.

Example

{ "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": "1b0c9d3e-4f50-4a61-8b72-9c83d4e5f607", "productId": "9a7e2c11-6b48-4d0f-8e35-71c9a4f2b6d8", "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" }
Last updated on