AXTON Station · 3D Configurator
Developer integration guide · base URL __HOST__

The configurator is an embeddable 3D viewer. You drop it into a page with an <iframe> and drive it with postMessage: when the shopper changes an option, you send the new configuration and the model updates instantly — no reload. Your page owns the UI and the configuration state; the viewer just renders what it's told and reports back.

Tip: open /demo to pick options and watch the exact embed URL and postMessage call update live — copy them straight from there.
1 · Embed2 · Update3 · Events 4 · Catalog5 · Snapshot6 · Full exampleReference

1 · Embed the viewer

Embed the /embed page. It is the bare viewer (no controls) — your page supplies the controls.

<iframe id="axton"
    src="__HOST__/embed"
    style="width:100%;height:600px;border:0"
    title="Axton Station configurator"></iframe>

Set the initial configuration with URL parameters, so even a bare iframe renders the right station:

__HOST__/embed?robot=ax-r10kg&carousel=ax-fullsize-carousel
ParameterMeaning
<slot> (e.g. robot, carousel)option id selected for that slot
accessoriescomma-separated accessory option ids
themelight or dark (default dark)
turntable0 to start with auto-rotate off (the viewer also shows a visible toggle button)

The valid slot and option ids come from the catalog (section 4). They're managed in the admin and may change, so read them at runtime rather than hard-coding.

2 · Update it live

Post messages to the iframe's contentWindow. Updates are instant and never reload the frame. Omit a slot to leave it unchanged.

const viewer = document.getElementById('axton').contentWindow;

// change one or more parts
viewer.postMessage({
  type: 'setConfig',
  requestId: 1,                       // optional, echoed back on configApplied
  config: { robot: 'ax-r10kg', carousel: 'ax-fullsize-carousel', accessories: [] }
}, '*');
Message to the viewerEffect
{ type:'setConfig', config }apply a configuration (swaps only what changed)
{ type:'snapshot', requestId }request a PNG of the current view (see §5)
{ type:'frame' }re-center / re-fit the camera
{ type:'setTheme', theme }background theme — 'light' or 'dark'
{ type:'setTurntable', enabled }enable/disable idle auto-rotate

3 · Listen for events

The viewer posts messages back to the parent window. Always check source === 'axton-viewer'.

window.addEventListener('message', (e) => {
  if (e.data?.source !== 'axton-viewer') return;
  switch (e.data.type) {
    case 'ready':         /* e.data.catalog — build your dropdowns from this */ break;
    case 'loading':       /* e.data.part — a part is streaming in */ break;
    case 'configApplied': /* e.data.config, e.data.requestId — swap finished */ break;
    case 'snapshot':      /* e.data.dataUrl — a PNG data URL */ break;
    case 'error':         /* e.data.message */ break;
  }
});

4 · Build your UI from the catalog

Fetch the catalog to discover the slots, their options, the defaults, and accessories — then render your own selectors. It updates automatically as parts are added in the admin, so don't hard-code option lists.

const catalog = await fetch('__HOST__/api/catalog').then(r => r.json());
// { name, units,
//   slots: { robot:    { label, default, options: { 'ax-r10kg': { label, model }, … } },
//            carousel: { label, default, options: { … } } },
//   accessories: { label, multiple, options: { … } } }
// (slot ids and options are admin-managed and can change — never hard-code them)

for (const [slotId, slot] of Object.entries(catalog.slots)) {
  // make a <select> with slot.label and one <option> per id in slot.options
  // (slot.default is the pre-selected id)
}

You also receive this same catalog object in the ready event, so a separate fetch is optional.

5 · Capture a snapshot

Ask for a PNG of the current view — handy for attaching the configured station to a quote or order.

viewer.postMessage({ type:'snapshot', requestId: 42 }, '*');

// then, in your message listener:
if (e.data.type === 'snapshot') {
  const pngDataUrl = e.data.dataUrl;        // e.g. put in an <img> or upload it
}

The snapshot is the 3D view only — the floor grid, the viewer's own controls (the auto-rotate toggle) and branding are never in the image, and it looks identical in light or dark mode, so it's clean to drop into a quote or order.

6 · Full example

A minimal but complete page: it builds selectors from the catalog and drives the viewer.

<iframe id="axton" src="__HOST__/embed" style="width:100%;height:560px;border:0"></iframe>
<div id="controls"></div>

<script>
const viewer = document.getElementById('axton').contentWindow;
let catalog = null, reqId = 0;

function pushConfig() {
  const config = { accessories: [] };
  document.querySelectorAll('select[data-slot]').forEach(s => config[s.dataset.slot] = s.value);
  viewer.postMessage({ type:'setConfig', requestId: ++reqId, config }, '*');
}

function buildControls() {
  const c = document.getElementById('controls');
  c.innerHTML = '';
  for (const [slotId, slot] of Object.entries(catalog.slots)) {
    const sel = document.createElement('select');
    sel.dataset.slot = slotId;
    for (const [id, o] of Object.entries(slot.options)) {
      const opt = new Option(o.label, id, id === slot.default, id === slot.default);
      sel.add(opt);
    }
    sel.addEventListener('change', pushConfig);
    c.appendChild(sel);
  }
}

window.addEventListener('message', (e) => {
  if (e.data?.source !== 'axton-viewer') return;
  if (e.data.type === 'ready') { catalog = e.data.catalog; buildControls(); }
});
</script>

Reference

Endpoints

URLWhat
GET /embedthe embeddable viewer (iframe this)
GET /demostandalone demo with a live API-call preview (/ redirects here)
GET /api/catalogslots, options, defaults, accessories (JSON)
GET /healthzhealth check

You → viewer

typefields
setConfigconfig, requestId?
snapshotrequestId?
frame
setThemetheme — 'light' | 'dark'
setTurntableenabled
setLightinglighting {intensity, tint} — optional; lighting is normally set in the admin

Viewer → you

typefields
readycatalog
loadingpart
configAppliedconfig, requestId?
snapshotdataUrl
errormessage
Before it works on your site: embedding is locked to an allowlist. Send Axton the exact origin(s) you'll embed from (e.g. https://www.axtonrobotics.com) so they can be added to ALLOWED_ORIGINS. From a non-allowlisted origin the iframe will be blocked by the browser.