Skip to content

Build and stock BOMs from your catalog via the API

Once a Claude or ChatGPT connection (or an asmk_ key) is set up under Settings ▸ LLM / API Access (see AI / Agent access), an assistant can take a shop from bare catalog to fully modelled BOMs without any copy-pasting of IDs. This guide shows the canonical flow and the snippets an assistant would run.

1. Find the variants in your Shopify catalog

Section titled “1. Find the variants in your Shopify catalog”

shopify.searchVariants accepts Shopify’s own search filter syntax — the same queries you’d type in the admin search box:

const page = await app.shopify.searchVariants({
query: 'sku:BOLT-* AND vendor:Acme',
first: 50,
});
return page.variants.map((v) => ({ id: v.variantId, sku: v.sku, title: v.displayName }));

Page through big result sets with after: page.pageInfo.endCursor. If you already know the ids, shopify.getVariants({ variantIds: [...] }) hydrates up to 100 at once.

2. Register them as raw materials — in one call

Section titled “2. Register them as raw materials — in one call”
return await app.rawMaterials.registerFromShopifyVariants({
variantIds: ['53680817373507', '53680817439043' /* …up to 100 */],
});

Each item reports created: true (newly registered) or false (already known — refreshed). Purely internal components that don’t exist in Shopify are created as virtual materials in bulk, with duplicate protection:

return await app.rawMaterials.createMany({
items: [
{ productName: 'Packaging Foam', variantName: 'Default', unit: 'pcs', inventoryQuantity: 500 },
{ productName: 'Assembly Glue', variantName: '50ml', sku: 'GLUE-50' },
],
skipExisting: true, // SKU first, (productName, variantName) fallback
dryRun: true, // preview first — flip off to commit
});

billOfMaterials.createMany takes up to 25 BOMs per call; unknown Shopify component references are auto-registered along the way, and skipExisting: true skips variants that already have a BOM:

return await app.billOfMaterials.createMany({
items: [
{
productId: '15330758263107',
variantId: '53680816652611',
productName: 'Gift Box Deluxe',
variantName: 'Default',
rawMaterialReferences: [
{ nodeId: '53680817373507', quantity: 2 },
{ nodeId: 'VMAT-VAR-…', quantity: 1 },
],
},
],
skipExisting: true,
});

Every batch method returns per-item results — check results[] / failedCount rather than assuming the whole batch landed; failed items can be resent alone.

billOfMaterials.buildable computes the same number the app’s Max Buildable column shows — live Shopify component levels, virtual levels and nested pre-assembled stock included:

const { items } = await app.billOfMaterials.buildable({ ids: ['BOM-…'] });
return items.map((i) => ({ bom: i.bomId, perLocation: i.perLocation, total: i.totals.maxBuildable }));

Real per-location levels for any mix of materials come from inventory.levels, and shopify.listLocations is the canonical way to resolve location ids and names.

When finished goods are physically assembled (or counted during a stock-take), adjust the pre-assembled quantity directly — no work order needed:

// Stock-take: SET the level (retry-safe — prefer absolute over delta for counts)
await app.billOfMaterials.adjustPreassembled({
id: 'BOM-…',
locationId: '101176672579',
absolute: 12,
});
// Or record 3 freshly built units
await app.billOfMaterials.adjustPreassembled({ id: 'BOM-…', locationId: '101176672579', delta: 3 });

adjustPreassembledMany batches up to 25 adjustments for a full stock-take, and assemblyBills.adjustPreassembled does the same for sub-assemblies. A BOM with Only sell pre-assembled enabled automatically re-anchors its Shopify availability to the new pre-assembled quantity.