Public API Order Hub - Orders & Order Lines Guide
How to manage orders and their line items on the Retraced platform via the Public API.
The Order Hub exposes orders and their line items:
- Orders (
/api/v2/orders) - the commercial document: order number, buyer, supplier, dates, status, and its line items. - Order lines (
/api/v2/order-lines) - the line items on an order (which style, how much, in what unit), exposed read-only (GETonly).
An order line only ever exists as part of an order, so all line writes happen through the order: create lines nested in POST /orders, and add/change/remove them later with PATCH /orders/:id. Deleting an order deletes its lines. You read lines back via the GET /order-lines endpoints.
Setup
# Base URL
BASE_URL="https://publicapi.retraced.com/api/v2"
# Authentication - use your company API key
API_KEY="your-company-api-key"
All examples use curl. The API key is passed via the companyapikey header. Every operation is scoped to the company the API key belongs to - you can only see and manage your own orders.
Orders
Create an order
curl -X POST "$BASE_URL/orders" \
-H "companyapikey: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"orderNumber": "PO-2026-0042",
"status": "IN_PROGRESS",
"orderSubType": "STANDARD",
"tags": ["priority"],
"buyerCompanyId": "<buyer-company-id>",
"supplierCompanyId": "<supplier-company-id>",
"receiverCompanyId": "<receiver-company-id>",
"vendorCompanyId": "<vendor-company-id>",
"factory": "<factory-company-id>",
"buyerFacilityProcess": "QUALITY_ASSURANCE",
"supplierFacilityProcess": "WEAVING_MILL",
"receiverFacilityProcess": "LAUNDRY",
"orderDate": "2026-07-01",
"deliveryDate": "2026-09-15",
"agreedDeliveryDate": "2026-09-10",
"expectedShippingDate": "2026-08-28",
"shippingDate": "2026-08-30",
"handoverDate": "2026-09-15",
"shipmentReference": "SHIP-2026-0042",
"internalResponsiblePersons": ["<user-id-or-email>"],
"counterpartyOrderNumberRef": "WW-SO-2026-118",
"lines": [
{
"orderLineNumber": "PO-2026-0042-PACKAGING",
"styleId": "<style-id>",
"quantity": 300,
"unit": "PIECES",
"secondaryQuantity": 120.5,
"secondaryUnit": "KILOGRAM"
}
]
}'
Only orderNumber, buyerCompanyId and supplierCompanyId are required - every other field can be omitted. Nested lines are created atomically with the order and are returned in the create response (and on GET /orders/:id), each carrying its server-assigned id and lineNo.
- The order owner must be a party to the order: your authenticated company must be the
buyerCompanyIdor thesupplierCompanyId, otherwise the request is rejected with400(codeorder_owner_not_party). - Receiver defaults to the buyer: if you omit
receiverCompanyId, it defaults tobuyerCompanyId(andreceiverFacilityProcessdefaults tobuyerFacilityProcess).receiverCompanyIdandreceiverFacilityProcessalways travel together - sending one without the other is rejected with400, on create and onPATCHalike. orderType,tracingStatusandisArchivedmay be present in the body for convenience but are ignored on create - the first two are computed by the server, and a new order always starts unarchived (archive it afterwards withPATCH).- A successful create returns
201 Createdwith the new order (including its nestedlines) indata. - Creates are not idempotent and are not rate limited: retrying a
POSTafter a timeout can produce a second order unless theorderNumbercollides (see below). Use your ownorderNumberas the natural key and re-fetch with?orderNumber=...before retrying. - Only the first error is reported. Reference lookups (company, style, responsible person →
404) can be reported before other field validation (400), so fix errors one at a time rather than expecting a complete list.
Request body fields:
| Field | Type | Required | Notes |
|---|---|---|---|
orderNumber |
string | yes | 1–255 characters (exact match - no trimming or case folding). Must be unique within your company for the same order type - a duplicate returns 400 with code order_number_already_existing. |
lines |
array | no | Nested order lines, created atomically with the order (a failing line rolls the whole request back). Each entry takes the per-line fields minus id. May be omitted or empty; lines can also be added later via PATCH /orders/:id. |
buyerCompanyId |
string | yes | Company ID of the buyer (max 36 chars). |
supplierCompanyId |
string | yes | Company ID of the supplier (max 36 chars). |
orderSubType |
enum | no | STANDARD, REPEAT, COMMIT, SAMPLE or EXTRACTION_SPLIT. |
status |
enum | no | IN_PROGRESS, IN_REVIEW or COMPLETED. |
tags |
string[] | no | Free-form tags. Stored as one comma-joined string: each tag is trimmed, empty tags are dropped, and a tag that itself contains a comma comes back as two tags - avoid commas inside a tag. Not nullable; clear all tags with "tags": []. |
receiverCompanyId / vendorCompanyId / factory |
string | no | Additional party company IDs (max 36 chars each). |
buyerFacilityProcess / supplierFacilityProcess / receiverFacilityProcess |
string | no | Facility process IDs (e.g. CUT_MAKE_TRIM) from GET /facility-processes - not display names. An unknown ID returns 400 (invalid_facility_process_ids). |
orderDate, deliveryDate, agreedDeliveryDate, expectedShippingDate, shippingDate, handoverDate, actualDeliveryDate |
date | no | ISO calendar date, YYYY-MM-DD. |
expectedDeliveryDate |
date | no | Deprecated - use handoverDate. Auto-mapped to handoverDate on write; when both are sent, handoverDate wins and this field is ignored (never an error). Always mirrored back in the response. |
shipmentReference |
string | no | Max 255 chars. |
internalResponsiblePersons |
string[] | no | Email addresses or user IDs of users in your company - the server resolves each email to a user ID before saving. Emails match case-insensitively, duplicates are removed, and empty strings are skipped. The user must have your company as their primary company. An unknown email/ID returns 404 (responsible_person_not_found). |
internalResponsiblePerson |
string | no | Deprecated - use internalResponsiblePersons. Accepts a single email/user ID, auto-mapped to a one-element internalResponsiblePersons. Do not send both fields (400). An empty string is rejected with 400. |
counterpartyOrderNumberRef |
string | no | Your counterparty's own order number reference. |
orderTypeis computed by the server - you never send it. It is derived from how the buyer/supplier relate to your (authenticated) company: you are the buyer →PURCHASE_ORDER, you are the supplier →SALES_ORDER, you are both →INTERNAL_ORDER.
Response (201):
{
"metadata": { "success": true },
"data": {
"id": "order_01KWHF7TD318JZA3KM9A9E9979",
"forCompanyId": "<your-company-id>",
"orderNumber": "PO-2026-0042",
"orderType": "PURCHASE_ORDER",
"orderSubType": "STANDARD",
"status": "IN_PROGRESS",
"tags": ["priority"],
"buyerCompanyId": "<buyer-company-id>",
"supplierCompanyId": "<supplier-company-id>",
"receiverCompanyId": "<receiver-company-id>",
"vendorCompanyId": "<vendor-company-id>",
"factory": "<factory-company-id>",
"buyerFacilityProcess": "QUALITY_ASSURANCE",
"supplierFacilityProcess": "WEAVING_MILL",
"receiverFacilityProcess": "LAUNDRY",
"orderDate": "2026-07-01",
"deliveryDate": "2026-09-15",
"agreedDeliveryDate": "2026-09-10",
"expectedShippingDate": "2026-08-28",
"shippingDate": "2026-08-30",
"handoverDate": "2026-09-15",
"expectedDeliveryDate": "2026-09-15",
"actualDeliveryDate": null,
"shipmentReference": "SHIP-2026-0042",
"internalResponsiblePersons": ["<user-id>"],
"internalResponsiblePerson": "<user-id>",
"counterpartyOrderNumberRef": "WW-SO-2026-118",
"isArchived": false,
"tracingStatus": null,
"createdAt": "2026-07-02T10:30:00.000Z",
"createdByApiKeyId": "<api-key-id>",
"updatedAt": "2026-07-02T10:30:00.000Z",
"lineCount": 1,
"lines": [
{
"id": "ordline_01KWHF7XAY4Q74CAVXYAPJ6FJV",
"orderLineNumber": "PO-2026-0042-PACKAGING",
"lineNo": 1,
"styleId": "<style-id>",
"quantity": 300,
"unit": "PIECES",
"secondaryQuantity": 120.5,
"secondaryUnit": "KILOGRAM",
"tracingReflectionOrderId": null,
"tracingOrderRequestedAt": null
}
]
}
}
(Response trimmed - every order also carries full createdBy* / updatedBy* audit fields. Fields you never sent come back as null; tracingStatus is system-computed and read-only.)
internalResponsiblePerson(singular) is a deprecated read-only mirror ofinternalResponsiblePersons[0](ornullwhen there are none); prefer the array.lineCountis the number of lines on the order.linesis the full nested line array onPOST /ordersandGET /orders/:id, but is always[]onGET /orders(list) - useGET /order-lines?orderId=...to page a list's lines. Nestedlinescome back sorted by ascendinglineNo.
Store data.id - you need it to manage order lines and for later updates. Order ids are server-generated with an order_ prefix (order lines use ordline_); treat them as opaque strings.
The party fields (who's who)
An order carries up to five company references. Only buyer and supplier are required and enforced (your company must be one of them); the rest are optional descriptive parties. How brands typically use them:
| Field | Typically represents |
|---|---|
buyerCompanyId |
The company placing the order. |
supplierCompanyId |
The company receiving the order - the agent or supplier on the other side of it. |
receiverCompanyId |
Who takes delivery of the goods. Defaults to the buyer when omitted. |
vendorCompanyId |
An intermediary between the agent and the factory, when one is involved. |
factory |
The company / site where the goods are actually manufactured. |
The FacilityProcess fields (buyerFacilityProcess, supplierFacilityProcess, receiverFacilityProcess) name the process each party performs. They take a facility process ID from Retraced's shared list - CUT_MAKE_TRIM, WEAVING_MILL, QUALITY_ASSURANCE, … - not a free-text label:
# List the valid IDs (page to metadata.pagination.totalPages - there are more than one page of them)
curl "$BASE_URL/facility-processes?page=1&limit=100" -H "companyapikey: $API_KEY"
An unrecognised value is rejected with 400 (code invalid_facility_process_ids), and the error message names the offending IDs. See the Reference data section for the other ID lookup endpoints.
⚠️ Supply chain responses (
GET /supply-chains/{id}) expose facility process display names such as"Cut make trim (CMT)". Those are not valid values here - always write the ID.
Which company is which is your convention, not a rule the platform enforces: Retraced only checks that your company is the buyer or supplier, that the receiver pair is supplied together (see Create an order), and that each facility process ID exists.
Order status
status (IN_PROGRESS, IN_REVIEW, COMPLETED) is not tied to any Retraced workflow - the platform never acts on it or transitions it for you. Use it to carry whatever state your own process needs.
Date fields
Business dates (orderDate, agreedDeliveryDate, deliveryDate, …) are calendar dates in YYYY-MM-DD with no time or timezone, stored and returned verbatim; createdAt / updatedAt are ISO 8601 UTC timestamps set by the server. See Dates & time for the full rules.
Archiving vs. deleting
An order can be archived (reversible) or deleted (permanent) - they are different:
- Archive via
"isArchived": trueonPATCH /orders/:id(unarchive withfalse). The order and its lines are kept intact and stay fully readable over the API - archiving only hides the order from the order lists and the analytics and reporting views inside the Retraced platform UI. Over the API, omitisArchivedto list both archived and active orders, or filter explicitly withisArchived=true/false. - Delete via
DELETE /orders/:idis a hard delete - the order and all its lines are permanently removed and can't be recovered.
Tracing side effects
Once an order line has been sent to Retraced's tracing module (its tracingReflectionOrderId is set), order writes carry over to the tracing request created from that line:
- Removing a sent line - omitting it from
linesonPATCH /orders/:id, or deleting the whole order - also deletes the tracing request built from it, including anything your counterparty can see on it. Integrations that sync by delete-and-recreate will destroy tracing data; update orders in place instead. - Renaming a sent line (
orderLineNumber) renames the tracing request's reference number to match. The new number must not collide with another tracing order of yours, otherwise thePATCHfails with400 order_number_already_existingand nothing is changed. - Structural fields lock once the counterparty builds on your request. After they link their own order to it or extend the supply chain under it, changing
buyerCompanyId,supplierCompanyId,receiverCompanyIdor any of the facility-process fields returns409 order_structural_fields_locked_by_tracing. All other fields stay editable.
Visibility & scoping
One rule decides which orders your API key can see, and it surprises integrators:
- Every order belongs to exactly one company - the one that created it. An order where you are the counterparty is a separate order row owned by them, and it is not readable with your key even though your company is named on it as buyer or supplier. You only ever read and write your own company's orders.
List orders
curl "$BASE_URL/orders?page=1&limit=20&sort=updatedAt&order=DESC&status=IN_PROGRESS" \
-H "companyapikey: $API_KEY"
Query parameters:
| Parameter | Type | Default | Notes |
|---|---|---|---|
page |
number | 1 |
1-based page number. |
limit |
number | 20 |
Page size, 1–100. |
sort |
enum | updatedAt |
updatedAt, createdAt, orderNumber, orderDate or status. |
order |
enum | desc |
ASC or DESC. |
type |
string | - | Filter by computed order type, e.g. PURCHASE_ORDER. |
supplier / buyer |
string | - | Filter by supplier / buyer company ID. |
status |
string | - | Filter by status. |
tag |
string | - | Filter by a single tag. Matches a whole tag, case-insensitively - not a substring. One value only. |
orderNumber |
string | - | Filter by order number (exact match, case- and whitespace-sensitive). |
isArchived |
true | false |
- | Filter archived / non-archived orders. Omit to get both. |
updatedAfterUnixMs / updatedBeforeUnixMs |
number | - | Unix timestamps in milliseconds; useful for incremental syncs. Both bounds are inclusive, so an order modified exactly on your cursor is returned again - de-duplicate by id on your side. |
Deleted orders are physically removed and never returned.
- Unrecognised query parameters are ignored, not rejected - a typo in a filter name silently returns unfiltered results. (Unknown fields in a request body are still rejected with
400.) type,statusandorderNumberare not validated: a value that matches nothing (including a misspelled enum) returns an empty page with200, not a400.- Ordering is stable across pages: rows with an identical sort value keep a fixed relative order, so paging through a result set never skips or repeats an order. See Pagination, sorting & incremental sync for the shared paging rules.
Response (200):
{
"metadata": {
"success": true,
"pagination": { "page": 1, "limit": 20, "total": 42, "totalPages": 3 }
},
"data": [ { "id": "order_01KWHF7TD318JZA3KM9A9E9979", "orderNumber": "PO-2026-0042", "...": "..." } ]
}
Fetch, update, delete
# Fetch one order
curl "$BASE_URL/orders/<order-id>" -H "companyapikey: $API_KEY"
# Partial update - send only the fields you want to change
curl -X PATCH "$BASE_URL/orders/<order-id>" \
-H "companyapikey: $API_KEY" \
-H "Content-Type: application/json" \
-d '{ "status": "COMPLETED", "actualDeliveryDate": "2026-09-12" }'
# Delete (hard - also deletes the order's lines)
curl -X DELETE "$BASE_URL/orders/<order-id>" -H "companyapikey: $API_KEY"
PATCHhas partial semantics: omitted fields keep their value. Nullable fields (dates, party IDs, …) can be cleared by sendingnull. You can also archive/unarchive via"isArchived": true|false.- An empty
PATCHbody ({}) is accepted and still counts as a write: it bumpsupdatedAtand records an audit entry. Skip the call client-side when you have nothing to change, or it will resurface in everyupdatedAfterUnixMssync. orderTypeis re-derived on everyPATCHfrom the current buyer/supplier. Changing either party can flip the type (e.g.PURCHASE_ORDER→SALES_ORDER) and, with it, the uniqueness group yourorderNumberlives in. Once your orderLine is sent to tracing, you cannot change the parties anymore.- Sending a
linesarray reconciles the order's lines - see Update an order's lines. - Changing
orderNumber- or changingbuyerCompanyId/supplierCompanyIdin a way that re-derives the order type - returns400(codeorder_number_already_existing) if the number is already taken in the resulting (company, order type) group. DELETEis a hard delete (returns204): the order and all of its lines are permanently removed - this cannot be undone. Deleting an already-deleted or foreign order returns404.- Tracing side-effects: if the order (or any of its lines) is part of a tracing chain, a
PATCHre-syncs the linked tracing orders and notifies the counterparty, and aDELETEcascades to remove the counterparty's mirrored tracing data. NestedstyleIds must reference a non-archived style your company owns, otherwise the write is rejected. - The receiver pair is enforced here too: send
receiverCompanyIdandreceiverFacilityProcesstogether, or neither. Sending one alone - or clearing one withnullwhile setting the other - is rejected with400. To remove the receiver, send both asnull.
Update an order's lines
Send a lines array on PATCH /orders/:id to declaratively full-sync the order's lines - the array is the desired final set:
- an entry with an
idupdates that existing line (only the fields you include change); - an entry without an
idcreates a new line (styleId,quantity,unitare then required); - any existing line absent from the array is deleted.
Leaving something out means two different things depending on the level:
- no
lineskey in the body - the order's lines are left completely untouched; - a
linesarray that a line is missing from - that line is deleted.
So once you send lines at all, it must be the complete set you want the order to end up with. The safe recipe: read the current lines with GET /order-lines?orderId=..., then add, change or drop entries in that list and send the whole list back.
curl -X PATCH "$BASE_URL/orders/<order-id>" \
-H "companyapikey: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"status": "IN_REVIEW",
"lines": [
{ "id": "ordline_01KWHF7XAY4Q74CAVXYAPJ6FJV", "quantity": 1750 },
{ "id": "ordline_01KWHF7YSW1A1R34E1DMEHMAK5", "secondaryQuantity": null },
{ "styleId": "<style-id>", "quantity": 250, "unit": "PIECES" }
]
}'
In the example the first line is updated, the second clears its secondary quantity (which also clears secondaryUnit), a third line is created, and any other existing line is deleted. The order's scalar fields and its lines are reconciled atomically - if any line fails (e.g. a duplicate orderLineNumber, 409), the whole PATCH rolls back. A bare { "id": "..." } entry keeps that line unchanged (its audit fields are not touched). Fetch the resulting lines with GET /order-lines?orderId=....
Reconciliation details worth knowing:
- The server reconciles in the order delete → update → insert. A line you create in the same request can therefore take an
orderLineNumber(orlineNo) that a line deleted in that request just freed up. - Swapping
orderLineNumberbetween two existing lines in onePATCHreturns409, because both values exist while the updates are applied. Do it in two requests: move the first line to a temporary number, then assign the final numbers. - Changing the order's
orderNumberdoes not renumber existing lines. Auto-generatedorderLineNumberkeep the old prefix, so an order can end up with mixed prefixes; setorderLineNumberexplicitly on the samePATCHif you need them aligned. - ⚠️ Removing a line that is already part of a tracing chain (
tracingReflectionOrderIdis notnull) removes the line from your order, and the linked tracing order is re-synced with the traced lines that remain. If the line you removed was the last traced line of that tracing order, the tracing order mirrored at your counterparty is currently left in place and no notification is sent - a known limitation. Because a full-syncPATCHdeletes every line you omit, always build thelinesarray from a fresh read, and treat traced lines as removal-sensitive.
See the order line fields for the accepted per-line properties.
Order lines (read-only)
Order lines are created, updated, and deleted only through their parent order (see Create an order and Update an order's lines). Over the Public API they are exposed read-only.
Order line fields
The per-line object used inside a create/update lines array:
| Field | Type | Required | Notes |
|---|---|---|---|
id |
string | on update | The line's ordline_ id. Present → update that line; absent → create a new line. |
styleId |
string | on create | A style owned by your company, addressed by either its Retraced ID or the externalId you gave it via the Products API, else 404. Required when creating (no id). Responses always return the Retraced ID. |
quantity |
number | on create | Positive and below 1e17. Stored with 2 decimal places - more precision is rounded half-up (10.005 → 10.01), so read the value back rather than assuming it round-trips. Required when creating. |
unit |
enum | on create | Unit of measure for quantity - one of the unit values below (e.g. PIECES, KILOGRAM, METRE). Required when creating. |
orderLineNumber |
string | no | 1–255 chars. Must be unique within the order. If omitted on create, auto-generated as {orderNumber}-{orderType}-L{lineNo} (e.g. PO-2026-0042-PURCHASE_ORDER-L1). |
secondaryQuantity |
number | null | no | Optional second measurement (positive, 2 decimal places). Always a weight in kilograms. Send null on update to clear it. |
secondaryUnit |
enum | null | no | Always derived from secondaryQuantity, never set directly. Setting a quantity sets the unit to KILOGRAM; clearing the quantity clears the unit. KILOGRAM and null are still accepted for backwards compatibility, but sending secondaryUnit without secondaryQuantity in the same line entry has no effect. To repair a line whose unit and quantity disagree, send its secondaryQuantity again. |
lineNois assigned by the server (1, 2, 3, … per order) and never accepted as input. Within one request, new lines are numbered after the highestlineNoamong the lines that request keeps - so if aPATCHremoves the order's highest-numbered line and adds another, the freedlineNo(and any auto-generatedorderLineNumberbuilt from it) is reused. TreatlineNoas a display position, not a stable identifier: use the lineidfor that. AnorderLineNumberthat already exists on the order - or that appears twice in one request - returns409with codeorder_not_unique, and the whole write rolls back.
Unit values
unit accepts the unit enum below. Any other value is rejected with 400.
| Group | Values |
|---|---|
| Count / textile | PIECES, PAIR, PERCENTAGE, DENIER, JUTE, ENGLISH_COUNT, METRIC_COUNT, WORSTED_COUNT, TEX |
| Weight / mass | MILLIGRAM, GRAM, KILOGRAM, METRIC_TON, POUNDS, OUNCE, STONE, SHORT_TON, LONG_TON, HUNDREDWEIGHT, SHORT_HUNDREDWEIGHT, LONG_HUNDREDWEIGHT, DRAM, GRAIN, PENNYWEIGHT, SCRUPLE, GRAM_PER_SQUARE_METER, GRAM_PER_SQUARE_FOOT, GRAM_PER_SQUARE_YARD, OUNCE_PER_SQUARE_YARD |
| Length | MILLIMETRE, CENTIMETRE, DECIMETRE, METRE, INCH, FOOT, YARD, MILE, NAUTICAL_MILE, FURLONG, ROD, FATHOM |
| Area | SQUARE_CENTIMETRE, SQUARE_DECIMETRE, SQUARE_METRE, SQUARE_INCH, SQUARE_FEET, SQUARE_YARD, SQUARE_ROD, SQUARE_MILE, ACRE |
| Volume | MILLILITRE, CENTILITRE, LITRE, CUBIC_DECIMETRE, CUBIC_METRE, CUBIC_INCH, CUBIC_FOOT, CUBIC_YARD, GALLON, QUART, PINT, GILL, FLUID_OUNCE, FLUID_DRAM, MINIM, BUSHEL, PECK, CORD, ACRE_FOOT |
List order lines
# Lines of one order
curl "$BASE_URL/order-lines?orderId=<order-id>&page=1&limit=20" \
-H "companyapikey: $API_KEY"
# All lines across all your orders (omit orderId)
curl "$BASE_URL/order-lines?page=1&limit=100" -H "companyapikey: $API_KEY"
Pagination works exactly like orders (page 1-based, limit 1–100 default 20, same metadata.pagination envelope). Lines of deleted orders are never returned.
orderIdis the only filter this endpoint accepts. There are nosort/orderparameters: lines always come back ordered by ascendinglineNo, stable across pages.- An
orderIdthat doesn't exist, or belongs to another company, returns200with an emptydataarray - not404. - Every line carries its parent
orderId, so you can list lines across all your orders and group them client-side.
Each line additionally carries two read-only tracing fields - tracingReflectionOrderId (the tracing order this line is reflected into, or null) and tracingOrderRequestedAt (null until tracing is requested) - plus an embedded order summary object (id, orderNumber, orderType, buyerCompanyId, supplierCompanyId, orderDate, updatedAt, updatedByUserName, tracingStatus, tags). The order object is populated on both the list endpoint and GET /order-lines/:lineId, so a single line read already tells you which order it belongs to.
Fetch a single line
curl "$BASE_URL/order-lines/<line-id>" -H "companyapikey: $API_KEY"
Returns the line (with full audit fields), or 404 when the line does not exist, belongs to another company, or its parent order has been deleted.
Orders and order lines both carry the same 16 audit fields - createdBy and updatedBy in each of these variants: …UserId, …UserName, …CompanyId, …CompanyName, …ImpersonateId, …ImpersonateName, …ApiKeyId, …ApiKeyLabel. Writes made with an API key populate the ApiKey* pair and leave the User* pair null; writes made in the platform UI do the opposite.
End-to-end example
# 1. Create the order together with its two lines - one atomic request
ORDER_ID=$(curl -s -X POST "$BASE_URL/orders" \
-H "companyapikey: $API_KEY" -H "Content-Type: application/json" \
-d '{
"orderNumber": "PO-2026-0042",
"buyerCompanyId": "<buyer-company-id>",
"supplierCompanyId": "<supplier-company-id>",
"lines": [
{"styleId": "<style-id-1>", "quantity": 1500, "unit": "PIECES"},
{"styleId": "<style-id-2>", "quantity": 800, "unit": "PIECES"}
]
}' | jq -r '.data.id')
# 2. Read the order's current lines (you need their ids to keep them on the next PATCH)
LINES=$(curl -s "$BASE_URL/order-lines?orderId=$ORDER_ID&limit=100" \
-H "companyapikey: $API_KEY" | jq -c '[.data[] | {id}]')
# 3. Add a third line while keeping the existing two (full-sync PATCH)
curl -s -X PATCH "$BASE_URL/orders/$ORDER_ID" \
-H "companyapikey: $API_KEY" -H "Content-Type: application/json" \
-d "{\"lines\": $(echo "$LINES" | jq -c '. + [{"styleId": "<style-id-3>", "quantity": 250, "unit": "PIECES"}]')}"
# 4. List the order's lines again
curl -s "$BASE_URL/order-lines?orderId=$ORDER_ID" -H "companyapikey: $API_KEY"
Common errors
| Status | When | How to fix |
|---|---|---|
400 |
Validation failure: missing required field, unknown field, invalid enum value, malformed date (must be YYYY-MM-DD), non-positive quantity, or string too long. |
Check the field tables above; send only documented fields with valid values. |
400 |
orderNumber already exists in your company for the same order type (code order_number_already_existing) - on create, or on PATCH when changing orderNumber or when a buyerCompanyId/supplierCompanyId change re-derives the order type into a colliding group. |
Use a different orderNumber. Deleting an order frees its number; archived orders still block reuse. |
400 |
Your company is neither the buyer nor the supplier of the order (code order_owner_not_party), or you sent both internalResponsiblePerson and internalResponsiblePersons, or only one half of the receiver pair. |
Make your company a party to the order; send responsible persons via one field only; send both receiverCompanyId and receiverFacilityProcess or neither. |
400 |
A buyerFacilityProcess / supplierFacilityProcess / receiverFacilityProcess value is not a known facility process ID (code invalid_facility_process_ids). The message lists the offending IDs. |
Send an ID from GET /api/v2/facility-processes - these fields take IDs (CUT_MAKE_TRIM), not display names ("Cut make trim (CMT)"). |
401 |
Missing or invalid credentials (code token_or_api_key_invalid). Besides companyapikey, the endpoints also accept a platform bearer token. |
Check the header name (all lowercase) and the key value. |
403 |
Authenticated, but the credential does not resolve to a company (code insufficient_rights). |
Use a company API key; a key that isn't bound to a company can't read or write orders. |
404 |
A styleId on a nested line is unknown, archived, or not owned by your company (code style_not_found). |
Create or unarchive the style first, and check it belongs to your company. |
404 |
The order, line or style does not exist, belongs to another company, or the order has been deleted. Also returned (code responsible_person_not_found) when an entry in internalResponsiblePersons (or the deprecated internalResponsiblePerson) matches no user in your company, and (code invalid_company_id) when a buyerCompanyId/supplierCompanyId/receiverCompanyId/vendorCompanyId/factory is not a company you can reach. On a PATCH lines entry, a referenced line id that isn't on the order returns 404 (code order_line_not_found). |
Verify the ID; remember deleted orders (and their lines) are permanently removed. |
409 |
orderLineNumber already exists on the same order (code order_not_unique) - when nested lines on create/PATCH carry a duplicate, or a create entry collides with an existing line. |
Use a different orderLineNumber, or omit it to let the server auto-number the line. Swapping two numbers needs two requests (see Update an order's lines). |
Two behaviours that are not errors, and bite silently:
- An unknown query parameter is ignored, so a mistyped filter returns unfiltered data with
200. Only request bodies reject unknown fields. - A filter value that matches nothing - including a misspelled
typeorstatus- returns an empty page with200rather than a400.