> For the complete documentation index, see [llms.txt](https://multiset.gitbook.io/multiset/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://multiset.gitbook.io/multiset/fundamentals/rest-api-docs/pano-360-virtual-tour.md).

# 360° Virtual Tour (Pano)

The Pano APIs serve navigable **360° panoramic virtual tours** built from your maps. A tour is a graph of **nodes**. Each node is a 360° capture point with a position, orientation, a panorama image, and links to its neighbours. Use these read APIs to build a "walk-through" viewer where a user steps from node to node.

All Pano endpoints are read-only and require your authentication token. They are mounted under **`/v1/pano`**.

{% hint style="info" %}
Pano data is generated for maps built from panoramic input: **360 video** captures (Insta360) and **`.e57` or MatterPak** scans with embedded panoramas. Maps without pano data return `404`. See [360° Virtual Tour](/multiset/fundamentals/maps/output-asset/360-virtual-tour.md) for the concepts behind these calls and how to preview a tour in the Developer Portal.
{% endhint %}

### The node object

Most responses return one or more **node** objects with this shape:

```json
{
    "nodeId": "000012",
    "keyframe": 12,
    "timeS": 4.5,
    "position": [1.20, 1.60, -3.40],
    "rotation": [0.0, 0.0, 0.0, 1.0],
    "rgbKey": "…/Pano/RGB/1024/000012.jpg",
    "rgbKeys": {
        "512":  "…/Pano/RGB/512/000012.jpg",
        "1024": "…/Pano/RGB/1024/000012.jpg",
        "4096": "…/Pano/RGB/4096/000012.jpg"
    },
    "maskKey": null,
    "neighbors": [ { "nodeId": "000013", "distance": 1.05 } ]
}
```

* **`position`**: node location `[x, y, z]` in the map's coordinate frame.
* **`rotation`**: orientation quaternion `[qx, qy, qz, qw]`.
* **`rgbKeys`**: the node's 360° panorama at each available resolution, keyed by equirect width. **`rgbKey`** is a single default tier for clients that don't select one. See [Panorama images](#panorama-images) below.
* **`maskKey`**: optional person-mask image key (`null` if none).
* **`neighbors`**: the nodes you can step to from here, with the distance in metres.

### Panorama images

Image fields are **storage keys, not URLs**. Exchange a key for a temporary download URL:

```
GET /v1/file?key=…/Pano/RGB/1024/000012.jpg
→ { "url": "https://…" }
```

Each panorama is published at several resolutions, listed in the tour header's `rgbSizes` (typically `[512, 1024, 4096]`). `rgbKeys` holds one key per **equirect width**, so a viewer can show a small image immediately and sharpen it in place. `rgbKey` is a single default tier (1024) if you'd rather not choose.

```js
// Small image for neighbours the user may step to next.
const preview = node.rgbKeys["512"];

// Sharpest image for the node the user is standing in.
const widths = Object.keys(node.rgbKeys).map(Number);
const full = node.rgbKeys[String(Math.max(...widths))];
```

Read the largest width out of `rgbKeys` rather than hard-coding `"4096"` — the available widths can differ between tours.

### Orienting a panorama

The images are equirectangular and **not** heading-stabilised, so a viewer must apply each node's `rotation` to place it in the world. These conventions are the same for every tour:

* **World frame**: right-handed — `+X` right, `+Y` up, `+Z` back.
* **Node frame**: OpenGL/ARKit convention; `rotation` maps node-local → world.
* **Equirect mapping**: the centre column of the image (`u = 0.5`) looks along the node's local `−Z`. Horizontally, `yaw(u) = (u − 0.5) × 360°` about `+Y`, positive toward `+X`. Vertically, `v = 0` is the zenith (`+90°`) and `v = 1` the nadir (`−90°`).

### Endpoints

#### List pano-enabled maps

**`GET /v1/pano`**: a paginated library of maps that have a virtual tour.

Query params: `page` (default `1`), `limit` (1–100, default `20`).

```json
{
    "totalCount": 8,
    "page": 1,
    "limit": 20,
    "panos": [
        {
            "mapCode": "MAP_ABC123DEF456",
            "mapId": "67e1…",
            "mapName": "Ground Floor",
            "thumbnailKey": "…/RGB/Rgb_4db3.jpg",
            "metric": true,
            "panoSize": "4096x2048",
            "rgbSizes": [512, 1024, 4096],
            "minSpacingM": 1.0,
            "nodeCount": 240,
            "updatedAt": "2026-07-20T10:15:00.000Z"
        }
    ]
}
```

{% openapi src="/files/T8MNstj6ZwhVHDx9RLt6" path="/pano" method="get" %}
[pano-api.yaml](https://3163433004-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FokTDI7QVY04Zvb1pQ8Ry%2Fuploads%2Fgit-blob-f6a24c1d33710073220b257d298a70880701c3c8%2Fpano-api.yaml?alt=media)
{% endopenapi %}

#### Tour summary

**`GET /v1/pano/{id}`**: header/summary for one map's tour (`{id}` = map id or map code). Includes `nodeCount`, `hasMasks`, the resolutions the tour ships (`rgbSizes`, `panoSize`), and navigation hints (`version`, `mapSet`) when the map belongs to a version chain or MapSet.

{% openapi src="/files/T8MNstj6ZwhVHDx9RLt6" path="/pano/{id}" method="get" %}
[pano-api.yaml](https://3163433004-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FokTDI7QVY04Zvb1pQ8Ry%2Fuploads%2Fgit-blob-f6a24c1d33710073220b257d298a70880701c3c8%2Fpano-api.yaml?alt=media)
{% endopenapi %}

#### Viewer-ready manifest

**`GET /v1/pano/{id}/manifest`**: the tour header plus **every node**, sorted by `nodeId`. Load this once to render a complete tour.

{% openapi src="/files/T8MNstj6ZwhVHDx9RLt6" path="/pano/{id}/manifest" method="get" %}
[pano-api.yaml](https://3163433004-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FokTDI7QVY04Zvb1pQ8Ry%2Fuploads%2Fgit-blob-f6a24c1d33710073220b257d298a70880701c3c8%2Fpano-api.yaml?alt=media)
{% endopenapi %}

#### Paginated nodes

**`GET /v1/pano/{id}/nodes`**: nodes in pages, for large tours. Query params: `page` (default `1`), `limit` (1–200, default `50`). Returns `{ totalCount, page, limit, nodes }`.

{% openapi src="/files/T8MNstj6ZwhVHDx9RLt6" path="/pano/{id}/nodes" method="get" %}
[pano-api.yaml](https://3163433004-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FokTDI7QVY04Zvb1pQ8Ry%2Fuploads%2Fgit-blob-f6a24c1d33710073220b257d298a70880701c3c8%2Fpano-api.yaml?alt=media)
{% endopenapi %}

#### Navigation window

**`GET /v1/pano/{id}/window`**: the local neighbourhood around a starting point, for stepping through a tour without loading it all. All query params are optional:

* **`nodeId`**: centre on a specific node.
* **`x`, `y`, `z`**: centre on the node nearest a world position (provide all three).
* **`qx`, `qy`, `qz`, `qw`**: an optional orientation quaternion (provide all four, together with a position) to prefer nodes the user is facing.
* **`depth`**: how many graph hops to include from the centre (1–4, default `2`).

You cannot pass both `nodeId` and a position. Precedence is `nodeId` → position → the tour's entry node.

```json
{
    "mapCode": "MAP_ABC123DEF456",
    "mapId": "67e1…",
    "center": "000012",
    "centerDistanceM": 0.42,   /* only when centring by position */
    "centerAngleDeg": 12.5,    /* only when a quaternion was also sent */
    "depth": 2,
    "nodeCount": 9,
    "nodes": [ /* node objects within `depth` hops of the centre */ ]
}
```

`centerDistanceM` tells you how far the chosen node sits from the position you asked for, and `centerAngleDeg` how far its heading differs from the orientation you passed. Both appear only when you centre by position (the second only when you also send a quaternion).

{% openapi src="/files/T8MNstj6ZwhVHDx9RLt6" path="/pano/{id}/window" method="get" %}
[pano-api.yaml](https://3163433004-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FokTDI7QVY04Zvb1pQ8Ry%2Fuploads%2Fgit-blob-f6a24c1d33710073220b257d298a70880701c3c8%2Fpano-api.yaml?alt=media)
{% endopenapi %}

### Tours across versions and MapSets

#### Same view across map versions

**`GET /v1/pano/map-version/{versionCode}/window`**: returns the "same viewpoint" seen across the maps in a version chain, so a viewer can switch between versions (e.g. *before* / *after* an update) at the same spot. Optional query params: `mapCodes` (comma-separated subset of the version), `anchorMapCode` (which version drives navigation), `nodeId`, `depth` (1–4, default `2`), `maxDistanceM` (how close a node must be to count as the "same view"). Positions are returned in the base map's frame.

{% openapi src="/files/T8MNstj6ZwhVHDx9RLt6" path="/pano/map-version/{versionCode}/window" method="get" %}
[pano-api.yaml](https://3163433004-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FokTDI7QVY04Zvb1pQ8Ry%2Fuploads%2Fgit-blob-f6a24c1d33710073220b257d298a70880701c3c8%2Fpano-api.yaml?alt=media)
{% endopenapi %}

#### One continuous tour across a MapSet

**`GET /v1/pano/map-set/{id}/window`**: treats the pano-enabled maps in a MapSet as a **single continuous tour**, bridging between maps where they meet. Optional query params: `mapCodes`, `mapCode` (the current node's map), `nodeId`, `depth` (1–4, default `2`), `bridgeRadiusM` (how close nodes in different maps must be to link). Each returned node is identified by `{ mapCode, nodeId }`, and neighbours that cross into another map are marked `"cross": true`.

{% openapi src="/files/T8MNstj6ZwhVHDx9RLt6" path="/pano/map-set/{id}/window" method="get" %}
[pano-api.yaml](https://3163433004-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FokTDI7QVY04Zvb1pQ8Ry%2Fuploads%2Fgit-blob-f6a24c1d33710073220b257d298a70880701c3c8%2Fpano-api.yaml?alt=media)
{% endopenapi %}

{% hint style="info" %}
For large tours, prefer the **window** endpoints and load neighbours as the user moves, rather than fetching the full manifest up front.
{% endhint %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://multiset.gitbook.io/multiset/fundamentals/rest-api-docs/pano-360-virtual-tour.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
