Skip to content

Conventions

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 the X-Request-Id response header. Quote it in support requests.
  • meta.apiVersion — the REST API version (1.2).

The list endpoints use page-number pagination (not cursors):

  • page — 1-based page number (default 1).
  • pageSize — rows per page (up to 1000).

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" }
}
Terminal window
# page 1
curl -s -H "Authorization: Bearer $ASMK_TOKEN" \
"https://assemblified.com/api/v1/raw-materials?pageSize=50"
# page 2
curl -s -H "Authorization: Bearer $ASMK_TOKEN" \
"https://assemblified.com/api/v1/raw-materials?pageSize=50&page=2"

There are three list endpoints and each has its own filter vocabulary — a parameter one accepts is not accepted by another:

ListFilters
/raw-materialssearch, usageFilter, originFilter, vendorFilter (repeatable), sortBy, sortOrder
/bills-of-materialssearch, statusFilter, sortBy, sortOrder
/work-ordersq, 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:

  • vendorFilter on the raw-materials list: ?vendorFilter=Holzwerk%20GmbH&vendorFilter=Acme%20Supplies
  • locationIds on the per-location inventory reads: ?locationIds=74001236152&locationIds=74001268920
  • ids on /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.

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

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) or absolute (target level) — both or neither is 400 INVALID_INPUT.
  • absolute is 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-zero delta still returns ok: true with the applied change truncated (re-read the levels to confirm). absolute must be >= 0 either way.
  • A row may optionally repeat variantId, but it must equal the path id; a mismatch is 400 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.

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.