Conventions
Success envelope
Section titled “Success envelope”Every 200 wraps the result in data, with a small meta block:
{ "data": { /* the resource(s) */ }, "meta": { "requestId": "0f8c…", "apiVersion": "1.2" }}meta.requestId— also returned as theX-Request-Idresponse header. Quote it in support requests.meta.apiVersion— the REST API version (1.2).
Pagination
Section titled “Pagination”The list endpoints use page-number pagination (not cursors):
page— 1-based page number (default1).pageSize— rows per page (up to1000).
and returns them inside data alongside the totals:
{ "data": { "items": [ /* … */ ], "totalCount": 128, "page": 2, "pageSize": 50, "totalPages": 3, "availableVendors": ["Holzwerk GmbH", "Acme Supplies"] }, "meta": { "requestId": "…", "apiVersion": "1.2" }}# page 1curl -s -H "Authorization: Bearer $ASMK_TOKEN" \ "https://assemblified.com/api/v1/raw-materials?pageSize=50"# page 2curl -s -H "Authorization: Bearer $ASMK_TOKEN" \ "https://assemblified.com/api/v1/raw-materials?pageSize=50&page=2"Filtering & repeated query parameters
Section titled “Filtering & repeated query parameters”There are three list endpoints and each has its own filter vocabulary — a parameter one accepts is not accepted by another:
| List | Filters |
|---|---|
/raw-materials | search, usageFilter, originFilter, vendorFilter (repeatable), sortBy, sortOrder |
/bills-of-materials | search, statusFilter, sortBy, sortOrder |
/work-orders | q, status, locationId, assignedTo, sourceType, tags, sortBy, sortDir |
All three take page and pageSize. See the reference for the accepted values.
Three parameters are repeatable — pass them once per value:
vendorFilteron the raw-materials list:?vendorFilter=Holzwerk%20GmbH&vendorFilter=Acme%20SupplieslocationIdson the per-location inventory reads:?locationIds=74001236152&locationIds=74001268920idson/order-bom-breakdown, which also accepts comma-separated values:?ids=1,2&ids=3
{variantId} in a path is either a numeric Shopify variant id (e.g. 45067340286136) or a virtual-material id (VMAT-VAR-…). Full gid://shopify/… ids are not supported in paths.
Methods
Section titled “Methods”GET and PATCH are the only supported methods. Any other method returns 405 with an Allow header listing the methods valid for that path (e.g. Allow: GET, PATCH).
PATCH semantics
Section titled “PATCH semantics”There are five PATCH endpoints: two on raw materials and three on work orders. All of them take a JSON object body with Content-Type: application/json, and all of them ignore unrecognised fields (a deny-by-default allowlist) rather than rejecting the request.
The two raw-material endpoints accept virtual (VMAT-*) materials only — a Shopify-managed id returns 400 INVALID_INPUT before anything is dispatched. That restriction is about materials and does not apply to the work-order endpoints.
Field updates — PATCH /raw-materials/{variantId}
Section titled “Field updates — PATCH /raw-materials/{variantId}”The body is a flat partial object of the editable fields: productName, variantName, sku, unit, unitCost, unitPrice, inventoryQuantity, vendor, specifications, imageUrl, isNonEssentialMaterial, weightValue, weightUnitId, productType, productCategory. Only the fields you send are changed.
Inventory updates — PATCH /raw-materials/{variantId}/inventory
Section titled “Inventory updates — PATCH /raw-materials/{variantId}/inventory”The body is a batch of per-location rows:
{ "updates": [ { "locationId": "74001236152", "absolute": 120 }, { "locationId": "74001268920", "delta": -5 } ]}- Each row carries exactly one of
delta(relative adjustment) orabsolute(target level) — both or neither is400 INVALID_INPUT. absoluteis converted to a delta server-side inside the transaction, making it the retry-safe form — prefer it for stock-takes.- Negative
deltas respect the shop’s allow negative inventory setting: by default, levels clamp at zero server-side — a past-zerodeltastill returnsok: truewith the applied change truncated (re-read the levels to confirm).absolutemust be>= 0either way. - A row may optionally repeat
variantId, but it must equal the path id; a mismatch is400 INVALID_INPUT.
The response is a per-row batch envelope — check each row’s ok, don’t assume all-or-nothing:
{ "data": { "results": [ { "index": 0, "ok": true, "id": "VMAT-VAR-7f3d2c1a-9b4e-4c8d-a2f6-5e1b0d9c8a7f" }, { "index": 1, "ok": false, "error": { "code": "INVALID_INPUT", "message": "…" } } ], "okCount": 1, "failedCount": 1, "skippedCount": 0 }, "meta": { "requestId": "…", "apiVersion": "1.2" }}When every row fails with the same error (for example, the material doesn’t exist), the API collapses the batch into a single request-level error instead — e.g. one 404 NOT_FOUND — so common failures surface with a proper status code.
Work-order PATCH semantics
Section titled “Work-order PATCH semantics”The three work-order writes share two rules.
expectedUpdatedAt is required on all three. It is an optimistic-lock token: send the updatedAt you last read for that work order. If someone else changed it in the meantime, the request is refused with 409 CONFLICT instead of overwriting their edit — re-read, then re-send.
A refusal changes nothing. Each of the three is all-or-nothing; there is no partial application to unwind.
Edit the header — PATCH /work-orders/{id}
Section titled “Edit the header — PATCH /work-orders/{id}”A flat partial object. The accepted fields are expectedUpdatedAt, name, description, notes, priority, dueDate, assigneeId, productionLocationId, defaultConsumptionLocationId, sourceFlowId, sourceTodoId, sourceOrderIds, tags, plannedLaborCost and actualLaborCost. null clears a field.
assigneeId is an id from GET /assignees, not a person’s name. A referenced location, flow, task or order that does not exist returns 404 NOT_FOUND naming what was missing, and nothing is written.
Move it to another state — PATCH /work-orders/{id}/transition
Section titled “Move it to another state — PATCH /work-orders/{id}/transition”Send exactly one of to (the target state: draft, ready, in_progress, paused, quality_control, qc_approved, completed, cancelled) or direction (forward / back). Both, or neither, is 400 INVALID_INPUT. Optional: note, actorUserId, actorDisplayName.
The move itself is derived from the work order’s current state, so a client never has to know the transition vocabulary. A move that isn’t allowed is 409 CONFLICT, with the blockers named.
Start a build run — PATCH /work-orders/{id}/build-runs
Section titled “Start a build run — PATCH /work-orders/{id}/build-runs”{ "expectedUpdatedAt": "2026-09-01T09:12:44.000Z", "buildRunId": "5f2b…", "items": [ { "…": "…" } ], "mode": "start"}Accepted fields: expectedUpdatedAt, items, mode, buildRunId, note, runType, reworkItemIds, actorUserId, actorDisplayName.