Orders
Read an organisation's orders, and place new ones priced and scheduled from their catalogue.
An order is what a customer booked: the product, where it goes, when it is delivered and collected, and what it costs. OPS25 turns each order into the jobs drivers carry out; see jobs.
Reading orders
With an API key, GET /api/v1/orders lists the organisation's orders newest
first, 25 at a time. Pass the last order's id as startingAfter to get the next page, and stop
when hasMore is false. To hear about new and cancelled orders as they happen instead of polling,
use webhooks.
Amounts are in cents, including GST. Dates are YYYY-MM-DD in the organisation's timezone.
Creating orders
There are two ways to place an order. Both take the same body, run the same pricing and booking rules, and return the booking reference the customer will quote on the phone.
- From your server,
POST /api/v1/orderswith a read and write API key. Use this for an integration: a quoting tool, a CRM, a website form handled on your server. - From a browser,
POST /api/public/{organisation}/orderson an allowlisted origin. This is the call the booking flow itself makes, and needs no key. A server-side call to it is refused with403; see authentication.
Before you create one
- Read the catalogue first.
productId,areaId, the add-ons'productIds anddeliveryTimePeriodIdall have to come from it. Nothing here accepts free text where an id is expected. - Send an
Idempotency-Key. Bookings are the one thing customers will not forgive you for duplicating.
Consent is not optional
acceptedTerms must be true, and acceptedNoticeIds must list every booking notice that applies
to the product and has mustAccept set. The API will not accept the order otherwise. Notices come
back in the catalogue under notices; one applies when its productGroupIds is empty or includes
the product's group. The order's record keeps the wording of each notice accepted. Show them. Do
not send ids behind a customer who never saw them.
acceptedCollectionNotice is retired: it is still accepted, and ignored.
What comes back
A 201. POST /api/v1/orders returns the whole order, the same object GET returns. The browser
call returns a shorter summary: the id, reference, total, dates, status and whether it has been
paid. Payment is a separate step handled by the
hosted flow — see hosted payments.
Validation failures
A 422 names the offending fields, semicolon-separated:
{ "error": "deliveryDate: Enter a valid date; quantity: Too small: expected number to be >=1" }
Show these to the customer if you like — they are written for a person — but map your own copy onto the field names rather than the message text.
GET/api/v1/orders
The organisation's orders, newest first.
- Beta — still moving. Changes are announced in the changelog before they ship.
- Needs an API key. Call it from your server, never a browser.
- Works with read-only and read and write keys.
Query parameters
| Name | Required | Description |
|---|---|---|
limit | No | Between 1 and 100. Defaults to 25. |
startingAfter | No | An order id from the previous page. Returns the orders created before it. Use it while `hasMore` is true. |
Headers
| Name | Required | Description |
|---|---|---|
Authorization | Yes | Your API key as a bearer token: `Bearer ops25_sk_…`. |
Errors
| Status | When |
|---|---|
401 | The key is missing, malformed or revoked. |
429 | More than 600 requests from this key in a minute. |
404 | `startingAfter` is not an order in this organisation. |
GET/api/v1/orders/{id}
One order, by its id.
- Beta — still moving. Changes are announced in the changelog before they ship.
- Needs an API key. Call it from your server, never a browser.
- Works with read-only and read and write keys.
Path parameters
| Name | Description |
|---|---|
id | The order id, a UUID. |
Headers
| Name | Required | Description |
|---|---|---|
Authorization | Yes | Your API key as a bearer token: `Bearer ops25_sk_…`. |
Errors
| Status | When |
|---|---|
401 | The key is missing, malformed or revoked. |
429 | More than 600 requests from this key in a minute. |
404 | No order in this organisation has that id. |
POST/api/v1/orders
Place an order from your server. Same body, pricing and booking rules as the public booking call. Answer the organisation’s booking questions in `customFields`, by field key. List the booking notices the customer accepted in `acceptedNoticeIds`; every notice that applies to the product and must be accepted is required. The old `placement` field is still accepted and becomes the answer to a question keyed `placement`.
- Beta — still moving. Changes are announced in the changelog before they ship.
- Needs an API key. Call it from your server, never a browser.
- Needs a read and write key. Read-only keys get 403.
Headers
| Name | Required | Description |
|---|---|---|
Authorization | Yes | Your API key as a bearer token: `Bearer ops25_sk_…`. |
Content-Type | Yes | Must be `application/json`. |
Idempotency-Key | No | Repeat a call safely. The same key returns the original order rather than creating a second one. |
Body
Generated from the schema the API validates with, so it cannot describe a body the API would reject.
| Field | Type | Required | Notes |
|---|---|---|---|
productId | uuid | Yes | |
areaId | uuid | "" | No | Defaults to "" |
locality | object | No | |
locality.suburb | string | Yes | 1–120 characters |
locality.postcode | string | Yes | |
lines | object[] | No | up to 20 items |
lines[].discountPercent | integer | No | 0–100. Defaults to 0 |
lines[].description | string | No | 1–500 characters |
lines[].lineType | "hire" | "sale" | No | |
lines[].workType | "none" | "delivery" | "service" | No | |
lines[].purchaseOrder | string | No | up to 100 characters |
lines[].equipmentReference | string | No | up to 120 characters |
lines[].deliveryOn | string | No | |
lines[].collectionOn | string | null | No | |
lines[].productId | uuid | Yes | |
lines[].quantity | number | Yes | 0.01–1000 |
lines[].deliveryDate | string | Yes | |
lines[].pickupDate | string | Yes | |
lines[].waitWhileLoad | boolean | No | Defaults to false |
lines[].schedule | object | null | No | |
purchaseOrder | string | No | up to 100 characters |
salesperson | string | No | up to 120 characters |
customerType | "individual" | "company" | Yes | |
customerName | string | Yes | 2–120 characters |
contactName | string | Yes | 2–120 characters |
email | email | Yes | up to 200 characters |
phone | string | Yes | 8–30 characters |
address | string | Yes | 5–250 characters |
lotNumber | string | No | up to 40 characters |
gateCode | string | No | up to 80 characters |
placement | string | No | up to 80 characters |
notes | string | No | up to 2000 characters. Defaults to "" |
customFields | object | No | |
addOns | object[] | No | up to 50 items. Defaults to [] |
addOns[].productId | uuid | Yes | |
addOns[].quantity | integer | Yes | 1–100 |
quantity | integer | Yes | 1–1000 |
deliveryDate | string | Yes | |
deliveryTimePeriodId | string | null | No | Defaults to null |
pickupDate | string | Yes | |
waitWhileLoad | boolean | No | Defaults to false |
acceptedTerms | true | Yes | |
acceptedNoticeIds | uuid[] | No | up to 50 items. Defaults to [] |
acceptedCollectionNotice | boolean | No |
Errors
| Status | When |
|---|---|
401 | The key is missing, malformed or revoked. |
429 | More than 600 requests from this key in a minute. |
402 | The organisation's subscription is inactive. |
403 | The key is read-only. |
422 | The body failed validation. The message names the offending fields. |
POST/api/public/{organisation}/orders
Place a booking against an organisation, priced and scheduled from their catalogue.
- Beta — still moving. Changes are announced in the changelog before they ship.
- Browser only — the API requires an allowlisted Origin header.
Path parameters
| Name | Description |
|---|---|
organisation | The organisation's booking slug. |
Headers
| Name | Required | Description |
|---|---|---|
Content-Type | Yes | Must be `application/json`. |
Idempotency-Key | No | Repeat a call safely. The same key against the same organisation returns the original order rather than creating a second one. |
Origin | Yes | Must be an allowlisted origin. The API rejects writes from unrecognised origins, which is why this endpoint cannot yet be called from a server. |
Body
Generated from the schema the API validates with, so it cannot describe a body the API would reject.
| Field | Type | Required | Notes |
|---|---|---|---|
productId | uuid | Yes | |
areaId | uuid | "" | No | Defaults to "" |
locality | object | No | |
locality.suburb | string | Yes | 1–120 characters |
locality.postcode | string | Yes | |
lines | object[] | No | up to 20 items |
lines[].discountPercent | integer | No | 0–100. Defaults to 0 |
lines[].description | string | No | 1–500 characters |
lines[].lineType | "hire" | "sale" | No | |
lines[].workType | "none" | "delivery" | "service" | No | |
lines[].purchaseOrder | string | No | up to 100 characters |
lines[].equipmentReference | string | No | up to 120 characters |
lines[].deliveryOn | string | No | |
lines[].collectionOn | string | null | No | |
lines[].productId | uuid | Yes | |
lines[].quantity | number | Yes | 0.01–1000 |
lines[].deliveryDate | string | Yes | |
lines[].pickupDate | string | Yes | |
lines[].waitWhileLoad | boolean | No | Defaults to false |
lines[].schedule | object | null | No | |
purchaseOrder | string | No | up to 100 characters |
salesperson | string | No | up to 120 characters |
customerType | "individual" | "company" | Yes | |
customerName | string | Yes | 2–120 characters |
contactName | string | Yes | 2–120 characters |
email | email | Yes | up to 200 characters |
phone | string | Yes | 8–30 characters |
address | string | Yes | 5–250 characters |
lotNumber | string | No | up to 40 characters |
gateCode | string | No | up to 80 characters |
placement | string | No | up to 80 characters |
notes | string | No | up to 2000 characters. Defaults to "" |
customFields | object | No | |
addOns | object[] | No | up to 50 items. Defaults to [] |
addOns[].productId | uuid | Yes | |
addOns[].quantity | integer | Yes | 1–100 |
quantity | integer | Yes | 1–1000 |
deliveryDate | string | Yes | |
deliveryTimePeriodId | string | null | No | Defaults to null |
pickupDate | string | Yes | |
waitWhileLoad | boolean | No | Defaults to false |
acceptedTerms | true | Yes | |
acceptedNoticeIds | uuid[] | No | up to 50 items. Defaults to [] |
acceptedCollectionNotice | boolean | No |
Errors
| Status | When |
|---|---|
402 | The organisation's subscription is inactive, so online booking is closed. |
403 | The request came from an origin that is not allowlisted. |
404 | No organisation has that slug. |
422 | The body failed validation. The message names the offending fields. |