API Endpoints
Complete endpoint reference for the Equos Orders API
Base URL
https://api.equos.one/e9v3
Machine-Readable Spec
The full API surface is described by an OpenAPI 3.1 specification at/openapi.yaml — import it into Postman, Insomnia, or an SDK generator. A ready-madePostman collection is also available, including the OAuth client-credentials token request.
Authentication
All endpoints require the following header:
Authorization: Bearer <token>The token must have scope orders:write.
Rate Limiting
1 request/second per API key with a burst capacity of 10. The limit applies to all /e9v3 endpoints, including reads. See Rate Limiting for details.
Return & Transfer Orders
There are no dedicated endpoints for return and transfer orders. Instead, use the /sync endpoint with the appropriate orderType:
- Return orders:
POST /syncwithorderType: "returns"(the singular"return"is also accepted) andlocationId - Transfer orders:
POST /syncwithorderType: "transfer"andagentId
Contact Field Reference
Each order requires exactly one contact field. The table below shows an example contact field for each order type — /e9v3/orders/sync only enforces that exactly one of customerId, supplierId, locationId, or agentId is present, not which type pairs with which field:
| Order Type | Endpoint | Required Contact Field | Description |
|---|---|---|---|
send | POST /send | customerId | ID of customer receiving the order |
receive | POST /receive | supplierId or email/phone | Supplier ID, or email/phone for match-or-create |
returns | POST /sync | locationId | ID of location returning items (return accepted as an alias) |
transfer | POST /sync | agentId | ID of agent receiving the transfer |
POST/e9v3/orders/syncCreate order (any type)
Creates a generic order that can be of any type (send, receive, return, transfer). Use this endpoint for integrations where the order type is dynamic.
| Field | Type | Required | Description |
|---|---|---|---|
orderType | string | Required | Order type: send, receive, returns, or transfer (case-insensitive). The singular return is accepted as an alias and canonicalized to returns. |
customerId | string | Optional | Customer ID (use for send orders) |
supplierId | string | Optional | Supplier ID (use for receive orders) |
locationId | string | Optional | Location ID (use for internal transfers) |
agentId | string | Optional | Agent ID (use for agent-based orders) |
items | array | Required | Array of line items (minimum 1 item) |
reference1 | string | Optional | PO number or purchase reference |
reference2 | string | Optional | SO number or sales reference |
reference3 | string | Optional | External reference ID |
orderDate | string (RFC3339) | Optional | Order date in ISO format (e.g., 2024-01-15T00:00:00Z) |
dispatchDate | string (RFC3339) | Optional | Expected dispatch date |
notes | string | Optional | Order notes or comments (input as string, stored as array) |
sourceRecordId | string | Optional | External system ID for reconciliation |
metadata | object | Optional | Free-form JSON object (string keys, any JSON-serializable values) |
| Field | Type | Required | Description |
|---|---|---|---|
sku | string | Required | Product SKU code |
productName | string | Required | Product name or description |
quantity | number | Required | Quantity (must be > 0, decimals allowed) |
metadata | object | Optional | Per-line item metadata |
{
"success": true,
"jobId": "inbound_1718000000000000000"
}{
"success": false,
"error": "Validation failed",
"details": [
{
"field": "items[0].sku",
"message": "required field",
"value": null
},
{
"field": "items[0].quantity",
"message": "must be greater than 0",
"value": 0
}
]
}{
"error": "invalid token"
}{
"error": "rate limit exceeded",
"retry_after": 0.82,
"rate_limit": "1 order/second",
"burst_capacity": 10
}{
"success": false,
"error": "Failed to queue order processing"
}- Exactly one contact field required: customerId, supplierId, locationId, or agentId
- orderType is case-insensitive: accepts "send", "SEND", "Send", etc.
- The job is processed asynchronously; track status using the job tracking API
POST/e9v3/orders/sendCreate send order (to customer)
Creates a send order. The orderType is forced to 'send' server-side and should be omitted from the request.
Request body: Same schema as /sync endpoint, but orderType is forced to "send" on the server (the value you send is ignored).
Required contact field: customerId (or other contact field if modified)
/orders/send does not verify that the customerId or the line-item SKUs exist — the order is still created and queued. Unknown references surface later when the job is processed. Only /inventory/sync validates the SKU up front (404 on an unknown SKU).| Field | Type | Required | Description |
|---|---|---|---|
orderType | string | Required | Order type: send, receive, returns, or transfer (case-insensitive). The singular return is accepted as an alias and canonicalized to returns. |
customerId | string | Optional | Customer ID (use for send orders) |
supplierId | string | Optional | Supplier ID (use for receive orders) |
locationId | string | Optional | Location ID (use for internal transfers) |
agentId | string | Optional | Agent ID (use for agent-based orders) |
items | array | Required | Array of line items (minimum 1 item) |
reference1 | string | Optional | PO number or purchase reference |
reference2 | string | Optional | SO number or sales reference |
reference3 | string | Optional | External reference ID |
orderDate | string (RFC3339) | Optional | Order date in ISO format (e.g., 2024-01-15T00:00:00Z) |
dispatchDate | string (RFC3339) | Optional | Expected dispatch date |
notes | string | Optional | Order notes or comments (input as string, stored as array) |
sourceRecordId | string | Optional | External system ID for reconciliation |
metadata | object | Optional | Free-form JSON object (string keys, any JSON-serializable values) |
| Field | Type | Required | Description |
|---|---|---|---|
sku | string | Required | Product SKU code |
productName | string | Required | Product name or description |
quantity | number | Required | Quantity (must be > 0, decimals allowed) |
metadata | object | Optional | Per-line item metadata |
{
"success": true,
"jobId": "inbound_1718000000000000000"
}{
"success": false,
"error": "Validation failed",
"details": [
{
"field": "items[0].sku",
"message": "required field",
"value": null
},
{
"field": "items[0].quantity",
"message": "must be greater than 0",
"value": 0
}
]
}{
"error": "invalid token"
}{
"error": "rate limit exceeded",
"retry_after": 0.82,
"rate_limit": "1 order/second",
"burst_capacity": 10
}POST/e9v3/orders/receiveCreate receive order (from supplier)
Creates a receive order with supplier match-or-create capability. Supports resolving suppliers by ID or by email/phone/name.
Request body: Uses ReceiveOrderRequest schema (different from /sync). Supports supplier match-or-create logic.
Supplier contact rule: Must provide at least one of: supplierId, supplierEmail, or supplierPhone. If supplierId is not provided but email or phone is, supplierName is required for new contact creation.
| Field | Type | Required | Description |
|---|---|---|---|
supplierId | string | Optional | Supplier ID (use if supplier already exists) |
supplierEmail | string | Optional | Supplier email (used for match-or-create) |
supplierPhone | string | Optional | Supplier phone (used for match-or-create) |
supplierName | string | Optional | Supplier name (required if supplierId not provided) |
supplierBusinessName | string | Optional | Business name for new contact creation |
supplierAddressLine1 | string | Optional | Street address (Flutter addressLine1). Recommended with suburb + postcode. |
supplierAddressLine2 | string | Optional | Apt, suite, unit (optional; Flutter addressLine2) |
supplierSuburb | string | Optional | Suburb / city (Flutter suburb) |
supplierState | string | Optional | State / territory e.g. QLD, NSW (Flutter state) |
supplierPostcode | string | Optional | Postcode (Flutter postcode) |
externalSupplierId | string | Optional | External system ID (dedup key for match-or-create) |
items | array | Required | Array of line items (minimum 1 item) |
reference1 | string | Optional | Reference 1 |
reference2 | string | Optional | Reference 2 |
reference3 | string | Optional | Reference 3 |
orderDate | string (RFC3339) | Optional | Order date |
dispatchDate | string (RFC3339) | Optional | Dispatch date |
notes | string | Optional | Notes |
sourceRecordId | string | Optional | External record ID |
metadata | object | Optional | Custom metadata |
| Field | Type | Required | Description |
|---|---|---|---|
sku | string | Required | Product SKU code |
productName | string | Required | Product name or description |
quantity | number | Required | Quantity (must be > 0, decimals allowed) |
metadata | object | Optional | Per-line item metadata |
{
"success": true,
"jobId": "inbound_1718000000000000000"
}{
"success": false,
"error": "Validation failed",
"details": [
{
"field": "items[0].sku",
"message": "required field",
"value": null
},
{
"field": "items[0].quantity",
"message": "must be greater than 0",
"value": 0
}
]
}{
"error": "invalid token"
}{
"error": "rate limit exceeded",
"retry_after": 0.82,
"rate_limit": "1 order/second",
"burst_capacity": 10
}POST/e9v3/orders/send-matchedCreate send order (customer match-or-create)
Like /e9v3/orders/send, but no pre-existing customerId is required. If customerId is supplied, the order uses that contact as-is even when ship-to details differ. Otherwise supply customerName plus line1, suburb, and postcode: reuse only on an exact name+address match, else create. Designed for storefront and marketplace sources where the customer is only known by their checkout details.
- Request body is identical to
/e9v3/orders/send;customerIdbecomes optional - If
customerIdis supplied, the order uses that contact as-is even when ship-to details differ — this is the only mismatch exception - Otherwise matching reuses a contact only when name plus
customerAddressLine1,customerSuburb, andcustomerPostcodeall match exactly (street abbreviations like st/rd allowed). Incomplete street or suburb+postcode alone never reuses; unmatched customers are created - Structured address fields mirror Flutter contacts:
customerAddressLine1,customerAddressLine2,customerSuburb,customerState,customerPostcode. Recommended complete set: line1 + suburb + postcode (+ state). Address verification warnings for non-exact matches are planned next. - Asynchronous — returns a
jobIdlike the other order writes
Reading Orders & Job Tracking
GET/e9v3/ordersList orders
Lists orders of one type. Query parameters: type (optional, default send; one of send, receive, transfer, returns — the singular return is also accepted and canonicalized to returns), archived (boolean, default false — set true to list archived orders), status, search (order number / contact / reference1), limit (default 100, max 500). status and search are filtered in-memory over the first limit+1 documents; there is no cursor pagination.
GET/e9v3/orders/{orderType}/{orderId}Get order with lines
Returns the order summary plus its line items. Looks in open orders first, then archived.
GET/e9v3/jobs/{jobId}Get job status
Reports a queued write's lifecycle: pending → processing → completed | failed. Failed jobs carry errorMessage; completed jobs may carry a result object (e.g. the created orderId).
Supplier Match-or-Create Logic (Receive Orders)
When creating a receive order via POST /receive, if you don't provide a supplierId, the system will attempt to find or create a matching supplier contact.
Matching Precedence
The system searches for an existing supplier in this order:
- External ID: If
externalSupplierIdis provided, match byexternalIdfield (fastest) - Email: If
supplierEmailis provided, match by email address- If multiple suppliers match → no match (ambiguous), continue to next method
- Phone: If
supplierPhoneis provided, match by normalized phone (last 10 digits)- If multiple suppliers match → no match (ambiguous), continue to next method
- Name: If
supplierNameis provided, match by business name or contact name- If multiple suppliers match → no match (ambiguous), continue to next method
- Create New: If no match found, create a new supplier contact using provided details
Creating New Suppliers
When no match is found, the system creates a new supplier with:
- Required: At least
supplierName(orsupplierBusinessNameif providing business entity) - Optional:
supplierAddressLine1,supplierAddressLine2,supplierSuburb,supplierState,supplierPostcode,supplierEmail,supplierPhone - Link:
externalSupplierIdis stored for future matches on that ID
Date Format Reference
Order dates (orderDate, dispatchDate) must be provided in RFC 3339 format (ISO 8601 with timezone).
Acceptable formats:
2024-01-21T12:00:00Z— UTC (recommended)2024-01-21T12:00:00+00:00— UTC with explicit offset2024-01-21T12:00:00-05:00— Eastern Standard Time2024-01-21T12:00:00.123Z— UTC with milliseconds
Important: Always include a timezone (Z for UTC or ±HH:MM offset). Dates without timezone information will be rejected.
Metadata Field Guidance
Both orders and line items support a metadata field for storing custom data:
- Type: Free-form JSON object (any valid JSON-serializable values)
- Keys: Must be strings (e.g.,
"department": "sales") - Values: Can be strings, numbers, booleans, arrays, or nested objects
- Use cases: Track internal IDs, integration tags, cost centers, timestamps, or any custom attributes
- Persistence: Stored throughout the order processing lifecycle
Example:
"metadata": {
"costCenter": "DEPT-42",
"internalId": "order-12345",
"tags": ["urgent", "vip"],
"processedAt": "2024-01-21T12:00:00Z"
}Common Validation Errors
| Field | Error Message | How to Fix |
|---|---|---|
orderType | required field (if blank) | Provide a valid order type: send, receive, returns (or return), transfer |
contact | exactly one of customerId, supplierId, locationId, or agentId must be provided | Provide exactly ONE contact field |
items | at least one line item is required | Include at least one item in the items array |
items[N].sku | required field | Provide a SKU for each item |
items[N].quantity | must be greater than 0 | Quantity must be a positive number (decimals allowed) |
Authentication Error Codes (401)
| Error | Description |
|---|---|
missing or invalid Authorization header | No Bearer token provided or header missing |
invalid token | JWT parse/verification failed |
token not valid | JWT validation check failed |
invalid token claims | JWT claims malformed |
token issuer mismatch | Token issuer does not match the expected issuer (https://id.equos.one) |
token audience mismatch | Token audience does not include https://api.equos.one |
token missing sub claim | Token does not have a subject claim |
unknown application | Token subject (client_id) not found in system |
Permission Error Codes (403)
| Error | Description |
|---|---|
api key revoked | API key has been revoked |
api key expired | API key expiration date has passed |
insufficient scope: orders:write required | Token does not have orders:write scope |
Job Result Schema
All order endpoints return a jobId and process asynchronously. Track the job's completion status using the job ID returned in the response.
Document Status Lifecycle: pending → processing → completed or failed
- Pending: Immediately after order creation
- Completed: When processing succeeds; includes
result.orderIdandcompletedAttimestamp - Failed: When processing fails; includes an
errorMessagestring (there is no separatefailedAt— the job keeps itscreatedAt)
See the Examples page for code to poll this document until job completion.
Contacts API
The contacts endpoints allow you to discover customers, suppliers, and other contacts in your account before creating orders. List and search read the published ContactIndex. Get-by-id reads the live contact document (full address, ABN, notes).
Authentication
All contacts endpoints require the contacts:read scope in your API key and Bearer token authentication.
Address Validation
List returns a contact when it has:
- At least one contact method: email, phone, or mobile
- Locality: suburb and postcode (street is optional on list)
- Valid postcode: postcode/suburb combination exists in the Australian postcode database (or the CSV was unavailable — then
unverified)
List endpoint validation: Uses the Australian postcode database (fast, no network calls). addressStatus is postcode_only or unverified. Archived contacts and invalid postcodes are excluded. If no ContactIndex is published, list returns 404 "Contact index not published".
Single contact validation: Tries the address server (full address matching) first, then falls back to the postcode database. Returns the contact even when addressStatus is invalid.
List Contacts
GET /e9v3/contacts
Pages the published ContactIndex. Optional search filters by name, business name, or email. limit is a page size (default 100, max 500). Omit cursor for the first page — existing clients that only send search/limit still work. When hasMore is true, pass nextCursor as cursor to continue.
Query Parameters
search(optional, string): Filter by contact name, business name, or email (case-insensitive)limit(optional, integer): Page size (default 100, max 500)cursor(optional, string): Previous page'snextCursor. Omit for the first page.
Example Request
GET /e9v3/contacts?search=acme&limit=100
Authorization: Bearer <jwt>Example Response (200 OK)
{
"success": true,
"contacts": [
{
"contactId": "cust-123",
"businessId": "biz-456",
"name": "Acme Corporation",
"businessName": "Acme Corporation",
"email": "orders@acme.com",
"phone": "+1-555-1234",
"mobile": "+1-555-5678",
"fax": "+1-555-9999",
"website": "acme.com",
"contactType": "customer",
"status": "active",
"priority": "high",
"address": {
"line1": "123 Main St",
"line2": "Suite 200",
"suburb": "Sydney",
"state": "NSW",
"postcode": "2000",
"country": "Australia"
},
"business": {
"abn": "12345678901",
"acn": "987654321",
"isGstRegistered": true
},
"metadata": {
"notes": "Key account",
"category": "wholesale",
"tags": ["vip", "bulk-discount"],
"createdAt": "2024-01-01T10:00:00Z",
"updatedAt": "2024-01-15T14:30:00Z",
"createdBy": "admin@equos.one",
"updatedBy": "user@company.com"
},
"integration": {
"externalId": "shopify-12345",
"source": "shopify"
},
"addressStatus": "postcode_only"
}
],
"count": 1,
"hasMore": false
}Get Single Contact
GET /e9v3/contacts/:contactId
Retrieve full details for a specific contact by ID. Returns all contacts with full address validation details via the addressStatus field (uses addressr for full address matching when available).
Path Parameters
contactId(required, string): The contact's unique identifier
Example Request
GET /e9v3/contacts/cust-123
Authorization: Bearer <jwt>Example Response (200 OK)
{
"success": true,
"contacts": [
{
"contactId": "cust-123",
"businessId": "biz-456",
"name": "Acme Corporation",
"businessName": "Acme Corporation",
"email": "orders@acme.com",
"phone": "+1-555-1234",
"address": {
"line1": "123 Main St",
"suburb": "Sydney",
"state": "NSW",
"postcode": "2000",
"country": "Australia"
},
"addressStatus": "validated"
}
],
"count": 1
}Example Response (404 Not Found)
{
"success": false,
"error": "Contact not found",
"count": 0
}Contact Response Fields
All contact responses include the following structure:
Contact Fields
| Field | Type | Required | Description |
|---|---|---|---|
contactId | string | Yes | Unique contact identifier |
businessId | string | — | Business ID this contact belongs to |
name | string | Yes | Contact name (primary or business name) |
businessName | string | — | Official business name |
email | string | * | Email address (*required or phone/mobile) |
phone | string | * | Phone number (*required or email/mobile) |
mobile | string | — | Mobile number |
fax | string | — | Fax number |
website | string | — | Website URL |
contactType | string | Yes | Type: customer, supplier, location, or agent |
status | string | Yes | Status: active, inactive, or archived |
priority | string | — | Priority: low, medium, high, or critical |
addressStatus | string | Yes | Address validation status: validated (addressr full address match), postcode_only (postcode/suburb match), invalid (postcode mismatch), or unverified (validation unavailable) |
Address Validation Statuses
| Status | Meaning |
|---|---|
validated | Full address matched against addressr (Australian address database). Highest confidence. |
postcode_only | Postcode/suburb combination exists in the Australian postcode database. Medium confidence. |
invalid | Postcode/suburb combination not found in any database. Contact is excluded from list endpoint. |
unverified | Address validation database unavailable at startup. Contacts returned without validation. |
Address Object
| Field | Type | Required | Description |
|---|---|---|---|
line1 | string | Yes* | Street address line 1 (*required for complete contact) |
line2 | string | — | Street address line 2 |
suburb | string | Yes* | Suburb/city (*required for complete contact) |
state | string | — | State/province |
postcode | string | Yes* | Postal code (*required for complete contact) |
country | string | — | Country (default: Australia) |
Business Object
| Field | Type | Description |
|---|---|---|
abn | string | Australian Business Number |
acn | string | Australian Company Number |
isGstRegistered | boolean | Whether the contact is GST registered |
Metadata Object
| Field | Type | Description |
|---|---|---|
notes | string | Contact notes |
category | string | Contact category |
tags | array | Array of tag strings |
role | string | Role (for agents) |
createdAt | string (RFC3339) | Contact creation timestamp |
updatedAt | string (RFC3339) | Last update timestamp |
createdBy | string | User who created the contact |
updatedBy | string | User who last updated the contact |
Integration Object
| Field | Type | Description |
|---|---|---|
externalId | string | External system record ID (e.g., Shopify customer ID) |
source | string | Source integration (e.g., "shopify", "airtable") |
Shopper Data API (GDPR)
Export or erase what EQUOS holds about one shopper, for Shopify's mandatory privacy webhooks (customers/data_request, customers/redact, shop/redact) or any equivalent request. Everything is limited to the key's business. Each call is synchronous, bounded, idempotent and writes one audit row (key, request id, which inputs were given, matched contact and order ids, statuses and counts; never the personal data).
Matching
Send at least one of externalRef, email, phone. Inputs are ORed; every matched contact is reported with matchedBy.
externalRef: exact match of{provider}:{store}:customer:{id}(e.g.shopify:acme.myshopify.com:customer:7012345), the ref send-matched stores. Erased contacts keep a SHA-256 of the ref, so repeat calls by ref reportalready_erased.email: case-insensitive.phone: digits only, last 10 digits, AU+61and0forms are equal. Fewer than 8 digits and 1300/1800/1900 numbers are rejected (400).
More than 25 matching contacts returns 422 and changes nothing. No match is a 200 (no_match / count: 0).
POST/e9v3/contacts/exportExport a shopper's personal data
Body: { externalRef?, email?, phone?, requestId?, reason? }. Returns each matched contact (contact record, externalRefs, status) and, when the key may read orders (orders:read: all types, orders:write: send orders), each order's summary, the personal fields stored on it (personalData) and its lines. Read-only.
POST/e9v3/contacts/eraseErase a shopper's personal data
Body: { externalRef?, email?, phone?, requestId?, reason?, dryRun? }. Blanks the contact's name, email, phone, address fields and notes, removes its external refs and archives it; blanks the shopper's name, email, phone and addresses on their orders (customer or supplier role), on freight consignment receiver details and on API-imported order notes; scrubs settled order jobs and stored webhook payloads. Order numbers, references, status, dates, totals, metadata and lines are kept. Response: outcome erased | already_erased | no_match | would_erase | partial, plus one result per contact. Repeat on partial.
POST/e9v3/contacts/erase/shopErase every shopper of a Shopify shop
Body: { shop, limit? (1–100, default 25), cursor?, requestId?, reason?, dryRun? }. Erases one page of contacts linked to shopify:{shop}:customer:*; call again with nextCursor until complete is true. A contact also linked to another shop is only unlinked (status unlinked): its refs for this shop are removed and only this shop's orders are blanked.
POST /e9v3/contacts/erase
Authorization: Bearer <jwt>
Content-Type: application/json
{
"externalRef": "shopify:acme.myshopify.com:customer:7012345",
"email": "jordan.lee@example.com",
"requestId": "shopify-webhook-5f0c",
"reason": "customers/redact"
}
200 OK
{
"success": true,
"outcome": "erased",
"requestId": "shopify-webhook-5f0c",
"auditId": "813",
"dryRun": false,
"matched": 1,
"contacts": [
{
"contactId": "con_8f3a2b1c",
"matchedBy": ["email", "externalRef"],
"status": "erased",
"ordersMatched": 3,
"ordersErased": 3,
"ordersRemaining": false,
"jobsScrubbed": 1,
"jobsInFlight": 0,
"refsRemoved": 1
}
],
"totals": { "contacts": 1, "ordersErased": 3, "jobsScrubbed": 1, "webhookLogsRedacted": 4, "shopifyDeliveriesRedacted": 2 }
}Inventory API
Read stock levels, and declare absolute on-hand quantities from an external source of truth.
GET/e9v3/inventoryList inventory
Lists active inventory items with availableQuantity and totalQuantity. Query parameters: search (SKU / product name / category / barcode), limit (default 100, max 500 per page), cursor (optional — omit for the first page). Existing clients that only send search/limit still get the first page; hasMore and nextCursor are additive. Loop while hasMore is true, passing nextCursor as cursor, to fetch the full catalog.
GET/e9v3/inventory/{itemId}Get inventory item
Returns a single inventory item with stock levels. The response uses the SAME shape as the list endpoint — { success, inventory: [item], count: 1 } — there is no top-level item object. An unknown itemId returns 404 { success: false, error: 'Inventory item not found' }.
POST/e9v3/inventory/syncDeclare on-hand quantity
Sets (not adjusts) the absolute on-hand quantity for a SKU. Applied through the platform's transactional inventory pipeline as an asynchronous job — returns 200 { success: true, jobId }. Body: { sku, quantity, sourceRecordId?, metadata? }.
- Validates the SKU: an unknown SKU returns
404(unlike the order writes, which do not check SKUs up front) - Idempotent: retries with the same
sourceRecordIdare deduplicated via a deterministic job id - Absolute, not delta:
quantityis the new on-hand count, not an adjustment
POST/e9v3/inventory/itemsUpsert catalog item
Creates or updates a catalog item keyed by SKU (Firestore Inventory + Postgres metadata). Does not change stock levels. Idempotent on sourceRecordId+SKU. Returns 409 when the SKU belongs to another sourceRecordId. SKU max length 256. active:false archives the item.
POST/e9v3/inventory/items/batchBatch upsert catalog items
Up to 50 items per request with per-item results (partial success). Same fields as the single upsert route.
POST/e9v3/inventory/items/{sku}/archiveArchive catalog item
Archives a catalog item by SKU (InventoryArchive job). Never deletes stock or hard-deletes documents.
Catalog API
Serve the published, quantity-free product feed for storefronts and static site generators — with feed-tier prices merged in by effective SKU.
GET/e9v3/catalogGet a catalog feed batch
Returns one batch of the published B2B catalog snapshot (500 items per batch). Query parameter: batch (zero-based, default 0; non-integer or negative → 400). This is a snapshot read — it does not touch live inventory. Iterate batch=0..version.batchCount-1 to page the whole feed.
- Effective SKU: each item's
skuis the parent SKU;variantSku(when present) is the orderable variant. The price join key isvariantSku ?? sku. - Prices: when a public feed tier is configured (Console → Pricing → Public feed tier) and published,
priceandgstare merged per item and a top-levelpricingblock names the tier. Omitted when no tier is set or it is unpublished. - ETag / revalidation: responses carry a strong
ETag; send it back inIf-None-Matchto get304 Not Modified(empty body) when the feed is unchanged.Cache-Control: no-cache. - 404 bodies:
{ success: false, error: "Catalog not published" }when nothing has been published;{ success: false, error: "Batch out of range" }whenbatch >= version.batchCount. - Rebuilds: subscribe to the
catalog.publishedandprices.publishedwebhooks to re-pull the feed when it changes.
Manufacturing API
GET/e9v3/manufacturingList manufacturing tasks
Lists manufacturing tasks of one type. Query parameters: type (optional, default create; one of create, assembly, quality, batch), archived (boolean, default false — set true to list archived tasks), status (filter by derived status: pending, finalized, committed, completed, voided), search (task number / description), limit (default 100, max 500). status and search are filtered in-memory over the first limit+1 documents; there is no cursor pagination.
GET/e9v3/manufacturing/{taskType}/{taskId}Get manufacturing task
Returns a single manufacturing task.
Address API
GET/e9v3/addresses/searchSearch Australian addresses
Validates and autocompletes Australian addresses against G-NAF. Query parameter: q (the partial address).
Secure Documents API
Upload a document and get back a hosted, shareable link. Synchronous — returns the URL directly, with no job to poll.
POST/e9v3/secure_doc_deliveryUpload a document for secure delivery
Uploads one or more documents as multipart/form-data and returns a hosted, shareable link. Unlike the order and inventory writes, this is synchronous: it responds 201 { document_url } directly — there is no job to poll. Document upload reuses the orders:write scope.
files(binary, required, repeatable) — one or more files to upload; repeat the part to send several in one request. A legacy singlefilepart is also accepted.order_id(text, optional) — order reference; labels the stored document (Order {order_id}) and is recorded as its task number.
- At least one file is required.
- PDF and CSV only — any other type is rejected with
400 invalid_file_type. - Combined size ≤ 32 MiB across all parts (
SDD_MAX_UPLOAD_BYTES).
{
"document_url": "https://docs.equos.one/r/a1b2c3"
}document_url is absolute ({publicBaseURL}/r/{shortCode}) when a public base URL is configured, otherwise root-relative (/r/{shortCode}).
Error bodies use the shape { error, code }.
- 400:
parse_error(malformed multipart),file_required(no file part),invalid_file_type(a non-PDF/CSV file) - 401: missing or invalid Bearer token
- 429: rate limit exceeded (1 request/sec per key, burst 10)
- 500:
shortcode_error,create_link_error,file_open_error,upload_error