Developer Docs

← Back to Console

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):

Read endpoints (require orders:read):

Authentication

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

ℹ️ return vs returns: both spellings are accepted on writes and reads and are canonicalized to returns (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. jobId values (inbound_…, or ext_send_… when you pass sourceRecordId) are not order ids. Poll GET /e9v3/jobs/{jobId} until status is completed, then use result.orderId (ord_…) and result.orderNumber (minted WO-####) with GET /e9v3/orders/send/{orderId} — that is what the Flutter app lists. There is no lookup by sourceRecordId.

reference1 is an optional external/PO reference — it is not the work-order number. Ship-from comes from shipFromLocationId or the business portal defaultLocationId (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/sync does not deduplicate on sourceRecordId.

⚠️ Order writes do not verify that the customerId or line-item SKUs exist — the order is still created and queued; unknown references surface when the job is processed. Only /e9v3/inventory/sync validates 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