Sets, kits and production recipes
Product composition and stock handling describe different decisions. A native recipe defines components, variants, factors and the distribution of order fields. A published stock structure determines which physical goods fulfill that composition.
| Mode | Physical stock source | Fulfillment |
|---|---|---|
virtual_set | Physical components; no independent header stock | Allocate, produce when required, and ship each component |
physical_kit | An actually assembled or received finished stock article | Allocate and ship finished kit quantities |
production_bom | Recorded component consumption and actual finished output | Use confirmed output; a recipe or production start creates no finished stock |
Publication creates no reservation, consumption or output. Use manufacturing commands for actual assembly, disassembly, consumption, output and scrap. Warehouse manufacturing does not automatically provide the production proof required by a particular dispatch section.
An imported shop group can repeat an existing order line as a virtual head. When one unambiguous real part of that same group is already linked and its shop product, variant identity and quantity match exactly, the head stays grouping context without requiring another catalog article. This recognition adds no physical line, quantity or automatic production job. Different or missing identities, conflicting assignments and unresolved real parts still require clarification. This interpretation does not publish a stock structure or rewrite existing warehouse or production work.
#Endpoints and permissions
GET /v1/articles/{id}/stock-structure: current definition, optionally selectingvariant_idor an owned historicaldefinition_id. Requiresproducts:read.GET /v1/articles/{id}/stock-structure/options: active native recipe, current definition, variants and authorized operator, warehouse and condition references. Requiresproducts:read,inventory:read,conditions:read, and the key creator's current product and warehouse viewing permissions.GET /v1/articles/{id}/stock-structure/availability: complete virtual sets available in oneoperator_refandwarehouse_ref, optionally forvariant_id. Requires the same read scopes as options.POST /v1/stock-structures/commands: publish withstock_structures:write,products:writeand current product editing permission.POST /v1/stock-structures/commands/preview: the same checks with complete rollback; additionally requiresproducts:read.GET /v1/stock-structures/commands/{idempotencyKey}: the original receipt belonging to this API connection; requiresproducts:read.
Test keys cannot publish real definitions or calculate against actual stock. Their options reads do not expose real article or warehouse choices. Use isolated simulator articles and inventory for complete write tests.
#Author a recipe directly
An ERP or authorized AI can create the first recipe without a prior UI edit. Native recipe authoring and publication of warehouse semantics are separate, explicit commands.
GET /v1/articles/{id}/structure: complete active recipe and its current revision, plus the separately published warehouse definition and its stale flag. Requiresproducts:read.structure: nullmeans there is no active recipe for the selected article/variant.GET /v1/articles/{id}/structure/options: owned component products, native variants or compiler-variable choices. Requiresproducts:read. Selectsection=products|variants|variables; usecomponent_product_idfor component variants, optionalsearch,offsetandlimit(1–100). Follownext_offsetuntil null. The response includes accepted providers and collection bounds.POST /v1/product-structures/commands: save or archive a native recipe. Requires bothproducts:writeand the separate opt-inproduct_structures:write, plus the key creator's current product editing permission. Existing product-write keys receive no new recipe authority automatically.POST /v1/product-structures/commands/preview: the identical checks with rollback; additionally requiresproducts:read.saved: falseis no stored version or command receipt.GET /v1/product-structures/commands/{requestId}: original receipt from this API connection, with currentproducts:readauthority. A null result leaves the outcome unknown.
Use one durable request_id for the exact intended change. Saving immediately activates a changed native definition; there is no separately stored draft. Identical current content can reuse the existing version, reported as reused: true. The preview reports the actual result_revision and reused decision. The recipe definition retains the same field names and meanings as the native editor:
| Field | Meaning |
|---|---|
parentProductId, parentProductVariantId | Own article and optional variant; use explicit null for the main article |
expectedRevision | Current recipe revision; 0 for its first creation |
parts | 1–100 flat slots, each with stable slotId, owned productId, nullable productVariantId, label, positive integer quantity, and zero-based sortOrder |
routes | At most 500 personalization rules; independently, the sum of all targetSlotIds.length values is at most 500 |
Route source | provider_field with returned provider and sourceKey, or compiler_variable with actual variableId and variableKey |
| Route handling | mode: append, replace or exclude, targetSlotIds, overwriteExisting, optional compileTargetVariable |
fulfillmentMode | atomic or per_part; a fulfillment rule, not permission to invent physical shipping evidence |
max_route_targets: 500 in the options response means the total target assignments across every rule, not 500 targets per rule or 500 distinct slots. Each rule permits at most 100 unique slot IDs, all referring to the recipe's at most 100 parts. Reusing one slot in two rules consumes two target assignments. Five rules targeting 100 slots each consume 500 assignments; adding another single-target rule exceeds the total even though there are only six rules. An exclude rule submitted with targetSlotIds: [] contributes one rule and zero targets. These limits are separate from shop bundle-rule limits.
Every route target must be a slot in the submitted complete recipe. Slot identifiers remain stable across deliberate edits. These component factors are integer counts; warehouse balances elsewhere use exact decimal strings. Unknown or foreign products/variants are unavailable and cannot be introduced through a field route.
{
"request_id": "e2000000-0000-4000-8000-000000000001",
"operation": {
"kind": "save",
"definition": {
"parentProductId": "e2000000-0000-4000-8000-000000000002",
"parentProductVariantId": null,
"expectedRevision": 0,
"fulfillmentMode": "atomic",
"parts": [{
"slotId": "e2000000-0000-4000-8000-000000000003",
"productId": "e2000000-0000-4000-8000-000000000004",
"productVariantId": null,
"label": "Cup",
"quantity": 2,
"sortOrder": 0
}],
"routes": []
}
}
}Replace the synthetic article references with actual choices and create the request/slot UUIDs for your intention. Archive uses operation: {kind: "archive", product_id, structure_id, expected_revision}. Active physical work or a warehouse publication can prevent archiving. A new recipe does not rewrite frozen orders, manufacturing evidence or historical shipments. If stock_publication_stale is true, read and explicitly publish the new warehouse definition before starting work that requires the current recipe.
After an uncertain response, read the receipt with the original request_id or repeat the identical command. A new request ID is a new intention, even when identical current content happens to reuse a version. Historical profile versions may recover only a proven identical original command, never admit a new edit.
#Publish a definition
- Create and activate the native recipe with
/v1/product-structures/commands, product management or the shared product structure tools. - Read options for the article and optional variant. Retain the active
structure_version_id, everyslot_idand the current publishedrevision(0if unpublished). - Choose a mode and a condition policy for every component. Preview the command, then submit the exact reviewed request.
{
"idempotency_key": "e1000000-0000-4000-8000-000000000001",
"product_id": "e1000000-0000-4000-8000-000000000002",
"variant_id": null,
"structure_version_id": "e1000000-0000-4000-8000-000000000003",
"expected_revision": 0,
"mode": "virtual_set",
"component_conditions": [
{
"slot_id": "e1000000-0000-4000-8000-000000000004",
"policy": { "kind": "certified_released" }
}
],
"output_condition_policy": { "kind": "certified_released" },
"reason": "Ship individual components from inspected stock"
}Include every slot of the active flat recipe exactly once. Factors are positive whole-piece counts taken from that native recipe; the command cannot overwrite them with different values. Stock quantities are exact decimal strings with at most six decimal places. available_sets is a whole count represented as a string. The documented unit is piece. Independently stocked variants require an unambiguous physical SKU.
The response includes definition_id, revision, each component's actual stock article, and frozen factors and policies. A virtual_set header has stock_product_id: null. For a physical kit or production recipe, that field identifies the actual finished stock article.
#Choose condition policies deliberately
certified_released accepts explicitly released conditions and excludes unclassified legacy stock. This policy is deliberately broad: subsequent released versions can also qualify. Use selected when eligibility must remain limited to particular condition versions and operators:
{
"kind": "selected",
"condition_refs": ["e1000000-0000-4000-8000-000000000005"]
}Obtain references from the authorized options response. Each represents an immutable condition version of a warehouse operator. New versions are not automatically added to a concrete selection. Quarantined or blocked goods do not become eligible through an arbitrary supplied reference. An explicit selection can include the clearly identified legacy condition; doing so does not retrospectively certify inspection.
Component policies govern eligible input goods. output_condition_policy governs the eligibility of finished physical goods for orders. Real output may initially enter quarantine; physical receiving and release are separate decisions.
#Availability without counting shared stock twice
A set needs two pieces of one SKU in slot A and three pieces of that same SKU in slot B. With 20 free eligible pieces, four complete sets are available. Both slots share the same 20 pieces. Do not add their individual eligible_free values.
The calculation considers reservations, exclusive holds, storage location, current partner releases and condition policies. Overlapping condition selections also share physical supply. Calculation stays within one operator warehouse; goods from separate warehouses are not silently combined.
This response is a current observation, not a reservation. The same components may support different offered sets. Their individually reported availability is not a simultaneous promise; an actual allocation is required to bind goods.
#Dispatch, production and returns
Orders retain their frozen composition. Physical components have their own component_id; reservations, production evidence and package contents remain bound to those actual quantities. The commercial header quantity is not the sum of components.
A shipment may contain only some required components. Its proven complete header quantity can therefore be 0 despite real component movement. Read /v1/orders/{id}/dispatches, including components and package contents, for these cases. A later shipment can supply missing components and prove a positive complete header quantity. Earlier shipment receipts are not rewritten. The older shipment-only reader may reject such a case with shipment_components_require_dispatches.
A virtual-set return refers to an actually shipped physical component from the return-source choices. It never receives a virtual header as stock. A physical kit remains a real finished article; disassembly requires its proven original manufacturing composition, not a guess based on today's active recipe.
#Conflicts, receipts and limits
The active native recipe and expected publication revision are checked at write time. On conflict, read again and review a new deliberate decision. Existing physical goods, physical history, unfinished manufacturing or bound orders can prevent a mode change. Changing modes cannot reinterpret historical goods.
Persist the complete request and idempotency_key before sending. After an uncertain response, read the original receipt or retry the exact original request. The receipt remains bound to that API connection and its unchanged arguments. A later current definition does not replace the historical reply. A null lookup does not prove failure.
Options are bounded to 100 components, 250 variants, 100 operators, 1,000 visible condition versions and 250 warehouses. Exceeding a bound is rejected explicitly rather than silently truncated. Nested virtual sets and unresolved variants are not supported physical component sources. External shop bundles must preserve their actually imported commercial lines; a missing external header line is never invented from component totals.
When a shop supplies real commercial child lines and only a logical header without an order line, each child keeps its original order-line identifier and already-total quantity. Its stock factor is 1; the logical header creates no extra demand. A group with both a commercial header and commercial children remains ambiguous and is rejected for allocation until its source structure is clarified. A physical-kit definition cannot silently become shipping of its recipe inputs.
Machine-readable version of this page: https://www.meykt.com/developers/stockstructures.md