Skip to main content

Player External API

The Player External JavaScript IFrame API provides third-party developers the ability to deeply integrate Seenic Tours to their solutions.

Using this API provides ability to make custom filtering, use your own website components to control Seenic Tours and make it look like part of your website/application.

What you can do​

  • Send method calls from the parent window to the iframe
  • Subscribe to state and receive responses from the iframe
  • Use a simple, promise-based API from your application code

How it works​

The library establishes a messaging channel between the parent page and the iframe. All communication uses the standard browser postMessage API.

Getting Started​

Include the script​

Add the API script to your page next to the iframe. Prefer loading it after the iframe so the target is available.

<iframe
id="seenic-player"
src="https://tour.seenic.io?locationId=5772"
></iframe>
<!-- Production (minified) -->
<script src="https://cdn.seenic.io/player-external-api.min.js"></script>
<!-- Or non-minified (debug) -->
<!-- <script src="https://cdn.seenic.io/player-external-api.js"></script> -->

Or load it as an ES module:

<iframe
id="seenic-player"
src="https://tour.seenic.io?locationId=5772"
></iframe>
<script
type="module"
src="https://cdn.seenic.io/player-external-api.js"
></script>
Download the script

Prefer to self-host? Download the embeddable script and serve it from your own domain:

You can also grab both files interactively from the Sandbox page.

The iframe src accepts URL query parameters (e.g. hideStart=true, pointId=3) to control initial tour state and UI before any API calls.

Get the API instance​

Obtain the client by passing the iframe element (or its selector):

const iframe = document.getElementById("seenic-player");
const api = PlayerExternalAPI(iframe);

// Or with a selector
const api = PlayerExternalAPI("#seenic-player");

Optionally enable strict origin validation (recommended only if your player origin is fixed):

const api = PlayerExternalAPI("#seenic-player", {
targetOrigin: "https://tour.seenic.io",
});

Wait for the player​

The player may take a moment to initialize after the iframe loads. Subscribe to on_ready before calling methods:

api.subscribe("on_ready", () => {
console.log("Player is ready");
// Safe to call api.send(), etc.
});

Call a method​

Methods are sent to the iframe with a type and optional payload:

// With a response
const result = await api.send("set_point", { pointId: 123 });
console.log(result);

Subscribe to events​

Listen for changes emitted from the iframe:

api.subscribe("on_app_state", (data) => {
console.log("State received:", data);
});

// Unsubscribe when done
const unsubscribe = api.subscribe("on_app_state", handler);
unsubscribe();

API Overview​

The Player External API object is created by calling PlayerExternalAPI(iframe, options?) with the target iframe element or a CSS selector string. Options may include targetOrigin, timeoutMs, and debug.

Client methods​

MethodDescription
send(type, payload?)Sends a method call to the iframe. Returns a Promise that resolves with the response payload.
subscribe(type, callback)Subscribes to an event from the iframe. Returns an unsubscribe function.
enableAnalytics(options?)Subscribes to tour metrics and pushes each event onto window.dataLayer.
disableAnalytics()Stops the subscription started by enableAnalytics().
destroy()Tears down listeners, rejects pending requests, and unsubscribes from all events.

Wire format​

Outbound requests use kind: "PlayerExternalAPI:request" with { id, type, payload }. Subscriptions use kind: "PlayerExternalAPI:subscribe" / "PlayerExternalAPI:unsubscribe".

Inbound responses and events include a type string and optional payload. Failed requests reject with a PlayerExternalAPIError that may include data, responsePayload, and responseType from the player.

Naming​

api.send() and api.subscribe() types use snake_case (for example get_tour_data, on_marker_event, on_app_state).

Full method list​

TypeKindDescription
on_readysubscribePlayer initialized and is ready to accept commands
get_markers_listsendAll markers in the player
get_tour_datasendTour data (cycles, views, points)
get_app_statesendCurrent app state snapshot
on_marker_eventsubscribeMarker click and hover events
on_app_statesubscribeApp state stream
on_metricssubscribeTour metrics for the host page dataLayer
set_filtered_unitssendFilter which units are shown
locate_markersendLocate marker by Seenic marker ID (with blink)
set_pointsendMove camera to a tour point (optional pitch/yaw)
set_cycle_viewsendSet active cycle and view
set_locationsendSwitch location by ID
set_camera_posesendSet camera pitch and yaw in degrees
set_fovsendSet field of view (zoom) in degrees
get_screenshotsendScreenshot as base64 / data URL
set_fullscreen_modesendEnter/exit fullscreen
set_dollhouse_modesendEnable/disable dollhouse mode
show_spinnersendShow interactive floorplan spinner
hide_spinnersendHide interactive floorplan spinner
set_spinner_statesendSet spinner floor, frame, zoom

Example​

const api = PlayerExternalAPI("#seenic-player", {
targetOrigin: "https://tour.seenic.io",
});

await api.send("set_filtered_units", {
units: [{ id: "Q1B1F1U0001" }],
});
await api.send("set_point", { pointId: 3 });

// Listen for events
api.subscribe("on_ready", () => console.log("Player is ready"));
api.subscribe("on_marker_event", (event) =>
console.log("Marker event:", event.payload.eventType, event.payload.marker),
);
api.subscribe("on_app_state", (data) =>
console.log("App state:", data),
);

Methods​

Use the Sandbox page to run the player in an iframe and try send and subscriptions against the bundled player-external-api.js.

  • Methods are sent via api.send(type, payload) and return a Promise when the iframe responds. Use await api.send(...) to wait until the method has completed.
  • Events are subscribed via api.subscribe(type, callback), which returns an unsubscribe function.

Method and event type strings use snake_case (for example get_tour_data, on_marker_event).

Data & lists​

get_markers_list​

Returns all markers currently in the player as a JSON array.

Example:

const response = await api.send("get_markers_list");
console.log("Markers in player:", response.payload.markers);

Example response:

api.send() resolves with the full response envelope; the data is under payload.

{
"kind": "PlayerExternalAPI:response",
"type": "get_markers_list",
"status": "success",
"id": "1717000000000:1",
"payload": {
"locationId": 5772,
"markers": [
{
"markerId": "Q1B5S1F1U5111",
"text": "Unit 5111",
"locationId": 5772,
"type": "Unit Preview",
"unitId": "Q1B5S1F1U5111",
"actionType": "Go To Location and Point",
"actionPayload": {
"hotspotId": null,
"actionLocationId": null,
"actionPointId": 3,
"actionCycleId": null,
"actionViewId": null,
"actionEventData": null
}
}
]
}
}

get_tour_data​

Returns tour data: cycles, views, points, and related structures. Use the result to pass IDs into set_point and set_cycle_view.

Example:

const response = await api.send("get_tour_data");
const tourData = response.payload;
console.log("Cycles:", tourData?.cycles, "Start point:", tourData?.startPoint);

Example response:

payload is null when the tour config has not loaded yet.

{
"kind": "PlayerExternalAPI:response",
"type": "get_tour_data",
"status": "success",
"id": "1717000000000:2",
"payload": {
"locationId": 5772,
"name": "Aventon Soraya — Building 5",
"companyId": 12,
"gAnalyticsId": null,
"status": "active",
"startPoint": 1,
"navigationMode": "points",
"units": { "scene": "m", "ruler": "ft" },
"pointsCount": 24,
"markersCount": 8,
"explorer3DId": 42,
"floors": [
{
"id": 1,
"minimapUrl": "https://media.seenic.io/locations/5772/maps/Floor1_minimap.webp?t=1743180088777"
}
],
"cycles": [],
"ui": {
"arrowsNavigation": true,
"panoSectorsNavigation": true,
"floorPointsNavigation": true,
"dollhouseButton": true,
"measureButton": true,
"minimap": true,
"locationbar": true,
"leaseButton": { "enabled": false, "link": null }
},
"camera": { "defaultFov": 75 },
"variants": [],
"spinner": null
}
}

get_app_state​

Returns the current global app state snapshot.

Example:

const response = await api.send("get_app_state");
console.log("Current app state:", response.payload);

Example response:

{
"kind": "PlayerExternalAPI:response",
"type": "get_app_state",
"status": "success",
"id": "1717000000000:3",
"payload": {
"locationId": 5772,
"navigationMode": "points",
"viewMode": "panorama",
"floor": 1,
"current": { "pointId": 3, "cycleId": null, "cycleViewId": null },
"visibleMarkers": ["Q1B5S1F1U5111", "Q1B5S1F1U5112"],
"motion": {
"active": false,
"targetType": null,
"targetPointId": null,
"targetCycleId": null,
"targetCycleViewId": null,
"targetLocationId": null,
"progress": null
},
"camera": { "yaw": 1.5708, "pitch": 0, "fov": 75 },
"minimap": { "state": "minimized" },
"units": [
{
"seenicId": "Q1B5S1F1U5111",
"filteredOut": false,
"availabilityStatus": "available",
"floorPlan": {
"name": "A2",
"tourLink": "https://tour.seenic.io/?locationId=521",
"spinnerLink": "https://tour.seenic.io/?spinnerId=2069"
}
}
]
}
}

Events from iframe​

Subscribe with api.subscribe(type, callback). The returned function unsubscribes.

on_ready​

Fired once when the external API plugin inside the player has finished initializing. Subscribe as early as possible so you do not miss the event if the plugin is already ready when you attach.

Example:

api.subscribe("on_ready", (event) => {
console.log("Player is ready", event.payload);
});

Example event:

The subscription callback receives the full response envelope; read the data from payload.

{
"kind": "PlayerExternalAPI:response",
"type": "on_ready",
"status": "success",
"payload": { "ready": true }
}

on_marker_event​

Fired when the user clicks or hovers a marker in the player. One subscription delivers all marker interaction events, discriminated by payload.eventType:

eventTypeMeaning
clickMarker was clicked
mouseenterPointer entered the marker
mousemovePointer moved over the marker
mouseleavePointer left the marker

Payload: { eventType, marker, mouseX?, mouseY? }

  • marker is the same shape as an entry from get_markers_list: { markerId, text?, locationId, type?, unitId?, actionType?, actionPayload } (fields may vary by marker type).
  • mouseX / mouseY are viewport coordinates, present on hover events (mouseenter, mousemove, mouseleave) only.
  • mousemove events are throttled to roughly 30 per second; click, mouseenter, and mouseleave are never throttled.

Example:

api.subscribe("on_marker_event", (event) => {
const { eventType, marker, mouseX, mouseY } = event.payload;
if (eventType === "click") {
console.log("Marker clicked:", marker.markerId, marker.unitId);
} else {
console.log(`Marker ${eventType}:`, marker.markerId, mouseX, mouseY);
}
});

Example event (click):

{
"kind": "PlayerExternalAPI:response",
"type": "on_marker_event",
"status": "success",
"payload": {
"eventType": "click",
"marker": {
"markerId": "Q1B5S1F1U5111",
"text": "Unit 5111",
"locationId": 5772,
"type": "Unit Preview",
"unitId": "Q1B5S1F1U5111",
"actionType": "Go To Location and Point",
"actionPayload": {
"hotspotId": null,
"actionLocationId": null,
"actionPointId": 3,
"actionCycleId": null,
"actionViewId": null,
"actionEventData": null
}
}
}
}

Example event (hover):

{
"kind": "PlayerExternalAPI:response",
"type": "on_marker_event",
"status": "success",
"payload": {
"eventType": "mouseenter",
"marker": {
"markerId": "Q1B5S1F1U5111",
"text": "Unit 5111",
"locationId": 5772,
"type": "Unit Preview",
"unitId": "Q1B5S1F1U5111",
"actionType": "Go To Location and Point",
"actionPayload": {
"hotspotId": null,
"actionLocationId": null,
"actionPointId": 3,
"actionCycleId": null,
"actionViewId": null,
"actionEventData": null
}
},
"mouseX": 640,
"mouseY": 312
}
}

on_app_state​

Streams app state updates on every heartbeat from the player.

Example:

const unsubscribe = api.subscribe("on_app_state", (event) => {
console.log("App state update:", event.payload);
});
// Call unsubscribe() when you no longer need updates

Example event:

Each heartbeat pushes the same app-state shape returned by get_app_state, under payload.

{
"kind": "PlayerExternalAPI:response",
"type": "on_app_state",
"status": "success",
"payload": {
"locationId": 5772,
"navigationMode": "points",
"viewMode": "panorama",
"floor": 1,
"current": { "pointId": 3, "cycleId": null, "cycleViewId": null },
"visibleMarkers": ["Q1B5S1F1U5111"],
"motion": {
"active": true,
"targetType": "point",
"targetPointId": 5,
"targetCycleId": null,
"targetCycleViewId": null,
"targetLocationId": null,
"progress": 0.42
},
"camera": { "yaw": 2.1, "pitch": -0.1, "fov": 75 },
"minimap": { "state": "minimized" },
"units": []
}
}

on_metrics​

Tour metrics for the host page. Each push is already shaped for Google Tag Manager dataLayer. The payload contains only data from the tour: location, point, and outbound click. Floor plan and Explorer 3D fields are not included.

Events are sent for published tours after the visitor enters the tour. A start screen delays the first event until the visitor continues. Draft tours and the editor do not emit these events.

If the visitor is already inside the tour when you subscribe, the player sends the latest metrics.tour.load and the current metrics.tour.point.view. An outbound click is sent only when it happens.

enableAnalytics() is the usual setup. It subscribes to on_metrics and pushes each payload onto window.dataLayer:

const api = PlayerExternalAPI("#seenic-player");

api.enableAnalytics();

// later, when the page should stop receiving tour metrics
api.disableAnalytics();

Pass your own array when the page does not use window.dataLayer:

api.enableAnalytics({ dataLayer: myLayer });

Subscribe yourself when the events should go somewhere other than dataLayer:

api.subscribe("on_metrics", (event) => {
if (!event.payload) return;
console.log(event.payload.event, event.payload);
});
payload.eventFields
metrics.tour.loadlocationId, locationName
metrics.tour.point.viewthose fields, plus pointId
metrics.tour.outbound.clickthose fields, plus label and destinationUrl

A field is omitted when the tour has no value for it.

Example event:

The subscription acknowledgement has no payload. Later messages carry one event.

{
"kind": "PlayerExternalAPI:response",
"type": "on_metrics",
"status": "success",
"payload": {
"event": "metrics.tour.point.view",
"locationId": 5772,
"locationName": "A1",
"pointId": 3
}
}

Google Tag Manager can use a Custom Event trigger on metrics.tour.load, metrics.tour.point.view, or metrics.tour.outbound.click, and Data Layer Variables for locationId, locationName, pointId, label, and destinationUrl.

Filtering​

set_filtered_units​

Accepts a list of units that should be shown inside the player. Each entry is typically { id: string } where id is a Seenic unit ID.

Example:

const response = await api.send("set_filtered_units", {
units: [{ id: "Q1B5S1F1U5111" }, { id: "Q1B1S1F2U1205" }],
});
console.log("Units hidden:", response.payload.filteredOutCount);

Example response:

{
"kind": "PlayerExternalAPI:response",
"type": "set_filtered_units",
"status": "success",
"id": "1717000000000:4",
"payload": {
"applied": true,
"filteredOutCount": 37
}
}

locate_marker​

Locates a marker by Seenic marker ID with a blink highlight effect.

Payload: { markerId: string }

Example:

const { payload } = await api.send("get_markers_list");
const firstMarkerId = payload.markers?.[0]?.markerId;
if (firstMarkerId) {
const response = await api.send("locate_marker", { markerId: firstMarkerId });
console.log("Located at point:", response.payload.pointId);
}

Example response:

cycleId and viewId are only present when the location uses cycles navigation.

{
"kind": "PlayerExternalAPI:response",
"type": "locate_marker",
"status": "success",
"id": "1717000000000:5",
"payload": {
"markerId": "Q1B5S1F1U5111",
"pointId": 3
}
}

set_point​

Moves the camera/view to a tour point (ID from get_tour_data). Optionally sets camera orientation in degrees when navigating to the point.

Payload: { pointId: number, pitch?: number, yaw?: number }

  • pitch and yaw are optional; when either is provided, both must be sent together.
  • pitch is in [-90, 90]; yaw is in [0, 360).

Example:

await api.send("set_point", { pointId: 3 });

// With camera angles
const response = await api.send("set_point", { pointId: 3, pitch: 0, yaw: 90 });
console.log(response.payload);

Example response:

pitch and yaw are echoed only when they were provided in the request.

{
"kind": "PlayerExternalAPI:response",
"type": "set_point",
"status": "success",
"id": "1717000000000:6",
"payload": {
"pointId": 3,
"navigationMode": "points",
"pitch": 0,
"yaw": 90
}
}

set_cycle_view​

Sets the active cycle and view (IDs from get_tour_data).

Payload: { cycleId: number, viewId: number }

Example:

const response = await api.send("set_cycle_view", { cycleId: 1, viewId: 2 });
console.log(response.payload);

Example response:

{
"kind": "PlayerExternalAPI:response",
"type": "set_cycle_view",
"status": "success",
"id": "1717000000000:7",
"payload": {
"cycleId": 1,
"viewId": 2
}
}

set_location​

Switches to a location by ID.

Payload: { locationId: number }

Example:

const response = await api.send("set_location", { locationId: 5772 });
console.log(response.payload);

Example response:

{
"kind": "PlayerExternalAPI:response",
"type": "set_location",
"status": "success",
"id": "1717000000000:8",
"payload": {
"locationId": 5772
}
}

Camera & FOV​

set_camera_pose​

Sets the camera orientation.

Payload: { pitch: number, yaw: number }

Example:

await api.send("set_camera_pose", { pitch: 0, yaw: 45 });

Example response:

{
"kind": "PlayerExternalAPI:response",
"type": "set_camera_pose",
"status": "success",
"id": "1717000000000:9",
"payload": {
"applied": true,
"pitch": 0,
"yaw": 45
}
}

set_fov​

Sets the field of view (zoom level). Lower values zoom in; higher values zoom out.

Payload: { value: number }

Example:

const response = await api.send("set_fov", { value: 40 });
console.log("FOV set to:", response.payload.fov);

Example response:

{
"kind": "PlayerExternalAPI:response",
"type": "set_fov",
"status": "success",
"id": "1717000000000:10",
"payload": {
"fov": 40
}
}

get_screenshot​

Captures the 3D scene (without interactive UI overlays) and returns image data.

Response payload: { dataUrl: string, width: number, height: number } where dataUrl is a PNG data:image/png;base64,... URL.

Example:

const response = await api.send("get_screenshot");
const { dataUrl, width, height } = response.payload;
document.querySelector("#preview").src = dataUrl;

Example response:

{
"kind": "PlayerExternalAPI:response",
"type": "get_screenshot",
"status": "success",
"id": "1717000000000:11",
"payload": {
"dataUrl": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...",
"width": 1280,
"height": 720
}
}

Modes​

set_fullscreen_mode​

Enables or disables fullscreen mode.

Payload: { value: boolean }

Example:

const response = await api.send("set_fullscreen_mode", { value: true });
console.log("Fullscreen:", response.payload.fullscreen);
await api.send("set_fullscreen_mode", { value: false });

Example response:

{
"kind": "PlayerExternalAPI:response",
"type": "set_fullscreen_mode",
"status": "success",
"id": "1717000000000:12",
"payload": {
"fullscreen": true
}
}

set_dollhouse_mode​

Enables or disables dollhouse view.

Payload: { value: boolean }

Example:

const response = await api.send("set_dollhouse_mode", { value: true });
console.log("Dollhouse (orbit) on:", response.payload.orbit);
await api.send("set_dollhouse_mode", { value: false });

Example response:

{
"kind": "PlayerExternalAPI:response",
"type": "set_dollhouse_mode",
"status": "success",
"id": "1717000000000:13",
"payload": {
"orbit": true
}
}

Spinner​

show_spinner​

Shows the interactive floorplan spinner. No payload.

Example:

const response = await api.send("show_spinner");
console.log("Spinner visible:", response.payload.visible);

Example response:

Tours without a 360° interactive floorplan spinner reject with a SPINNER_NOT_CONFIGURED error instead.

{
"kind": "PlayerExternalAPI:response",
"type": "show_spinner",
"status": "success",
"id": "1717000000000:14",
"payload": {
"visible": true,
"configured": true
}
}

hide_spinner​

Hides the interactive floorplan spinner. No payload.

Example:

const response = await api.send("hide_spinner");
console.log("Spinner visible:", response.payload.visible);

Example response:

{
"kind": "PlayerExternalAPI:response",
"type": "hide_spinner",
"status": "success",
"id": "1717000000000:15",
"payload": {
"visible": false,
"configured": true
}
}

set_spinner_state​

Sets interactive floorplan spinner state.

Payload: { floor: number, frame: number, zoom: number }

Example:

const response = await api.send("set_spinner_state", {
floor: 2,
frame: 27,
zoom: 1.5,
});
console.log(response.payload);

Example response:

{
"kind": "PlayerExternalAPI:response",
"type": "set_spinner_state",
"status": "success",
"id": "1717000000000:16",
"payload": {
"applied": true,
"floor": 2,
"frame": 27,
"zoom": 1.5
}
}