Orders API
The Orders API enables you to create orders, list existing orders, retrieve order details with line items, and monitor background job status.
Overview
Write endpoints (require orders:write):
- Create Send Order: Ship products to a customer
- Create Receive Order: Receive products from a supplier
- Sync Order: Create any order type with full control
Read endpoints (require orders:read):
- List Orders: Retrieve orders by type with filtering
- Get Order Detail: Full order with line items
- Get Job Status: Monitor background processing of order operations
Authentication
- Write scope:
orders:write - Read scope:
orders:read - Auth Method: OAuth 2.0 M2M (Machine-to-Machine)
Order Types
On the write side, orderType accepts send, receive, returns, or transfer (case-insensitive). Each order must include exactly one contact field — customerId, supplierId, locationId, or agentId. The table shows a typical field per type; the server only enforces that exactly one is present.
| Type | Description | Typical contact field |
|---|---|---|
send |
Ship to a customer | customerId |
receive |
Receive from a supplier | supplierId |
transfer |
Move between locations | locationId |
returns |
Customer return | customerId |
ℹ️
returnvsreturns: both spellings are accepted on writes and reads and are canonicalized toreturns(the value the rest of the platform uses). You can use either when creating or listing.
Order Status Lifecycle
draft → finalized → committed → processing → fulfilled
↘ voided
| Status | Description |
|---|---|
draft |
Initial state, editable |
finalized |
Locked, ready for commit |
committed |
Inventory committed |
processing |
Active fulfillment |
fulfilled |
Complete |
voided |
Cancelled |
Create a Send Order
POST /e9v3/orders/send
Authorization: Bearer <jwt-token>
Content-Type: application/json
Request Body
{
"customerId": "cust-123",
"items": [
{
"sku": "WIDGET-001",
"productName": "Premium Widget",
"quantity": 5
},
{
"sku": "GADGET-002",
"productName": "Standard Gadget",
"quantity": 10
}
],
"reference1": "PO-2024-001",
"shipFromLocationId": "wh-123",
"notes": "Urgent delivery"
}
Response (200 OK)
{
"success": true,
"jobId": "inbound_1776400267719207348"
}
Async: HTTP 200 means the write was queued, not that an order document exists yet.
jobIdvalues (inbound_…, orext_send_…when you passsourceRecordId) are not order ids. PollGET /e9v3/jobs/{jobId}untilstatusiscompleted, then useresult.orderId(ord_…) andresult.orderNumber(mintedWO-####) withGET /e9v3/orders/send/{orderId}— that is what the Flutter app lists. There is no lookup bysourceRecordId.
reference1is an optional external/PO reference — it is not the work-order number. Ship-from comes fromshipFromLocationIdor the business portaldefaultLocationId(job fails if neither is set).
Create a Receive Order
POST /e9v3/orders/receive
Authorization: Bearer <jwt-token>
Content-Type: application/json
Request Body
{
"supplierId": "sup-456",
"items": [
{
"sku": "RAW-MATERIAL-A",
"productName": "Raw Material A",
"quantity": 100
}
],
"reference1": "INV-2024-789"
}
If you don’t have a supplierId, supply one of supplierEmail / supplierPhone (plus supplierName when only an email or phone is given) and the platform matches an existing supplier or creates a new contact.
Create a Send Order with Match-or-Create
POST /e9v3/orders/send-matched works like /e9v3/orders/send, but no pre-existing customerId is required. If customerId is supplied, the order uses that contact as-is even when the ship-to address differs — that is the only case where contact details need not match exactly (one customer, several sites).
Otherwise supply customerName plus structured address fields. The platform reuses an existing customer only when business name, line1, suburb, and postcode all match exactly (street abbreviations like st/rd allowed). A colliding externalCustomerId (for example pdf:name-postcode) is not enough if the street or business name differs. Suburb+postcode without a matching street, or an incomplete street, creates a new contact. customerEmail / customerPhone do not reuse a different delivery site.
Use structured address fields that mirror Flutter contacts (addressLine1 / suburb / state / postcode). Recommended complete set: line1 + suburb + postcode (+ state). Address verification warnings for non-exact matches are planned next (not returned yet).
POST /e9v3/orders/send-matched
Authorization: Bearer <jwt-token>
Content-Type: application/json
{
"customerEmail": "buyer@example.com",
"customerName": "Jane Buyer",
"customerPhone": "0405958874",
"customerAddressLine1": "Unit 5 Happy Rd",
"customerSuburb": "Acacia Ridge",
"customerState": "QLD",
"customerPostcode": "4108",
"items": [
{ "sku": "WIDGET-001", "productName": "Premium Widget", "quantity": 2 }
]
}
Returns 200 { "success": true, "jobId": "..." } like the other order writes.
Create Any Order Type (Sync)
POST /e9v3/orders/sync is the generic write: set orderType to send, receive, returns, or transfer (case-insensitive; the singular return is accepted as an alias for returns) and include exactly one of customerId / supplierId / locationId / agentId.
{
"orderType": "transfer",
"locationId": "loc-456",
"items": [
{ "sku": "ITEM-X", "productName": "Item X", "quantity": 25 }
]
}
ℹ️ Unlike
send/receive/send-matched/inventory/sync,/orders/syncdoes not deduplicate onsourceRecordId.
⚠️ Order writes do not verify that the
customerIdor line-item SKUs exist — the order is still created and queued; unknown references surface when the job is processed. Only/e9v3/inventory/syncvalidates the SKU up front (404 on an unknown SKU).
List Orders
Retrieve orders by type with optional filtering.
GET /e9v3/orders?type=send&status=draft&limit=50
Authorization: Bearer <jwt-token>
Query Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
type |
string | No | send |
Order type: send, receive, transfer, returns (the singular return is accepted as an alias) |
archived |
boolean | No | false |
Set true to query archived/completed orders |
status |
string | No | — | Filter by status: draft, finalized, committed, processing, fulfilled, voided |
search |
string | No | — | Search by order number, contact name, or reference |
limit |
integer | No | 100 | Max results (up to 500) |
Response (200 OK)
{
"success": true,
"orders": [
{
"orderId": "ord-abc123",
"orderNumber": "SO-001",
"orderType": "send",
"status": "draft",
"contactName": "Acme Corporation",
"contactEmail": "orders@acme.com",
"locationName": "Main Warehouse",
"lineCount": 3,
"totalItems": 25,
"reference1": "PO-2024-001",
"created": "2024-01-15T10:30:00Z",
"orderDate": "2024-01-15T00:00:00Z"
}
],
"count": 1
}
Get Order Detail
Retrieve a single order with its line items.
GET /e9v3/orders/:orderType/:orderId
Authorization: Bearer <jwt-token>
Response (200 OK)
{
"success": true,
"order": {
"orderId": "ord-abc123",
"orderNumber": "SO-001",
"orderType": "send",
"status": "draft",
"contactName": "Acme Corporation",
"lineCount": 2,
"totalItems": 15
},
"lines": [
{
"lineId": "line-001",
"sku": "WIDGET-001",
"productName": "Premium Widget",
"quantity": 5,
"availableQty": 150,
"unitOfMeasure": "pcs",
"unitWeight": 0.5
},
{
"lineId": "line-002",
"sku": "GADGET-002",
"productName": "Standard Gadget",
"quantity": 10,
"availableQty": 200,
"unitOfMeasure": "pcs"
}
]
}
Get Job Status
Monitor the background processing of order operations (create, finalize, commit, void).
When status is completed, use result.orderId (ord_…) — never the jobId — to fetch the order or find it in Flutter.
GET /e9v3/jobs/:jobId
Authorization: Bearer <jwt-token>
Job Status Values
| Status | Description |
|---|---|
pending |
Queued, awaiting processing |
processing |
Currently being processed |
completed |
Successfully completed |
failed |
Failed with error |
Response (200 OK — Completed)
{
"success": true,
"jobId": "inbound_1776400267719207348",
"status": "completed",
"processType": "InboundOrderCreate",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:30:02Z",
"result": {
"orderId": "ord_abc12345",
"orderNumber": "WO-0042"
}
}
Response (200 OK — Failed)
{
"success": true,
"jobId": "inbound_1776400267719207348",
"status": "failed",
"processType": "InboundOrderCreate",
"errorMessage": "Customer not found: cust-invalid",
"createdAt": "2024-01-15T10:30:00Z"
}
Polling Pattern
async function waitForJob(jobId, token, maxAttempts = 10) {
for (let i = 0; i < maxAttempts; i++) {
const res = await fetch(`https://api.equos.one/e9v3/jobs/${jobId}`, {
headers: { Authorization: `Bearer ${token}` }
});
const data = await res.json();
if (data.status === 'completed') return data.result;
if (data.status === 'failed') throw new Error(data.errorMessage);
await new Promise(r => setTimeout(r, 1000)); // wait 1 second
}
throw new Error('Job timed out');
}
Error Handling
| Status | Meaning |
|---|---|
200 |
Success |
400 |
Invalid request (bad order type, missing fields) |
401 |
Invalid or missing Bearer token |
403 |
Insufficient scope |
404 |
Order or job not found |
429 |
Rate limit exceeded (1 request/sec per key, applies to all /e9v3 endpoints incl. reads, burst 10) |
500 |
Server error |
Next Steps
- Inventory to check stock before ordering
- Customers to look up valid customer IDs
- Webhooks to receive order status change notifications
- API Tester to try these endpoints interactively