Styles
/data/styles/?api_key=…
Every live style, dimensions, pillow counts, photo URLs, canonical URL, and a prefilled 3D embed snippet for any style with a 3D model.
Craftmaster Data Platform
One API key unlocks every product data feed — styles, fabrics, leathers, wood finishes, nails — as CSV or JSON, plus the interactive 3D style viewer you can drop onto any page with a single script tag.
Start here
Everything here needs an API key. Email [email protected] and we will issue one.
01 — Endpoints
Append ?api_key=YOUR-KEY to any endpoint. Every feed ships as
UTF‑8 CSV by default. Add &format=json to get the
same rows as a JSON object with a results array — ideal
for pulling straight into your ecommerce stack.
Your key is saved only in this browser. Endpoint URLs become clickable once a key is entered.
/data/styles/?api_key=…
Every live style, dimensions, pillow counts, photo URLs, canonical URL, and a prefilled 3D embed snippet for any style with a 3D model.
/data/fabrics/?api_key=…
Fabric swatches with grade, content, cleaning code, rub count, program, color, repeat, and correlates.
/data/leathers/?api_key=…
Leather swatches with color, finish, thickness, and descriptive copy.
/data/finishes/?api_key=…
Wood finishes with image and canonical URL.
/data/nails/?api_key=…
Nail options with image and canonical URL.
02 — 3D Embed
Drop this script anywhere in the <body> of your product
page. We inject the viewer inline, auto-size it to the container, and let
shoppers swap fabrics, finishes, and nails in real time.
See it working before you write anything
Drive a live cross-origin viewer embed, watch the selection payload change as you swap fabrics and finishes, and copy out the tag.01 — Requirements
02 — Styling
.craftmaster-three-embed.03 — Troubleshooting
403 — key missing or inactive.Live preview — 002910BD (002910bd). Any style with a published 3D model works.
03 — Sectional Builder
A full configurator, not a viewer. The shopper assembles a run piece by piece and you read back the finished order: style numbers, quantities and the fabric on every surface.
See it working before you write anything
Drive a live cross-origin embed, watch the payload change as you build, and copy out the exact tag for the configuration you land on.Snippet<script
src="https://www.cmfurniture.com/static/embed.js"
data-style="f9-sect"
data-api-key="YOUR-KEY"
data-embed-mode="builder"
defer></script>
Five styles are buildable. Call /data/api/builders/ for the
current list rather than hard-coding slugs. Only f9-sect
carries design options (arm / back / welt).
Script attributes
| Attribute | What it does |
|---|---|
data-style |
Required. The builder slug, e.g. f9-sect. |
data-api-key |
Required. Your key. The builder embed is gated; the older single-model embed is not. |
data-embed-mode |
Required, "builder". Omit it and you get the single-model 3D viewer instead. |
data-options |
JSON object of design-option codes, e.g. '{"arm":"2","back":"1","welt":"1"}'. F9 only. |
data-fabric |
Fabric title for the body surface, e.g. "Nomad-10". Everything else inherits from it. |
data-finish |
Wood finish title, e.g. "Driftwood". Ignored on a style with no stainable leg. |
data-pieces |
JSON array, left to right. Ids or item numbers —
'["52","34","31"]' — or objects carrying power and
per-piece call-outs:
'[{"id":"52","power":true,"fabrics":{"a_pillow":"Corolla-07"}}]'.
|
data-fronts |
JSON array of front-attached ottomans by host position, 1-based: '[{"host":2}]'. |
data-surfaces |
JSON map of surface key to fabric title, for anything beyond body and finish: '{"a_pillow":"Corolla-07"}'. |
data-state |
A whole saved configuration — the ?c= token off a
share URL, or getState() re-encoded. Wins over every
attribute above.
|
data-embed-id |
Your own id for this embed, if you want a stable handle key. Generated for you otherwise. |
data-initial-height |
Starting pixel height before the frame reports its real one. Defaults to 900; we auto-size after that. |
Preselection rides in on the iframe URL, so the shopper sees the requested
configuration on the first paint rather than watching the default arm flash
to yours a second later. We re-check it once the frame reports
ready and reapply through the API if anything did not land.
window.CraftmasterEmbed
Published as soon as the tag runs. Every method returns a Promise:
writers resolve with the fresh getBuild(), and reject with a
message you can show a shopper. Always await them — a model swap is
a real network fetch. Two embeds on a page: use
window.CraftmasterEmbed.get(embedId).
| Method | Does |
|---|---|
.ready | Promise, resolves once the builder has its swatches and any preselection applied. |
getCatalog() | The menu: every piece, surface, option axis, fabric and finish. What you need to draw your own controls. |
getBuild() | The order: the payload documented below. |
getState() | The compact, round-trippable configuration. This is what you store. |
getShareUrl() | A cmfurniture.com style-page link carrying the current build. Your API key is stripped out of it. |
setOption(axis, code) | One axis: setOption('arm', '2'). Codes, not resolved digits. |
setOptions({arm, back, welt}) | Several at once. Validated as a set, then applied in order. |
setBuild({options, pieces, fronts, surfaces, state}) | A whole configuration in one call and one rebuild. fronts takes 1-based host positions. |
setState(state) | Restore exactly what getState() returned. |
addPiece(id, {end}) | Add to the 'start' or 'end' of the run. Rejected if the piece is not legal there. |
attachOttoman(hostPosition) | Snap a bumper ottoman to the front of a run piece. addPiece cannot express this. |
removePiece(position) | End pieces only, to keep the run contiguous. 'F1' removes a front ottoman. |
setPower(position, on) | Power motion on one piece. Rejected on a piece the catalog does not offer it on. |
setFabric(surfaceKey, title) | Run-wide drape. null clears the surface back to inheriting. |
setFinish(title) | The wood finish, which is run-wide. |
setPieceFabric(position, surfaceKey, title) | A call-out on one piece, for the surfaces that allow it. |
clear() | Empty the run. Option picks survive. |
undo() | One step back, same stack the on-canvas Undo uses. |
setMode('build'|'view') | Swap the build UI for a clean presentation view. |
downloadPdf() | Generate and save the spec sheet from inside the frame. Keep allow-downloads if you sandbox us. |
Events
cm.on(event, handler) and cm.off(event, handler),
plus cm.once(...). Handlers receive
{ type, build }, except error, which carries
{ type, message }.
| Event | Fires |
|---|---|
ready | Once, when the builder is live. Attach late and we replay it for you. |
change | Once per shopper action. Coalesced, so a single click never emits three. |
error | A rejected write. Also delivered as the Promise rejection on the call itself. |
Listening<script>
window.addEventListener('load', async () => {
const cm = window.CraftmasterEmbed;
await cm.ready;
cm.on('change', ({ build }) => {
// build.lines is your order. build.state is what you save.
renderOrder(build.lines);
});
await cm.setOptions({ arm: '2', back: '1', welt: '1' });
await cm.setFabric('body', 'Nomad-10');
await cm.addPiece('52');
});
</script>
The getBuild() payload
A real F9 build, trimmed to one entry per array. The shapes are exact.
getBuild(){
"schema": "craftmaster.sectional-build/1",
"api_version": 1,
"generated_at": "2026-09-09T13:36:32.514Z",
"style": {
"slug": "f9-sect",
"group_name": "F9 Sectional",
"group_number": "F9***-SECT", // the program mask
"product_number": "F9211-SECT", // the mask resolved to the current picks
"has_option_family": true,
"cushion_descriptor": ""
},
"options": [ // one row per axis, in catalog order
{ "key": "arm", "label": "Arm", "code": "2", "value": "Sock Arm", "resolves_to": "2" },
{ "key": "back", "label": "Back", "code": "1", "value": "Semi-Attached Knife Edge", "resolves_to": "1" },
{ "key": "welt", "label": "Welt", "code": "1", "value": "Welt / Piping", "resolves_to": "1" }
],
"option_summary": "Arm: Sock Arm · Back: Semi-Attached Knife Edge · Welt: Welt / Piping",
"summary": {
"piece_count": 3,
"seat_count": 3, // run pieces that are not ottomans
"power_count": 0,
"width_in": 127, // measured, not catalog
"depth_in": 101,
"is_empty": false
},
"surfaces": [ // the run-wide picks
{
"key": "body", "name": "Body", "type": "fabric", "per_piece": false,
"selection": {
"title": "Nomad-10",
"grade": "13", // ← the price-list key
"color": "Beige", "category": "Solid",
"cleaning": "W", "contents": "92% POLYESTER, 8% LINEN",
"rubs": "Contract", "repeat": "", "railroad": "Railroad",
"program": "Wall, Paula Deen, Design Studio, Designer Handle, Crypton",
"thumb_url": "…", "image_url": "…"
}
}
// body_welt, a_pillow, b_pillow, pillow_welt, wood_finish …
// "selection": null means nobody has picked that surface; it inherits.
],
"pieces": [ // one per PLACED piece, left to right
{
"position": "1", // 1-based; "F1" for a front ottoman
"attachment": "run", // or "front"
"host_position": null, // the run piece a front ottoman sits against
"piece_id": "52", "item": "52",
"type": "laf-sofa", "name": "LAF Sofa",
"style_number": "F921152", // what you order, power suffix included
"power": false, "power_available": true, "power_note": "",
"catalog_dimensions": "86 to 90Wx38Dx38H",
"dimensions": { "width_in": 89.4, "depth_in": 38.3, "height_in": 38 },
"pillows": { "a": { "count": 1, "width_in": 19, "height_in": 19 } },
"fabrics": {
"body": { "name": "Body", "source": "run", "titles": ["Nomad-10"], "grade": "13", … },
"body_welt": { "name": "Body Welt", "source": "self-welt", "titles": ["Nomad-10"], "grade": "13", … },
"a_pillow": { "name": "A Pillows", "source": "default", "titles": ["Nomad-10"], "grade": "13", … },
"pillow_welt": { "name": "Pillow Welt", "source": "self-welt", "titles": ["Nomad-10"], "grade": "13", … },
"wood_finish": { "name": "Wood Finish", "source": "run", "titles": ["Driftwood"], "type": "finish", … }
},
"spec": {
"width_in": 89, "depth_in": 38, "height_in": 38,
"seat_height_in": 20, "seat_depth_in": 24, "arm_height_in": 25,
"cushion": "Comfort Soft DPT", "leg": "Wood",
"weight_lb": 135, "cubes": 65
},
"model_url": "/media/builder/f9/models_normalized/F921152.glb?v=…",
"icon_url": "/media/builder/f9/thumbnails/52-2.png?v=…"
}
],
"lines": [ // quantity-aggregated order lines
{
"style_number": "F921152",
"item": "52",
"name": "LAF Sofa",
"quantity": 1,
"positions": ["1"], // which placements this line covers
"power": false,
"catalog_dimensions": "86 to 90Wx38Dx38H",
"fabrics": {
"body": { "name": "Body", "titles": ["Nomad-10"], "source": "run", "grade": "13", "type": "fabric" }
// …
}
}
],
"share_url": "https://www.cmfurniture.com/styles/f9-sect/?c=JTdCJTIybGF5b3V0…",
"state": { // the compact form — store THIS
"layout": [ { "id": "52" }, { "id": "34" }, { "id": "31" } ],
"surfaces": { "body": "Nomad-10", "wood_finish": "Driftwood" },
"opts": { "arm": "2", "back": "1", "welt": "1" }
}
}
01 — Pricing, on your side
grade.lines[].style_number plus each surface's grade is a priceable line.cleaning, contents, rubs, repeat and railroad ride along.02 — Positions
"1", "2", "3" left to right."F1", "F2".03 — Fabric sources
piece — a call-out on that one piece.run — the run-wide pick for that surface.self-welt — a welt cut from what it pipes.default — nothing picked, so it follows the body.Data without the 3D
/data/api/builders/?api_key=…
Every live sectional builder: slug, title, group name, option family, piece count, and its embed and data URLs.
/data/api/builders/<slug>/?api_key=…
The whole builder blob with every URL absolute: pieces, model variants, spec rows, drape surfaces, option axes and defaults, and the swatch endpoints. Render your own pickers without booting WebGL.
/data/embed/builder/<slug>/?api_key=…
The page our script frames for you. Accepts embed_id and a c state token, and posts its height to the parent.
04 — Advanced
Skip our viewer and wire the 3D model into your own WebGL pipeline. All
responses set Access-Control-Allow-Origin: *.
/data/api/styles/<slug>/?api_key=…
Style metadata, 3D model URL, finish restrictions, and configurable option groups (fabrics, finishes, nails, correlates).
/data/api/styles/<slug>/model/
The raw GLB/FBX model with CORS-friendly caching.
/data/embed/<slug>/?api_key=…
The iframe-friendly HTML our script injects for you, if you prefer to host it yourself.
05 — FAQ
Quick answers to the questions we get most often. Anything missing? Email [email protected].
.glb or .fbx, depending
on the style. The three_model.extension field on the JSON
response tells you which. The drop-in viewer handles both
transparently — only worry about this if you're rendering with
your own WebGL pipeline.
/data/api/styles/<slug>/model/
and serve it from your own infrastructure. Models update when we
re-export them, so you'll need a refresh strategy — we recommend
re-pulling weekly or on a content webhook.
fetch calls with
credentials: "omit" and sets no first-party cookies. The
iframe variant runs entirely in a sandboxed context on
cmfurniture.com.