Embed the 3D body viewer
Add the BodyDouble 3D viewer to your website while keeping your own input fields, controls and workflow. The iframe contains the 3D canvas, loading and error feedback and, on the free plan, SeamScape attribution.
Try the live demo · Set up your embed
1. Activate your embed and add your domains
Activate BodyDouble Embed in your SeamScape account to get a public site key. Add up to 10 website addresses, such as https://shop.example.com and https://www.example.com. They are ready to use immediately, with no DNS setup required.
Add each domain and subdomain separately, including www. Use the full origin (protocol, domain and port if needed), without a path, wildcard or trailing slash. All addresses share the same plan and appearance settings.
Use the public bd_site_… key in your browser code. The viewer checks it against your allowed origins. Your existing SeamScape API bearer key is separate and must never be included in browser code.
Local development and staging
Every active site key automatically works on localhost and 127.0.0.1, on any port over HTTP or HTTPS. For example, http://localhost:8000 works with the same site key and pinned viewer URL you use in production. No extra domain entry or development key is needed.
For staging, add the exact HTTPS origin, such as https://staging.example.com, to your allowed domains. You can then use the same site key to test before publishing. Other local hostnames and LAN addresses are not automatically included. The embed must be active for both local and hosted use.
2. Mount a pinned release
Give the target element a height. Serve the host page over HTTPS (HTTP also works for local development on localhost or 127.0.0.1) and allow the embed origin in your script-src and frame-src Content Security Policy. The loader is an ES module with CORS support; it must be loaded from its published URL.
<div id="body-view" style="height: 520px"></div>
<script type="module">
import { mount } from "https://embed.bodydbl.com/1.0.2/viewer.js";
let viewer;
try {
viewer = mount({
target: '#body-view',
siteKey: "bd_site_YOUR_PUBLIC_SITE_KEY"
});
const capabilities = await viewer.ready;
viewer.on('profile', profile => {
// All supported fields, including predictions for fields absent from your UI.
// Each field includes { value, unit, source: 'input' | 'predicted' }.
console.log(profile.fields);
});
await viewer.setProfile({
schemaVersion: 1,
gender: 'female',
unitSystem: 'metric',
fields: { height: 168, chestGirth: 91, waistGirth: 72, hipGirth: 99 }
});
// Call this from your own field's focus handler:
await viewer.showMeasurement('waistGirth', { autoZoom: true });
} catch (error) {
viewer?.destroy();
document.querySelector('#body-view').textContent =
'3D preview unavailable. You can continue entering measurements.';
}
// On SPA navigation/unmount: viewer?.destroy();
</script> Use sample measurements in the demo. The viewer calculates in the browser: it does not save profiles or send measurement values to SeamScape. If your own site stores measurements, you are responsible for that data and your privacy information.
3. Drive it from your own UI
Wait for viewer.ready before enabling your controls. Read the capability catalog for the selected gender instead of hard-coding ranges or assuming every measurement has a marking. Values use centimetres, kilograms and years; the current contract accepts only unitSystem: 'metric' and schemaVersion: 1.
Call setProfile with the full input on each update. Empty fields should be null or omitted. Never send predictions back as inputs unless the user explicitly chooses them. The returned profile event identifies each value as input or predicted.
Read all measurements, including predictions
viewer.on('profile', callback) returns every supported field for the selected body model, not only the fields supplied to setProfile or shown in your UI. Showing a measurement marking is independent of receiving its value. Read profile.fields; each entry contains value, unit and source. A value can be null if unavailable. Predicted values are estimates, not measurements taken from the person. Keep that distinction when displaying or storing them.
The demo displays the live count of entered and predicted fields and lets you inspect the complete JSON. Use the returned keys and the selected model's capability catalog rather than hard-coding a field count. Subscribe before calling setProfile to receive its result; the method's promise itself returns an acknowledgement, not the measurement data.
To save measurements in your own database, send your form values and any required profile event data to your own authenticated backend. Validate the values and associate them with your own user there; keep units and the input/predicted distinction. No SeamScape API key is needed for this. The site key only authorizes the viewer. A secret API key is relevant to separate server-to-server REST calls and must never appear in browser code.
| Method | Behavior |
|---|---|
ready | Promise of the exact release’s capabilities: fields per gender, units, ranges, marking/autozoom support and poses. |
setProfile(profile) | Replace the complete input profile. Omitted fields and null values return to prediction; this is not a patch. |
showMeasurement(name, { autoZoom, durationMs }) | Show a supported marking and its associated pose; optionally move the camera. |
clearMeasurement() | Remove the marking and restore the pose from before measurement mode. |
zoomToMeasurement(name, { durationMs }) | Move to a supported camera preset without changing the marking. |
setPose(name, { durationMs }) | Choose a pose from capabilities.poses, or null for neutral. Clears measurement mode. |
resetCamera() | Frame the whole body. Does not change the profile. |
setAppearance({ preset, background, bodyColor }) | Presets: light or dark. Custom #rrggbb background and bodyColor require Pro. |
on('profile' | 'measurement' | 'error', callback) | Subscribe to an event. Returns an unsubscribe function. |
destroy() | Remove the iframe, listeners and GPU resources. Call on SPA unmount. Safe to repeat. |
Mutation methods return a promise of { status: "applied" | "superseded", revision: number }. Rapid updates are coalesced; a replaced command resolves as superseded rather than hanging. Revisions track applied profiles. Prefer a short debounce for sliders. Focus/camera commands interrupt older motion. Animation duration is 0–1500 ms; reduced-motion preferences disable camera and pose animation.
Failure handling
Catch both ready and mutation promises. Subscribe to error for asynchronous WebGL failures. Continue allowing users to fill in your own form even when 3D is unavailable.
INIT_TIMEOUT: check the site key, active status, exact allowed origin, CSP and network, then remount.VERSION_MISMATCH: the frame and loader disagree. Use the same untouched release URL.INVALID_PROFILE,UNKNOWN_FIELD,OUT_OF_RANGE: validate against capabilities and correct the input.UNSUPPORTED_MEASUREMENT,AUTOZOOM_UNAVAILABLE,UNSUPPORTED_POSE: check the capability before exposing the control.INVALID_APPEARANCE: use a built-in preset or activate Pro for custom colors.WEBGL_UNAVAILABLE,WEBGL_CONTEXT_LOST,MODEL_LOAD_FAILED,VIEWER_ERROR: show a fallback and offer remount/retry.COMMAND_TIMEOUT,TOO_MANY_REQUESTS: slow down updates and remount if the frame has stopped responding.DESTROYED: create a new instance; destroyed viewers cannot be reused.
Versioning and upgrades
Pin the full release URL, such as https://embed.bodydbl.com/1.0.2/viewer.js. There is no floating “latest” endpoint. Test a new version on your staging domain before updating the URL in production. You can roll back by restoring the previous URL.
Branding, limits and privacy
Free and pilot integrations show “Powered by SeamScape”. Pro supports custom colors and lets you remove the attribution.
Removing a registered domain blocks new viewers on that domain. Pausing a key blocks new viewers everywhere, including localhost. A viewer that is already open uses its current configuration until the page reloads.
BodyDouble Embed does not include measurement values, customer identities, page URLs or query strings in application telemetry. It does not require third-party cookies.