Craftmaster

Menu






Craftmaster Data Platform

Catalog feeds & interactive 3D for your storefront

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.

Always-on access CSV + JSON CORS-friendly Single-tag embed

Start here

What do you want to do?

Everything here needs an API key. Email [email protected] and we will issue one.

01 — Endpoints

Product data 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.

GET — Styles

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.

GET — Fabrics

Fabrics

/data/fabrics/?api_key=…

Fabric swatches with grade, content, cleaning code, rub count, program, color, repeat, and correlates.

GET — Leathers

Leathers

/data/leathers/?api_key=…

Leather swatches with color, finish, thickness, and descriptive copy.

GET — Wood Finishes

Wood Finishes

/data/finishes/?api_key=…

Wood finishes with image and canonical URL.

GET — Nails

Nails

/data/nails/?api_key=…

Nail options with image and canonical URL.

02 — 3D Embed

3D style 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.
Open the working demo
Snippet<script
  src="https://www.cmfurniture.com/static/embed.js"
  data-style="002910bd"
  data-api-key="craftmaster-demo-key"
  defer></script>

01 — Requirements

Drop-in ready

  • Modern browser with WebGL + JavaScript.
  • HTTPS page hosting the embed.
  • Any active API key — no separate activation.

02 — Styling

Self-contained

  • Injected wrapper uses .craftmaster-three-embed.
  • Responds to the parent container width.
  • Avoid fixed heights — we auto-size.

03 — Troubleshooting

Predictable failures

  • Styles without a 3D model show a fallback message.
  • 403 — key missing or inactive.
  • Email [email protected] with logs.

Live preview — 002910BD (002910bd). Any style with a published 3D model works.

03 — Sectional Builder

Sectional builder embed

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.
Open the working demo
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

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

MethodDoes
.readyPromise, 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 }.

EventFires
readyOnce, when the builder is live. Attach late and we replay it for you.
changeOnce per shopper action. Coalesced, so a single click never emits three.
errorA 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.

Show the full payload (about 100 lines)
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 is the key

  • We publish no prices. Every fabric carries its grade.
  • lines[].style_number plus each surface's grade is a priceable line.
  • cleaning, contents, rubs, repeat and railroad ride along.

02 — Positions

One-based, always

  • Run pieces are "1", "2", "3" left to right.
  • Front-attached ottomans are "F1", "F2".
  • These are the labels the builder itself prints. Never send an array index.

03 — Fabric sources

Why it wears what it wears

  • 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

GET — Builder index

Buildable styles

/data/api/builders/?api_key=…

Every live sectional builder: slug, title, group name, option family, piece count, and its embed and data URLs.

GET — Builder data

Builder catalog

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

GET — Iframe

Hosted builder

/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

JSON embed API

Skip our viewer and wire the 3D model into your own WebGL pipeline. All responses set Access-Control-Allow-Origin: *.

GET — Style data

Style JSON

/data/api/styles/<slug>/?api_key=…

Style metadata, 3D model URL, finish restrictions, and configurable option groups (fabrics, finishes, nails, correlates).

GET — Model binary

3D Model

/data/api/styles/<slug>/model/

The raw GLB/FBX model with CORS-friendly caching.

GET — Iframe

Hosted iframe

/data/embed/<slug>/?api_key=…

The iframe-friendly HTML our script injects for you, if you prefer to host it yourself.

05 — FAQ

Frequently asked questions

Quick answers to the questions we get most often. Anything missing? Email [email protected].

How do I get an API key?
Email [email protected] with your company name and the site you plan to use the feed on. Keys are typically issued the same business day. One key works for every endpoint and the 3D embed.
Are there rate limits?
We don't enforce hard rate limits today, but please cache responses on your end and avoid polling the CSV feeds more than once per hour. Each export is a full table dump. If you need real-time updates, contact us and we can discuss webhooks.
How fresh is the data?
Every response is generated live from the catalog at request time, so you're always seeing the current published state. Wagtail editors publish new fabrics, finishes, and styles throughout the week.
What format are the 3D models?
Models ship as either .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.
Can I host the 3D model on my own CDN?
You can fetch the binary from /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.
Which browsers does the embed support?
Any current evergreen browser with WebGL 2 enabled: Chrome, Edge, Firefox, and Safari (desktop and iOS 15+). The viewer degrades to a static fallback message on browsers without WebGL.
Does the embed set cookies or track users?
No. The embed makes anonymous fetch calls with credentials: "omit" and sets no first-party cookies. The iframe variant runs entirely in a sandboxed context on cmfurniture.com.
How do I report a bug or request a feature?
Email [email protected] with the endpoint, the request URL (omit your API key), the response you got, and what you expected. Screenshots help for embed issues.

Ready to integrate?

Request your API key and start syncing today.

Email [email protected] with your company name and the site you plan to use the feed on. We generate keys in the data console and send them the same day.

Request an API key

On Instagram

Fresh from our feed

Follow Us