Splat LabsSplat Labs Docs

Embed API (Viewer Control)

Control the embedded Splat Labs viewer from your own website β€” fly visitors to named locations, list a project's stops programmatically, and react when the camera arrives

Embed API

Build deeper interactivity between your website and an embedded Splat Labs viewer. With the Embed API, your page can fly the visitor's camera to named locations in the scene, enumerate a project's locations programmatically, and react when navigation completes β€” all from your own UI.

Typical uses: real-estate tour pages with room-by-room navigation buttons, product showrooms that sync scene position with page content, and facility documentation that deep-links into specific areas.

Enterprise feature

The Embed API is available on Enterprise plans and is enabled per account. Contact us or email support@splatlabs.ai with the exact origins of your site (for example https://example.com and https://www.example.com) and we'll allowlist them for all your projects β€” current and future.

How it works

The API is a small postMessage surface between your page and the viewer iframe β€” no SDK, no tokens, no extra HTTP calls. Without an allowlist entry the embed still displays and all built-in viewer controls work, but messages from your page are ignored.

Requirements:

  1. Link sharing enabled on the project (this is what makes the embed publicly viewable).
  2. Your site's origins allowlisted for your account (see above).

Name your locations

Locations are stops β€” named camera viewpoints authored in the viewer's Collections editor (for example Kitchen, Master Bedroom, Loading Dock). Add as many per project as you like. Your page addresses them by name or by id.

Embed the viewer

<iframe id="splat" src="https://cloud.splatlabs.ai/viewer/YOUR-PROJECT-UUID"
        allow="xr-spatial-tracking" style="width:100%;height:600px;border:0"></iframe>

All messaging is with this iframe. Always pass the explicit target origin https://cloud.splatlabs.ai when sending, and verify event.origin === 'https://cloud.splatlabs.ai' when receiving.

Commands (your page β†’ viewer)

Send with iframe.contentWindow.postMessage(message, 'https://cloud.splatlabs.ai').

Fly to a stop

{ type: 'splatViewer.gotoStop', name: 'Kitchen' }            // by name
{ type: 'splatViewer.gotoStop', id: 'stop-id' }              // by id (rename-proof)
{ type: 'splatViewer.gotoStop', name: 'Closet', collection: 'Main Floor' }  // scoped to a collection

Name matching is exact after trimming, case-insensitive, first match in collection order. No match β†’ a navigationEnded event with status: 'not_found' and no camera movement.

Every gotoStop produces exactly one navigationEnded event. Send a second one while a flight is still in progress and the first is closed out with status: 'superseded' β€” the newer command wins. You do not need to serialise commands yourself, though addressing one stop at a time still gives the smoothest result for a visitor.

List a project's stops

{ type: 'splatViewer.getStops' }

Replies with a stopsAvailable event (below).

Walk to an annotation (advanced)

{ type: 'splatViewer.navigateToAnnotation', uuid: 'annotation-uuid' }

Unlike gotoStop (a direct camera flight), this plans a walking route along the floor to an annotation marker, and returns status: 'error' on scenes without navigation data. Prefer gotoStop unless you specifically want the walking behavior.

Events (viewer β†’ your page)

Delivered to your window via postMessage from origin https://cloud.splatlabs.ai.

stopsAvailable β€” the stop list and your ready signal

{
  type: 'splatViewer.stopsAvailable',
  stops: [
    { id: 'stop-id', name: 'Kitchen', collection: 'Main Floor', collectionId: 'col-id', order: 0 },
    // ...
  ]
}

Sent once automatically as soon as the viewer can serve navigation commands (with stops: [] if the project has no collections), and again as the reply to every getStops. Commands sent before this event arrives may be dropped β€” build your navigation UI in response to it.

getStops is answered as soon as the viewer's message channel is live, which can be earlier than the automatic push. Polling it until it answers is a valid alternative readiness check if you prefer not to depend on the unsolicited event.

{ type: 'splatViewer.navigationEnded', status: 'arrived', id: 'stop-id', name: 'Kitchen' }

Emitted only for navigations your page initiated β€” visitor-driven navigation inside the viewer does not produce this event.

statusMeaning
arrivedThe camera reached the destination.
errorThe stop was found but the flight failed.
not_foundNo stop matched the id or name you sent. The camera did not move.
supersededA newer gotoStop arrived while this one was still in flight. Expect a separate event for that newer command.

Putting it together

<iframe id="splat" src="https://cloud.splatlabs.ai/viewer/YOUR-PROJECT-UUID"
        allow="xr-spatial-tracking" style="width:100%;height:600px;border:0"></iframe>
<nav id="rooms"></nav>
 
<script>
  const VIEWER_ORIGIN = 'https://cloud.splatlabs.ai';
  const splat = document.getElementById('splat');
 
  const send = (msg) => splat.contentWindow.postMessage(msg, VIEWER_ORIGIN);
 
  window.addEventListener('message', (e) => {
    if (e.origin !== VIEWER_ORIGIN) return;
    const msg = e.data;
 
    if (msg?.type === 'splatViewer.stopsAvailable') {
      // Viewer is ready β€” build room buttons from live data.
      const nav = document.getElementById('rooms');
      nav.replaceChildren(...msg.stops.map((s) => {
        const b = document.createElement('button');
        b.textContent = s.name;
        b.onclick = () => send({ type: 'splatViewer.gotoStop', id: s.id });
        return b;
      }));
    }
 
    if (msg?.type === 'splatViewer.navigationEnded' && msg.status === 'arrived') {
      // e.g. highlight the active room button
    }
  });
</script>

Good practice

  • Address by id (from stopsAvailable) when you want wiring that survives stop renames.
  • Keep stop names unique per project β€” name lookup is first-match-wins.
  • No delivery receipts. A command the viewer never receives (origin not allowlisted, viewer not ready, unknown type) produces no event at all. Once a gotoStop is received it always resolves to exactly one navigationEnded, so a missing event after a few seconds means the send itself failed β€” not that the navigation is still running.
  • Multiple viewers on one page: track which iframe each event came from via event.source.

Stability

The commands and events on this page are a supported contract: we won't rename fields or change semantics without advance notice and a deprecation window communicated to enabled accounts. Additions (new optional fields, new commands or events) may happen without notice β€” write your handlers to ignore unknown fields and types. Anything you may observe on the message channel that is not documented here is internal and unsupported.

Embed API (Viewer Control) | Splat Labs Docs | Splat Labs - Gaussian Splat Cloud