# Welcome to MeshMap

Learn about how MeshMap is building an open 3D map of the world and a network of AR content.

<figure><img src="/files/Zs1zkJ2KMtbLWEIYj1YK" alt=""><figcaption><p>3D mapping to enable use cases in games &#x26; immersive experiences, navigation &#x26; local discovery, robotics, and planning &#x26; real estate.</p></figcaption></figure>

## Getting Started

<table data-view="cards"><thead><tr><th></th><th></th><th></th><th data-hidden data-card-cover data-type="image">Cover image</th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>Mapping</strong></td><td>Learn about best practices for capturing scans.</td><td></td><td><a href="/files/HonsBdl5koPJ6VdrJVCU">/files/HonsBdl5koPJ6VdrJVCU</a></td><td><a href="/pages/FmQsR1UagyVeQlXhhu62">/pages/FmQsR1UagyVeQlXhhu62</a></td></tr><tr><td><strong>Unity SDK</strong></td><td>Review docs for our Unity packages and samples.</td><td></td><td><a href="/files/HonsBdl5koPJ6VdrJVCU">/files/HonsBdl5koPJ6VdrJVCU</a></td><td><a href="/pages/4XiOzI9lkujU8kSSgPKa">/pages/4XiOzI9lkujU8kSSgPKa</a></td></tr><tr><td>API</td><td>Integrate our API into your app.</td><td></td><td><a href="/files/HonsBdl5koPJ6VdrJVCU">/files/HonsBdl5koPJ6VdrJVCU</a></td><td><a href="/pages/XI9jN43YLgophN7Sgo0f">/pages/XI9jN43YLgophN7Sgo0f</a></td></tr></tbody></table>

{% hint style="info" %}
**Updates**

Stay up to date on MeshMap news by following our progress at <https://twitter.com/meshmapxyz>.
{% endhint %}


# About

MeshMap is building an open 3D map of the world and a network of AR content.

<figure><img src="/files/CjmHg5tBkTwOvyCNWhep" alt=""><figcaption><p>A 3D digital map to place AR content, objects, and experiences in the real world.</p></figcaption></figure>

Our mission at MeshMap is to make **3D maps of the real world** that are open, accurate, and compatible with XR tooling across devices, platforms, and applications.

### Why Maps Matter

Extended reality (AR/VR/MR) and spatial computing rely on more than just headsets and apps. For digital content to feel truly grounded, it needs to know:

* How environments are shaped;
* Where objects and surfaces are;
* And how people can interact with them.

MeshMap provides the tools to create and share XR experiences on top of these maps.

### Who These Docs Are For

These docs are designed to support multiple roles within the MeshMap ecosystem:

* **Mappers** – Learn how to capture real-world spaces with LiDAR, photogrammetry, or Gaussian splatting, and contribute them to MeshMap.
* **XR Developers** – Use the MeshMap Unity SDK to bring mapped environments into your games, apps, and XR experiences with ease.
* **Community Contributors (coming soon)** – Future guides will cover ways to support, extend, and benefit from the MeshMap ecosystem beyond mapping and development.

### What You’ll Find Here

* **Mapping Guides** – Practical tips, workflows, and requirements for creating high-quality, fun-to-use maps.
* **Unity SDK Documentation** – Modular packages, setup instructions, and example projects for integrating MeshMap into XR apps.
* **Future Sections** – As MeshMap grows, we’ll expand these docs to include new roles, tools, and opportunities for participation.

### Getting Started

If you’re here to **create maps**, start with [Mapping](/contribute/mapping) guides.

If you’re here to **build XR apps**, start with the [Unity SDK Overview](/unity-sdk/overview).


# Mapping

Scan environments with LiDAR, photogrammetry, or Gaussian splatting to expand MeshMap's coverage of public places around the world.

## Mapping with your phone

You have several options for creating 3D scans with your phone. Apps such as [Polycam](https://learn.poly.cam/hc/en-us/sections/30298203696788-Using-the-Polycam-App-to-Create-3D-Models), [Immersal](https://developers.immersal.com/docs/mapsmapping/howtomap/mapper-2.0/), [Scaniverse](https://scaniverse.com/support), and [Pix4D](https://www.pix4d.com/product/pix4dcatch/) offer robust methods including [LiDAR scanning](/contribute/faq#what-is-lidar-scanning), [photogrammetry](/contribute/faq#what-is-photogrammetry), and [Gaussian splatting](/contribute/faq#what-is-gaussian-splatting). For MeshMap, textured meshes created with LiDAR or photogrammetry are best.

Once you've mapped and processed a scan using your chosen app, you can export it as a .gltf file.

<table data-view="cards"><thead><tr><th></th><th></th><th></th><th data-hidden data-card-cover data-type="files"></th></tr></thead><tbody><tr><td>LiDAR &#x26; 3D Scanner for iPhone &#x26; Android</td><td><a href="https://apps.apple.com/us/app/polycam-3d-scanner-lidar-360/id1532482376">iOS</a></td><td><a href="https://play.google.com/store/apps/details?id=ai.polycam&#x26;hl=en_US">Android</a></td><td><a href="/files/ULuMeKfOKn2KrNOd8TTI">/files/ULuMeKfOKn2KrNOd8TTI</a></td></tr><tr><td>Accurate scanning for navigation &#x26; visualization</td><td><a href="https://apps.apple.com/us/app/immersal-mapper/id1466607906">iOS</a></td><td><a href="https://play.google.com/store/apps/details?id=com.immersal.sdk.mapper&#x26;hl=en_US">Android</a></td><td><a href="/files/LooqgAJdZCRWBiy9xrk3">/files/LooqgAJdZCRWBiy9xrk3</a></td></tr></tbody></table>

## Mapping with professional equipment

MeshMap also supports 3D meshes created using professional 3D scanners, such as the Leica BLK2Go, Eagle, and XGRIDS. Simply convert the scan to a .gltf file using your preferred software.

## Submitting to MeshMap

Go to the [MeshMap web app](https://app.meshmap.com/scans) to import your scan and its metadata (map name, coordinates, time of day, and weather).

Meshes with a file size of <100 MB are supported. <20 MB will work best for MeshMap's runtime API.

CLI tools like [glTF Transform](https://gltf-transform.dev/) and apps like [Polycam](https://poly.cam/) support the easy compression of meshes.

## What makes a map fun for location-based XR?

Scanning game maps is its own art form.

Here are some tips and examples of how to create the most fun maps:

1. Plan ahead by charging your device to full battery and scanning on fair weather days.
2. Choose times that are not busy. Avoiding busy hours makes scanning easier and reduces the likelihood of data artefacts from people.
3. Pick pedestrian-safe locations that have cool shapes, elevation changes, and other environmental features that can make gameplay unique. Artful walls, occluding corners, and gradual slopes are easy cheat codes for fun.

<figure><img src="/files/2q7NKFoDyJiTrrIO9GkJ" alt=""><figcaption><p>A T-shape map occludes enemies and items around corners, allowing for surprises and sneak attacks.</p></figcaption></figure>

<div align="center"><figure><img src="/files/jbj5wAcQpiW8kP4PuAuL" alt=""><figcaption><p>A loop map can feel maze-like in the best ways, allowing you to drag enemies around the map or fall susceptible to a classic pincer tactic.</p></figcaption></figure></div>

<figure><img src="/files/Alh2kkvx7I5TT3vviK7n" alt=""><figcaption><p>A map with elevation changes can heighten immersion and enable terraced battlefields ("I have the high ground!").</p></figcaption></figure>


# FAQ

Frequently asked questions about mapping.

<details>

<summary>How do I contribute my scans to MeshMap?</summary>

Once you've mapped your own scans using a mobile app (such as [Polycam](https://learn.poly.cam/hc/en-us/sections/30298203696788-Using-the-Polycam-App-to-Create-3D-Models), [Immersal](https://developers.immersal.com/docs/mapsmapping/howtomap/mapper-2.0/), [Scaniverse](https://scaniverse.com/support), or [Pix4D](https://www.pix4d.com/product/pix4dcatch/)) or professional scanning equipment (such as the Leica BLK2Go) you can export them as GLTF files (for LiDAR and photogrammetry) or PLY files (for Gaussian splatting). Then, import that file and its metadata into your MeshMap account at <https://app.meshmap.com/scans>.

See [Mapping](/contribute/mapping) for best practices.

</details>

<details>

<summary>What is LiDAR scanning?</summary>

LiDAR (Light Detection And Ranging) is a method for creating accurate 3D representations of objects and environments. LiDAR-equipped devices use a laser to emit infrared light pulses which hit and reflect off of nearby objects. By calculating the time between the emission and reflection, it determines the distance to objects and is thus able to create accurate representations.

Many modern smartphones are equipped with LiDAR scanners. iPhone 12+ Pro and Pro Max models are all LiDAR capable.

</details>

<details>

<summary>What is photogrammetry?</summary>

Photogrammetry is a method of ingesting and triangulating a set of 2D images in order to construct an accurate 3D representation of an object or environment.

Any camera can be used for photogrammetry since the input is a set of regular images. Wide lenses will capture more of an area in each image, increasing the overlap between images to improve the overall accuracy of the outcome.

</details>

<details>

<summary>What is Gaussian splatting?</summary>

Gaussian splatting is a rasterization method to create photorealistic 3D reconstructions of environments from a sampling of images. Unlike LiDAR and photogrammetry, splatting does not produce a 3D mesh of the object or environment.

Any camera can be used for Gaussian splatting since the input is a set of regular images.

</details>

<details>

<summary>Should I use LiDAR, photogrammetry, or Gaussian splatting?</summary>

It depends on the scenario of data collection and the intended use case for the data.

LiDAR excels at quickly capturing an environment, but cannot capture glass, water, and dark shadows because the laser’s light pulses are not reflected back to the LiDAR sensor.

Photogrammetry is more time consuming since it requires taking hundreds of overlapping images, but it can produce more photorealistic representations with finer details.

Gaussian splatting can also quickly capture an extremely photorealistic 3D environment when viewed from certain angles but does not produce a 3D mesh.

</details>


# Overview

Public beta access in late Q3 2025.

<figure><img src="/files/Zs1zkJ2KMtbLWEIYj1YK" alt=""><figcaption><p>Create Unity 3D, VR, and AR apps for any platform with the Scans API</p></figcaption></figure>

The **MeshMap Unity SDK** is a suite of modular Unity packages designed to accelerate the creation of **location-based XR experiences**.

* [Core](/unity-sdk/core)
* [XR](/unity-sdk/xr)
* [Magic Leap 2 Support](/unity-sdk/magic-leap-2-support)
* [Building Blocks](/unity-sdk/building-blocks)

It provides ready-to-use samples, building blocks, and cross-platform integrations that let you focus on creating content instead of boilerplate code.

The goal is to provide Unity developers of all skill levels with tools that simplify and accelerate their workflow from weeks to minutes.

***

## Principles

* **Modular design** — install only the packages you need.
* **Cross-platform support** — including Magic Leap 2, Meta Quest 3, and XREAL Air 2 Ultra.
* **Rapid prototyping** — common systems like rigs, interactors, UI, events, save, audio, and utilities are pre-built.


# Getting Started

## Install Unity

1. Follow the steps to download and install [Unity Hub](https://unity.com/download), the desktop application for managing Unity projects.
2. Launch the app and go to Installs.
3. Click "Install Editor" and select **Unity 6000.0.58f2 LTS**.
   1. You may need to go to the [download archive](https://unity.com/releases/editor/whats-new/6000.0.50#notes).
4. Add the "Android Build Support" module (includes OpenJDK and Android SDK & NDK Tools).

***

## Your first Unity project

1. Select one of the [Example Projects](/unity-sdk/overview/example-projects) provided by MeshMap.
   1. These projects already have all the necessary assets, packages, settings, and permissions configured to support cross-platform XR development.
2. Clone the GitHub repository for your selected project to your computer.
3. In the top right of Unity Hub, click "Add" and select the project folder.
4. Click the project in the Hub to open the project.
   1. This may take several minutes the first time.


# Example Projects

Public access in late Q3 2025.

## Basic

### [Location-based AR Sample](https://github.com/MeshMap/LocationBasedARSample)

An AR sample project that demonstrates how to position objects around a map and localize on site with fiducial marker tracking.

<mark style="color:$info;">XR,  Building Blocks</mark>

*<mark style="color:$info;">Magic Leap 2</mark>* ✅*<mark style="color:$info;">,  Meta Quest 3</mark>* ✅*<mark style="color:$info;">,  XREAL Air 2 Ultra (coming soon)</mark>*

***

## Advanced

### [Bullseye Blasters](https://github.com/MeshMap/BullseyeBlasters)

An arcade target shooting AR sample project. Players blast targets that emerge from portals and pipes all around the play area. Transform anywhere into a carnival game.

<mark style="color:$info;">Magic Leap 2 Support,  Building Blocks</mark>

*<mark style="color:$info;">Magic Leap 2</mark>* ✅

### [Melody Blocks](https://github.com/MeshMap/MelodyBlocks)

A musical AR sample project that merges music, movement, and creativity. Place and play colorful melody blocks around your physical environment. Compose melodies that stretch across parks and plazas.

<mark style="color:$info;">XR,  Building Blocks</mark>

*<mark style="color:$info;">Magic Leap 2</mark>* ✅*<mark style="color:$info;">,  Meta Quest 3</mark>* ✅*<mark style="color:$info;">,  XREAL Air 2 Ultra (coming soon)</mark>*


# Using Our Packages

Choose the path that's right for you.

## For Beginners and Newcomers

*<mark style="color:$info;">Read-only permission</mark>*

1. Follow the [Getting Started](/unity-sdk/overview/getting-started) instructions to download the correct Unity version and MeshMap [Example Project](/unity-sdk/overview/example-projects) you want to remix.
2. Open the downloaded project via Unity Hub. The relevant packages, samples, and settings are already setup.
3. Remix it and share your creations!

***

## For New/Existing Unity Projects

*<mark style="color:$info;">Read-only permission</mark>*

1. Create or open your project via Unity Hub.
2. Add the `VContainer` scoped registry by including the information below via `Edit > Project Settings > Package Manager > Scoped Registries` or by directly editing the `Packages/manifest.json` file.

```json
"scopedRegistries": [
   {
      "name": "package.openupm.com",
      "url": "https://package.openupm.com",
      "scopes": [
         "jp.hadashikick.vcontainer"
      ]
   }
],
```

3. Import any **Package Dependencies** that are listed for each MeshMap Unity package ([Core](/unity-sdk/core#package-dependencies), [XR](/unity-sdk/xr#package-dependencies), [ML2 Support](/unity-sdk/magic-leap-2-support#package-dependencies), [Building Blocks](/unity-sdk/building-blocks#package-dependencies)) you plan to use in your project.
4. Go to `Window > Package Manager > Add Package from git URL...` and paste the git URL, then click Add.
   1. For example, `https://github.com/MeshMap/com.meshmap.sdk.xr.git`
   2. If you want to import a specific version, include the release number. For example, `https://github.com/MeshMap/com.meshmap.sdk.xr.git#v0.0.1`
   3. If you are using Mac, use this path format instead `git@github.com:MeshMap/com.meshmap.sdk.xr.git#v0.0.1`
5. Accelerate your project development with real-world scans, cross-platform tooling, and various building blocks!

***

## For Contributors

*<mark style="color:$info;">Read and write permissions</mark>*

1. Follow steps 1 through 3 above.
2. Clone the package's Git repository ([Core](https://github.com/MeshMap/com.meshmap.sdk.core), [XR](https://github.com/MeshMap/com.meshmap.sdk.xr), [ML2 Support](https://github.com/MeshMap/com.meshmap.sdk.bb), [Building Blocks](https://github.com/MeshMap/com.meshmap.sdk.bb)) to your computer and create a new local branch.
3. In the `Packages/manifest.json` file, add a dependency reference to the cloned repository.

```json
{
   "dependencies": {
      "com.meshmap.sdk.xr": "file:/path/to/com.meshmap.sdk.xr/"
   }
}
```

{% hint style="info" %}
By adding the package this way, Unity will treat it as editable. You can reference the package this way across all your projects and the edits will be synced, reducing the chance of code conflicts.
{% endhint %}

4. Suggest new features, patch bugs, and support the open-source XR community!

***

## Basic Usage

After import, the files will be available in the `Packages` section of the `Project` window in Unity Editor.

{% hint style="warning" %}
The files will not work outside the `Packages` section due to dependencies and assembly definitions that are specific to the Editor and Runtime, so be sure to import them correctly using one of the processes described above.
{% endhint %}

### Editor

Any custom editor windows and tools can be accessed via the `MeshMap` section of the toolbar at the top of the Unity Editor.

### Samples

Import any samples via `Window > Package Manager > package-name > Samples`.

{% hint style="warning" %}
For contributors, there are a few steps to ensure that changes to Samples are synced properly and asset GUIDs are not mismatched or overridden. &#x20;

1\) Close the project.

2\) Rename the **package's** `Samples~` folder to `Samples`.

3\) Move any of this package's already imported samples from your **project's** `Assets` folder to an external folder. If you delete, the .meta files will all get deleted and references may break.

4\) Open the project.

5\) Make your changes in the package's `Samples` folder.

6\) Close the project.

7\) Rename the package's folder to `Samples~` and delete `Samples.meta` if it was created.

8\) Move the old imported samples back into the project's `Assets` folder.

9\) Re-open the project.

10\) Go to Package Manager and reimport the samples. This will preserve the .meta files.

11\) Commit changes in a branch and submit a pull request.
{% endhint %}


# License

## Apache 2.0

Similar to many other open-source projects, the use, reproduction, and distribution of the MeshMap Unity SDK is governed by the [Apache 2.0 license](https://www.apache.org/licenses/LICENSE-2.0).

You can find a copy of this license in the LICENSE.md file included at the root level of each of our packages and sample projects. There is also a NOTICE file.

## Third-Party Licenses

Some of our packages and sample projects rely on third-party software dependencies.

You can find a list of third-party dependencies in the THIRD\_PARTY\_LICENSES.md file included at the root level of our packages and sample projects. The full text of each license and any provided NOTICE files are included in the /licenses/ folder.


# Core

com.meshmap.sdk.core

`v0.4.0-pre.7`   `30 Oct 2025`

The **MeshMap Core** package contains foundational systems, services, and data models for the MeshMap API, including account authentication and scan retrieval.

{% hint style="warning" %}
We do not yet recommend shipping this package in production or commercial projects. APIs may change without notice, and breaking changes are expected.
{% endhint %}

[**Changelog**](https://github.com/MeshMap/com.meshmap.sdk.core/blob/main/CHANGELOG.md)

***

## API Key

Create a [MeshMap account](https://app.meshmap.com/) and configure your [app API key](https://app.meshmap.com/apps) in the MeshMap Hub editor window (`MeshMap > MeshMap Hub`) to use the Auth, Scans, and Apps features.

***

## Features

* [**Auth**](/unity-sdk/core/auth) — Login and token handling for MeshMap services.
* [**Scans**](/unity-sdk/core/scans) — Retrieving and loading MeshMap scans.
* [**Apps**](/unity-sdk/core/apps) — Tracking play sessions and scores across apps.

***

### Samples

To import a sample into your Unity project, go to `Windows > Package Manager > MeshMap Unity SDK > Samples`.

* **Auth and Scan Import** — Demonstrates how to set up a simple runtime user flow for account login/logout, persistent authentication, loading paginated lists of user scans and public scans, and importing one scan into the scene at a time. Optionally, you can generate NavMesh data for the scan to use it as a `NavMeshSurface` for agents.

***

### Tools

* [Flat Surface Identifier](/unity-sdk/core/tools/flat-surface-identifier) — Editor tool for visualizing potential areas to place fiducial markers on site.
* [AR Marker Generator](/unity-sdk/core/tools/ar-marker-generator) — Editor tool for creating custom fiducial markers (AprilTag, ArUco marker, QR code) that can be used to localize AR content.

To use a tool in your Unity project, go to `MeshMap > Tools > [tool name]`.

***

## Getting Started

### Requirements

* **Unity 2022.3.11 LTS** or later
  * Universal Render Pipeline (URP)

***

### Package Dependencies

* VContainer v1.16.8 (scoped registry)
* ZXing.NET v0.16.10 (already included)
* Unity Newtonsoft JSON v3.2.1 (already included)
* Unity AI Navigation v1.1.5
* Unity gltFast v6.8.0
* Unity TextMeshPro v3.0.7
  * Make sure to [Import TMP Essential Resources](https://docs.unity3d.com/Packages/com.unity.textmeshpro@4.0/manual/index.html).

***

### Import

The [com.meshmap.sdk.core](https://github.com/MeshMap/com.meshmap.sdk.core) package can be added via Unity Package Manager (UPM) from Git. Follow the import [instructions](/unity-sdk/overview/using-our-packages).


# Auth

The **Auth** system provides account authentication and token management for accessing MeshMap services.

{% content-ref url="/pages/F6k0cXXhMLih48Lub3Wu" %}
[Runtime](/unity-sdk/core/auth/runtime)
{% endcontent-ref %}

{% content-ref url="/pages/BxZx4ic1UYzfhywzPBdM" %}
[Editor](/unity-sdk/core/auth/editor)
{% endcontent-ref %}


# Runtime

## Method Calls

* Auth.HandleAuthProcessAsync
* Auth.CancelAuthProcess
* Auth.CheckIfConnectedToMeshMapAccount
* Auth.DisconnectFromMeshMapAccount

## Events

* Auth.OnCodeGenerated
* Auth.OnCodeValidated
* Auth.OnAuthenticated
* Auth.OnAuthenticationCancelled
* Auth.OnAuthenticationFailed
* Auth.OnDisconnected

## Getting Started

The easiest way to get started is to import the **Auto and Scan Import** sample into your Unity project. Go to `Windows > Package Manager > MeshMap Unity SDK > Samples`.

Make sure to [configure your app API key in the MeshMap Hub](/unity-sdk/core#api-key).

#### Features

* A simple user flow for MeshMap account login and logout.
  * Generates a 6-digit verification code to enter in the [MeshMap web app](https://app.meshmap.com/play). The player may be prompted to login or create a MeshMap account.
* Persistent authentication status using PlayerPrefs. Deleted on log out.

#### Components

<table><thead><tr><th width="226.00006103515625">Class</th><th>Description</th></tr></thead><tbody><tr><td><a href="https://github.com/MeshMap/com.meshmap.sdk.core/blob/main/Runtime/API/Auth/Auth.cs"><code>Auth</code></a></td><td>Handles user authentication flow, including code generation, validation polling, cancellation, and disconnecting a user session.</td></tr><tr><td><a href="https://github.com/MeshMap/com.meshmap.sdk.core/blob/main/Samples~/AuthAndScanImport/Scripts/Auth/LoginUI.cs"><code>LoginUI</code></a></td><td>Demonstrates the authentication UI logic for logging into a MeshMap account, including auth code generation, validation, cancellation, and feedback display.</td></tr><tr><td><a href="https://github.com/MeshMap/com.meshmap.sdk.core/blob/main/Samples~/AuthAndScanImport/Scripts/Auth/LogOutUI.cs"><code>LogoutUI</code></a></td><td>Demonstrates the logout process, including PlayerPrefs deletion, UI transitions, and event signaling.</td></tr></tbody></table>


# Editor

Use the **MeshMap Hub** editor window to configure your app API key and connect to your MeshMap account.

{% hint style="warning" %}
If you are using version control, add "user.keystore" and "Assets/Resources/ApiKeyConfig.asset" to your .gitignore file.
{% endhint %}

## Usage

1. Open the editor window via `MeshMap > MeshMap Hub`.
2. Paste your [app API key](https://app.meshmap.com/apps) into the field.

<figure><img src="/files/cIz1kH4egqInfQSWZJA7" alt="" width="410"><figcaption><p>MeshMap Hub editor window, Setup tab.</p></figcaption></figure>

3. Click "Connect with Code" to generate a 6-digit verification code.

<figure><img src="/files/EYVyF3N996voYpWmBKbu" alt="" width="411"><figcaption><p>MeshMap Hub editor window, Setup tab with verification code and status.</p></figcaption></figure>

4. Enter your code into the [window](https://app.meshmap.com/connect) that pops up and click "Submit".
5. You may be prompted to login or create a MeshMap account.

<figure><img src="/files/n87bvAUIjTE3VSN7Kdtx" alt="" width="375"><figcaption><p><a href="https://app.meshmap.com/play">https://app.meshmap.com/play</a></p></figcaption></figure>

6. Return to Unity, where you should now be connected to your MeshMap account.

<figure><img src="/files/b6fESnDYdKjCBn5b5XSP" alt="" width="416"><figcaption><p>MeshMap Hub editor window, Setup tab with account connected.</p></figcaption></figure>

7. If you are using version control, add "user.keystore" and "Assets/Resources/ApiKeyConfig.asset" to your .gitignore file.


# Scans

The **Scans** system handles data models, serialization, and retrieval service for MeshMap scan files.

{% content-ref url="/pages/fQHA91b9JLUxjSfL6zji" %}
[Runtime](/unity-sdk/core/scans/runtime)
{% endcontent-ref %}

{% content-ref url="/pages/WyZTCyiPTDjepiP6nNGW" %}
[Editor](/unity-sdk/core/scans/editor)
{% endcontent-ref %}


# Runtime

## Method Calls

* ScansInfoLoader.FetchAndWrapUserScansAsync
* ScansInfoLoader.FetchAndWrapPublicScansAsync
* ScanLoader.InstantiateScanAsync

## Events

* ScansInfoLoader.OnInfoLoaded  (user's scans)
* ScansInfoLoader.OnPublicInfoLoaded  (all public scans)
* ScansInfoLoader.OnInfoFailed
* ScanLoader.OnScanLoaded
* ScanLoader.OnScanFailed
* ScanLoader.OnScanDestroyed

## Getting Started

The easiest way to get started is to import the **Auto and Scan Import** sample into your Unity project. Go to `Windows > Package Manager > MeshMap Unity SDK > Samples`.

Make sure to [configure your app API key in the MeshMap Hub](/unity-sdk/core#api-key).

#### Features

* A simple user flow for loading user scans and all public scans.
  * The player needs to be connected to their MeshMap account (see [Auth - Runtime](/unity-sdk/core/auth/runtime)).
* Import any scan with a supported file type (.gltf/.glb) and load it to the active scene.
* Optionally, add a `NavMeshModifier` to the scan model and rebuild the `NavMeshSurface` in the scene.
* Destroy the previous scan from the scene when importing a new scan.

#### Components

<table><thead><tr><th width="226.00006103515625">Class</th><th>Description</th></tr></thead><tbody><tr><td><a href="https://github.com/MeshMap/com.meshmap.sdk.core/blob/main/Runtime/API/Scans/ScansInfoLoader.cs"><code>ScansInfoLoader</code></a></td><td>Handles loading scan information asynchronously from the MeshMap Scans API.</td></tr><tr><td><a href="https://github.com/MeshMap/com.meshmap.sdk.core/blob/main/Samples~/AuthAndScanImport/Scripts/Scans/ScanListingUI.cs"><code>ScanListingUI</code></a></td><td>Displays metadata for a single scan entry in the UI, including its name, location, and user information.</td></tr><tr><td><a href="https://github.com/MeshMap/com.meshmap.sdk.core/blob/main/Samples~/AuthAndScanImport/Scripts/Scans/ScanLoader.cs"><code>ScanLoader</code></a></td><td>Demonstrates loading, instantiation, and cleanup of scan models in the active scene.</td></tr><tr><td><a href="https://github.com/MeshMap/com.meshmap.sdk.core/blob/main/Samples~/AuthAndScanImport/Scripts/Scans/ScansUI.cs"><code>ScansUI</code></a></td><td>Demonstrates UI logic for listing, paginating, filtering, and loading scans from the MeshMap API. Provides controls to request user or public scans, validate pagination input, and display scan listings dynamically.</td></tr><tr><td><a href="https://github.com/MeshMap/com.meshmap.sdk.core/blob/main/Samples~/AuthAndScanImport/Scripts/Scans/NavMesh/BuildNavMeshAtRuntime.cs"><code>BuildNavMeshAtRuntime</code></a></td><td>Automatically builds or clears <code>NavMesh</code> data at runtime when a scan is loaded or destroyed.</td></tr></tbody></table>


# Editor

Use the **MeshMap Scan Importer** editor window to view, import, and load scans into Unity.

Having access to highly detailed scans is helpful for knowing where to place `GameObject`s when designing location-based XR experiences.

{% hint style="success" %}
Currently, only .glb/.gltf files are supported.
{% endhint %}

## Usage

1. Make sure to [configure your app API key in the MeshMap Hub](/unity-sdk/core#api-key) and [connect to your MeshMap account](/unity-sdk/core/auth/editor).
2. Open the editor window via `MeshMap > Scan Importer`.
3. Load the scans data.
   1. Select "Get User Scans" to load public and private scans from your MeshMap account.
   2. Select "Get Public Scans" to load all public scans.
4. Select "Import" next to any supported filetypes.

<figure><img src="/files/sm25yWxqJ0GiKYz0w8he" alt=""><figcaption><p>Scan Importer editor window.</p></figcaption></figure>

4. Scans are imported to `Assets/Resources/MeshMap/Scans/`.

<figure><img src="/files/NTZCfBx3kkbCmuxcUFGI" alt=""><figcaption><p>Scan Downloaded popup window.</p></figcaption></figure>

5. Select "Add to Scene" to automatically load the scan `GameObject` into the active scene.

<figure><img src="/files/5HZMyy33zF88NPxih6OW" alt=""><figcaption><p>Example scan of Washington Square Park loaded in the scene.</p></figcaption></figure>

6. You can now use the scan as a reference to design location-based XR experiences. See [Mapping](/contribute/mapping) for tips on how to capture your own realistic scans.


# Apps

The **Sessions** system provides session and score tracking services for MeshMap apps.

## Method Calls

* AppsService.GetAllMinigameTypesAsync
* AppsService.CreateSessionAsync
* AppsService.EndSessionAsync
* AppsService.CreatePlayerAsync
* AppsService.RegisterPlayerAsync
* AppsService.PatchScoreAsync
* AppsService.GetPlayersListAsync
* AppsService.GetLeaderboardsAsync

## Getting Started

Make sure to [configure your app API key in the MeshMap Hub](/unity-sdk/core#api-key).

#### Features

* Methods for creating sessions and players, registering players, updating minigame scores, and retrieving scores.

#### Components

<table><thead><tr><th width="226.00006103515625">Class</th><th>Description</th></tr></thead><tbody><tr><td><a href="https://github.com/MeshMap/com.meshmap.sdk.core/blob/main/Runtime/API/Apps/AppsService.cs"><code>AppsService</code></a></td><td>Provides methods to set and retrieve app, session, player, minigame, and leaderboard data.</td></tr></tbody></table>


# Tools

{% content-ref url="/pages/LzM1H6tksP6jHo7zS7j2" %}
[Flat Surface Identifier](/unity-sdk/core/tools/flat-surface-identifier)
{% endcontent-ref %}

{% content-ref url="/pages/qNdImlqGjxzawI5bIuBR" %}
[AR Marker Generator](/unity-sdk/core/tools/ar-marker-generator)
{% endcontent-ref %}


# Flat Surface Identifier

Use the **Flat Surface Identifier** editor tool to highlight potential areas to safely place fiducial markers for on-site AR content localization.

Ideally, markers should be placed on level surfaces so that the pose data remains accurate for faraway content.

Note: This tool assumes that the scan is accurate to reality.

## Usage

1. [Import a scan](/unity-sdk/core/scans/editor) into your scene.

<figure><img src="/files/pSlfQKZcaXF979gMEna7" alt=""><figcaption><p>Example scan of the MoMA lobby loaded into the scene.</p></figcaption></figure>

2. Open the editor window via `MeshMap > Tools > Flat Surface Identifier`.
3. Drag and drop the `MeshRenderer` of your scan `GameObject` into the tool's "Mesh Renderer" field.
4. "Use Shared Material" is true by default. Set to false if you have multiple instances of this `Material` in your project and want to avoid potential global side-effects.
5. Click "Calculate".
6. Flat surfaces are highlighted in green.
7. The material's shader will be reset when you close the window.

<figure><img src="/files/fy60s4FzwK8q9mrsY03o" alt=""><figcaption><p>Example of the Flat Surface Identifier tool highlighting the flat surfaces of the MoMA lobby in green.</p></figcaption></figure>


# AR Marker Generator

Use the **AR Marker Generator** editor tool to create custom fiducial markers (AprilTag, ArUco marker, QR code) that can be used to localize AR content.

Note: AprilTags are the best for accurate pose detection.

{% hint style="info" %}
To use the markers, print them out at \~17cm size on white paper. Keep at least a \~2cm margin to improve readability.

Outside of Unity, [aurcogen](https://chev.me/arucogen/) is a great resource for creating AprilTags and ArUco markers.
{% endhint %}

## Usage

1. Open the editor window via `MeshMap > Tools > AR Marker Generator`.
2. Input the "Marker ID" that you want to generate.
   1. AprilTags and ArUco markers only support numbers. This makes them more efficient to read, especially for pose detection.
   2. QR code are able to encode complex strings. If desired, you can structure the Marker ID as a URL with a query parameter. Unity apps using [Marker Tracking](/unity-sdk/xr/marker-tracking) will extract the parameter to use for localization, while regular QR code readers (like smartphone cameras) will direct to the URL. For example, <https://www.meshmap.com/experiences?marker=0>.
3. Optionally, input the "Filename" you want to save the file as.
4. Select the desired "Marker Type".
   1. (See Step 2 for guidance on which type to use.)
5. Select the pixel "Size" of your image.
6. Click "Generate".
7. The markers are saved to `Assets/Markers/`.

<div><figure><img src="/files/BgqA5g0DW3UT5a1SNEtI" alt=""><figcaption><p>AprilTag</p></figcaption></figure> <figure><img src="/files/qeq3jNAdW3N7Sw2yryL2" alt=""><figcaption><p>ArUco marker</p></figcaption></figure> <figure><img src="/files/U9ymDnqqUOuOfM5Zhgdt" alt=""><figcaption><p>QR code</p></figcaption></figure></div>


# XR

com.meshmap.sdk.xr

`v0.1.1-exp.7`   `19 Nov 2025`

The **MeshMap XR** package is *experimental* and contains cross-platform device SDK management systems, player rigs, interactables, interactors, UI, and marker tracking modules.

Some developers will find that building for one target XR device is sufficient, particularly when designing a unique location-based experience (LBE). This package is most useful when designing LBEs that reuse assets across several locations and target devices.

{% hint style="warning" %}
Due to its experimental nature, we do not yet recommend shipping this package in production or commercial projects. APIs may change without notice, and breaking changes are expected. Use is primarily intended for evaluation and prototypes.
{% endhint %}

{% hint style="info" %}
This package depends on a minimally modified version of the Magic Leap SDK to prevent compilation errors when building for multiple target XR devices with Unity OpenXR Plugin 1.15.0. In accordance with the [Magic Leap 2 Software License Agreement](https://www.magicleap.com/legal/software-license-agreement-ml2), the package is included when necessary in sample projects, but not redistributed solely on its own.

This package also depends on a minimally modified version of jp.keijiro.apriltag which includes the 36h11 tag family. It is licensed under the BSD 2-Clause License.
{% endhint %}

[**Changelog**](https://github.com/MeshMap/com.meshmap.sdk.xr/blob/main/CHANGELOG.md)

***

## Features

In addition to the samples below:

* **Cross-platform Management** — Editor tooling that automatically manages package wrapping, build profiles, and Android manifests for the supported target HMD devices and mobile spectator devices.
* **Rigs** — Generic XR rig assets.
* **Interactables** — Simple `XRBaseInteractable`-based classes and related components.
* **Interactors** — Simple `XRBaseInteractor`-based classes and related components.
* **UI** — Generic menu assets.
* **Marker Tracking** — Assets for supporting cross-platform fiducial marker tracking.

***

### Samples

To import a sample into your Unity project, go to `Windows > Package Manager > MeshMap XR > Samples`.

* **Marker Tracking** — Demonstrates how to set up a simple scene with marker tracking to localize content for location-based AR experiences.

***

## Getting Started

The easiest way to get started is to clone the [**MeshMap Location-based AR Sample** repository](https://github.com/MeshMap/LocationBasedARSample) and open the project using [Unity Hub](https://unity.com/unity-hub).

***

### Requirements

* Windows, Mac
* **Unity 6000.0.58f2 LTS**
  * Android Build Support
  * iOS Build Support
  * Universal Render Pipeline (URP)

{% hint style="info" %}
Other Unity versions *may* work but they have **not** been tested. We strongly recommend using the version listed above.
{% endhint %}

***

### Package Dependencies

* VContainer v1.16.8 (scoped registry)
* XR Plugin Management v4.5.1
* XR Interaction Toolkit v3.0.8
  * Starter Assets and Hand Interactions Demo samples.
* AR Foundation v6.2.0
* Google ARCore XR Plugin v6.0.6
* Apple ARKit XR Plugin v6.0.6
* XR Hands v1.5.1
  * Hand Visualizer sample.
* OpenXR Plugin v1.15.0
* XR Composition Layers v2.0.0
* Unity Meta OpenXR v2.2.0
* Unity Meta XR v74.0.0
* Unity Meta MR Utility Kit v74.0.0
* Magic Leap Unity SDK v2.6.0-pre.R15-meshmap
* XREAL Unity SDK v3.0.0
* [Keijiro AprilTag v1.0.2-meshmap.2](https://github.com/MeshMap/jp.keijiro.apriltag.meshmap)

***

### Import

The [com.meshmap.sdk.xr](https://github.com/MeshMap/com.meshmap.sdk.xr) package can be added via Unity Package Manager (UPM) from Git. Follow the import [instructions](/unity-sdk/overview/using-our-packages).

***

### Basic Usage

After import, the files will be available in the Packages section of the Project window in Unity Editor.

***

## Settings, Permissions, and Installation

Review these settings configuration guides with reference images for each target device:

* [Magic Leap 2](/unity-sdk/xr/cross-platform-management/magic-leap-2)
* [Meta Quest 3](/unity-sdk/xr/cross-platform-management/meta-quest-3)
* [XREAL Air 2 Ultra](/unity-sdk/xr/cross-platform-management/xreal-air-2-ultra)


# Cross-platform Management

{% hint style="warning" %}

## This package uses a combination of build profiles, wrapping dependency packages in scripting defines, and custom AndroidManifests and Gradle templates to support multiple XR SDKs in the same project. This is a recent capability as of Unity 6.

We highly recommend starting with an [Example Project](/unity-sdk/overview/example-projects) and carefully following the instructions for each device ([Magic Leap 2](/unity-sdk/xr/cross-platform-management/magic-leap-2), [Meta Quest 3](/unity-sdk/xr/cross-platform-management/meta-quest-3), and [XREAL Air 2 Ultra](/unity-sdk/xr/cross-platform-management/xreal-air-2-ultra)) to ensure your app works properly.
{% endhint %}

## Getting Started

Supporting multiple XR SDKs in the same Unity project requires handling many settings and permissions. The best way to get started is to use one of our [Example Projects](/unity-sdk/overview/example-projects), which have these settings preconfigured.

## Tools

<table><thead><tr><th width="359.800048828125">Class</th><th>Description</th></tr></thead><tbody><tr><td><a href="https://github.com/MeshMap/com.meshmap.sdk.xr/blob/main/Editor/Tools/AndroidGradlePatcher.cs"><code>AndroidGradlePatcher</code></a></td><td>Injects Gradle packagingOptions to resolve duplicate native libs during Android builds.</td></tr><tr><td><a href="https://github.com/MeshMap/com.meshmap.sdk.xr/blob/main/Editor/Tools/AndroidPluginFilter.cs"><code>AndroidPluginFilter</code></a></td><td>Disables platform-specific Android AARs when building for other targets to avoid native lib conflicts.</td></tr><tr><td><a href="https://github.com/MeshMap/com.meshmap.sdk.xr/blob/main/Editor/Tools/MeshMapManifestSwitcher.cs"><code>MeshMapManifestSwitcher</code></a></td><td>Automatically selects the correct AndroidManifest.xml template for the active target (<code>METAQUEST</code>, <code>MAGICLEAP</code>, <code>XREAL</code>) before Android builds.</td></tr><tr><td><a href="https://github.com/MeshMap/com.meshmap.sdk.xr/blob/main/Editor/MarkerTracking/MetaQuest/PassthroughCameraEditorUpdateManifest.cs"><code>PassthroughCameraEditorUpdateManifest</code></a></td><td>Ensures that Meta camera and passthrough feature permissions are enabled in the AndroidManifest.xml to use the <code>Passthrough Camera Access API</code>.</td></tr><tr><td><a href="https://github.com/MeshMap/com.meshmap.sdk.xr/blob/main/Editor/Tools/UPMWrapper.cs"><code>UPMWrapper</code></a></td><td>Automatically wraps XR device SDK files in scripting defines (e.g., <code>#if MAGICLEAP</code>) for multi-device Build Profile support. Runs when Unity compiles.</td></tr></tbody></table>


# Magic Leap 2

These screenshots show the Unity 6 and device settings required to successfully deploy and launch apps on the Magic Leap 2.

## Settings & Permissions in Unity

#### 1. Build Profiles > Android > ML2 Profile > Scene List & Scripting Defines

Ensure that you have a Build Profile targeting Magic Leap with the correct scene list and scripting defines.

<figure><img src="/files/1pbXpMRYsjxhF68sM4gC" alt=""><figcaption></figcaption></figure>

#### 2. Build Profiles > Android > ML2 Profile > Other Settings

Make sure the Vulkan Graphics API, DXTC texture compression format, package name, version, IL2CPP scripting backend, new input system, x86-64 target architecture, internal write permission, and application entry point are set correctly.

<figure><img src="/files/lgHpGoB1lan5YFo1KxHX" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/EgWhMXWMmLT0mK287bfn" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/Fg9HJwnnmkaEfjZB8MJh" alt=""><figcaption></figcaption></figure>

#### 3. Build Profiles > Android > ML2 Profile > Publishing Settings&#x20;

Make sure the custom main manifest, custom main Gradle template, and custom launcher Gradle template are all correctly assigned.

<figure><img src="/files/M4Mfyq3bn2icjSdg9Lmf" alt=""><figcaption></figcaption></figure>

#### 4. Project Settings > XR Plug-in Management

Enable the OpenXR Plugin and Magic Leap 2 feature group.

<figure><img src="/files/Z8yf6vpAANSfLZ5T61q2" alt=""><figcaption></figcaption></figure>

#### 5. Project Settings > XR Plug-in Management > OpenXR > Enabled Interaction Profiles & Feature Groups

Check that any required interaction profiles, feature groups, and subsystems used in your project are enabled.

<figure><img src="/files/UqSaYTFWvkeA1tRycBtt" alt=""><figcaption></figcaption></figure>

#### 6. Project Settings > XR Plug-in Management > Project Validation

Review that all checks pass successfully.

<figure><img src="/files/JbciffepWvs1vaSCX0SI" alt=""><figcaption></figcaption></figure>

#### 7. Project Settings > MagicLeap > Permissions

Include all Android permissions that you anticipate your app needing. Additional permissions may be required on the device, in the app permission settings.

<figure><img src="/files/lzOqQR4p6R4j3OZco9k0" alt=""><figcaption></figcaption></figure>

## Installing an App to Magic Leap 2

1. To install an .apk to your device, enable [Developer Mode](https://developer-docs.magicleap.cloud/docs/guides/getting-started/enable-developer-mode/) and [USB Debug Mode](https://developer-docs.magicleap.cloud/docs/guides/getting-started/enable-developer-mode/#enable-usb-debug-mode).
2. Connect your Magic Leap 2 to your computer via USB-C cable.
   1. If your computer has a USB-A port, you may need to use a USB-A-to-C cable.
3. Enable "Allow USB debugging" if prompted.
   1. Check "Always allow from this computer" to not receive this popup in the future.
4. Use [Magic Leap Hub 3](https://developer-docs.magicleap.cloud/docs/guides/developer-tools/ml-hub-3/get-started/) to install the app. Go to Device Bridge > Apps > Install App, and select your .apk file.&#x20;

## Settings & Permissions on Device

For each app you download, you may have to manually grant permissions (e.g., camera, voice input) on device in `Settings > Apps & Notifications > App info > your-app > Permissions`.


# Meta Quest 3

These screenshots show the Unity 6 and device settings required to successfully deploy and launch apps on the Meta Quest 3.

## Settings & Permissions in Unity

#### 1. Build Profiles > Android > MetaQuest Profile > Scene List & Scripting Defines

Ensure that you have a Build Profile targeting Meta Quest 3 with the correct scene list and scripting defines.

<figure><img src="/files/g877qlAq9hpd3Blqcs7y" alt=""><figcaption></figcaption></figure>

#### 2. Build Profiles > Android > ML2 Profile > Other Settings

Make sure the Vulkan Graphics API, ASTC texture compression format, package name, version, IL2CPP scripting backend, new input system, ARM64 target architecture, internal write permission, and application entry point are set correctly.

<figure><img src="/files/rD9u4MaB1s7UPU7cuCbT" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/EjDnG5G2Z31HuBZZtaXX" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/KK5h6smIKCVNSRsKzQCa" alt=""><figcaption></figcaption></figure>

#### 3. Build Profiles > Android > ML2 Profile > Publishing Settings&#x20;

Make sure the custom main manifest, custom main Gradle template, and custom launcher Gradle template are all correctly assigned.

<figure><img src="/files/M4Mfyq3bn2icjSdg9Lmf" alt=""><figcaption></figcaption></figure>

#### 4. Project Settings > XR Plug-in Management

Enable the OpenXR Plugin and Meta Quest feature group.

<figure><img src="/files/cJBOb3Zfem6lAdGhzinW" alt=""><figcaption></figcaption></figure>

#### 5. Project Settings > XR Plug-in Management > OpenXR > Enabled Interaction Profiles & Feature Groups

Check that any required interaction profiles, feature groups, and subsystems are enabled.

<figure><img src="/files/iAJXrdcXf3R5CoxzDGxP" alt=""><figcaption></figcaption></figure>

#### 6. Project Settings > XR Plug-in Management > Project Validation

Review that all *desired* checks pass successfully.

{% hint style="danger" %}
If you are using the **Marker Tracker** feature, it is necessary to leave some checks **unfixed.**
{% endhint %}

<figure><img src="/files/rfce8LO4fGyCEjggK25i" alt=""><figcaption></figcaption></figure>

If you are not using any of our features, like marker tracking, this should be alright:

<figure><img src="/files/TyVIuR7dfZHaS27V8j6u" alt=""><figcaption></figcaption></figure>

## Installing an App to Meta Quest 3

1. To install an .apk to your device, enable [Developer Mode](https://developers.meta.com/horizon/documentation/native/android/mobile-device-setup/#enable-developer-mode).
2. Connect your Meta Quest 3 to your computer using the Link Cable.
3. Enable "Allow USB debugging" if prompted.
   1. Check "Always allow from this computer" to not receive this popup in the future.
4. Use [SideQuest](https://sidequestvr.com/setup-howto) to install the app. Click "Install APK" and select your .apk file.

## Settings & Permissions on Device

For each app you download, you may have to manually grant permissions (e.g., camera, passthrough, hand tracking) in `Quick Settings > Passthrough`, `Settings > Camera`, and `Settings > Movement Tracking`.

Some app features require `Settings > Advanced > Developer Mode` to be enabled.


# XREAL Air 2 Ultra

These screenshots show the Unity 6 and device settings required to successfully deploy and launch apps on the XREAL Air 2 Ultra.

## Settings & Permissions in Unity

#### 1. Build Profiles > Android > XREAL Profile > Scene List & Scripting Defines

Ensure that you have a Build Profile targeting XREAL with the correct scene list and scripting defines.

<figure><img src="/files/1PKYZ0OrlPcAkig9r2c6" alt=""><figcaption></figcaption></figure>

#### 2. Build Profiles > Android > XREAL Profile > Other Settings

Make sure the OpenGLES3 Graphics API, ASTC texture compression format, package name, version, IL2CPP scripting backend, new input system, ARM64 target architecture, external write permission, and application entry point are set correctly.

<figure><img src="/files/weAf7N77nXpGPDzBW8fW" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/OZYKFJf7JbMnEH1GIVL0" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/QEIbZT471heEnbVQJUVP" alt=""><figcaption></figcaption></figure>

#### 3. Build Profiles > Android > ML2 Profile > Publishing Settings&#x20;

Make sure the custom main manifest, custom main Gradle template, and custom launcher Gradle template are all correctly assigned.

<figure><img src="/files/M4Mfyq3bn2icjSdg9Lmf" alt=""><figcaption></figcaption></figure>

#### 4. Project Settings > XR Plug-in Management

Enable the XREAL Plugin, and disable the OpenXR Plugin and any feature groups.

<figure><img src="/files/wQwUHutwZyrsv3kCUOb3" alt=""><figcaption></figcaption></figure>

#### 5. Project Settings > XR Plug-in Management > OpenXR > Enabled Interaction Profiles & Feature Groups

Remove any unused interaction profiles, feature groups, and subsystems.

<figure><img src="/files/x2USWh4gAKzxIpmT0db3" alt=""><figcaption></figcaption></figure>

#### 6. Project Settings > XR Plug-in Management > Project Validation

Review that all checks pass successfully.

<figure><img src="/files/BZR9mC9BiQ9mGvJ6bCfC" alt=""><figcaption></figcaption></figure>

#### 7. Project Settings > XR Plug-in Management > XREAL

Select the appropriate Tracking Type, Input Source, and Android permissions that you anticipate your app needing. Additional permissions may be required on the device, in the app permission settings.

<figure><img src="/files/jr60eoAvdvSluJIlSNbH" alt=""><figcaption></figcaption></figure>

## Installing an App to XREAL Air 2 Ultra

1. To install an .apk to your XREAL Air 2 Ultra, you will need [adb platform-tools](https://developer.android.com/tools/adb) on your computer.
2. Enable [Developer options](https://developer.android.com/studio/debug/dev-options) on your Beam Pro or Samsung Galaxy S24 and connect it to your computer via USB-C.
3. Enable "Allow USB debugging" if prompted.
   1. Check "Always allow from this computer" to not receive this popup in the future.
4. Open the command line on your computer and use `adb install "path\to\your\app.apk"` to install the app to your device.
5. Connect your glasses to your Beam Pro or Samsung Galaxy 24 and launch the app through the My Glasses app's AR Mode.

## Settings & Permissions on Device

For each app you download, you have to enable *"Display over other apps"* on your Beam Pro or Samsung Galaxy 24 in `Settings > Apps > your-app`.

On your XREAL Air 2 Ultra, make sure to select *"6 DOF"* mode and enable *"hand tracking"* in the device settings.


# Mobile Spectator (iOS)

Documentation in progress.


# Mobile Spectator (Android)

Documentation in progress.


# Rigs

## Components

<table><thead><tr><th width="226.00006103515625">Class</th><th>Description</th></tr></thead><tbody><tr><td><a href="https://github.com/MeshMap/com.meshmap.sdk.xr/blob/main/Runtime/Rigs/CustomHandTracker.cs"><code>CustomHandTracker</code></a></td><td>Helper class to swap between <code>TrackedPoseDriver</code> and <code>TrackedPoseDriver (Input System)</code> based on target device.</td></tr><tr><td><a href="https://github.com/MeshMap/com.meshmap.sdk.xr/blob/main/Runtime/Rigs/XRRigPlatformProvider.cs"><code>XRRigPlatformProvider</code></a></td><td>Provides platform-specific XR rig and input action assets at runtime based on the active build target's scripting define.</td></tr></tbody></table>

## Prefabs

* **Cross-platform XR Rig** — A generic XR Rig that can be used across devices. Any device-specific components are automatically enabled by `XRRigPlatformProvider`. Implements the **XR Origin (XR Rig)** prefab from Unity XR Interaction Toolkit v3.0.8 Starter Assets sample.


# Interactables

## Components

<table><thead><tr><th width="268.39996337890625">Class</th><th>Description</th></tr></thead><tbody><tr><td><a href="https://github.com/MeshMap/com.meshmap.sdk.xr/blob/main/Runtime/Interactables/XRInteractableCooldownFilter.cs"><code>XRInteractableCooldownFilter</code></a></td><td>Prevents rapid re-hovering or re-selecting of an <code>XRBaseInteractable</code>. Implements both <code>IXRSelectFilter</code> and <code>IXRHoverFilter</code>. Attach this component to the same <code>GameObject</code> as the interactable and add it to the Hover Filters and/or Select Filters list in the Inspector window.</td></tr></tbody></table>


# Interactors

## Components

<table><thead><tr><th width="337.19989013671875">Class</th><th>Description</th></tr></thead><tbody><tr><td><a href="https://github.com/MeshMap/com.meshmap.sdk.xr/blob/main/Runtime/Interactors/CustomRotationAxisLockGrabTransformer.cs"><code>CustomRotationAxisLockGrabTrasnformer</code></a></td><td>Inherits from <code>XRBaseGrabTransformer</code>. Allows for the locking of specific rotation axes in world space. When an object is grabbed and manipulated, this class ensures that rotations are only applied to the specified axes in global coordinates, preserving the initial world rotation for the others.</td></tr><tr><td><a href="https://github.com/MeshMap/com.meshmap.sdk.xr/blob/main/Runtime/Interactors/CustomXRInputModalityManager.cs"><code>CustomXRInputModalityManager</code></a></td><td>A cross-platform replacement helper for <code>XRInputModalityManager</code> to robustly switch between hands and controllers, with an override to use controller when any controller button is pressed, even if the OS hasn't reported tracking yet.</td></tr><tr><td><a href="https://github.com/MeshMap/com.meshmap.sdk.xr/blob/main/Runtime/Interactors/XRPinchInteractor.cs"><code>XRPinchInteractor</code></a></td><td>XR Interactor that tracks hand joints via <code>XRHandSubsystem</code> to detect pinch gestures. Optionally, attach a <code>TrackedPoseDriver</code> to update the position instead of XR Hands pose.</td></tr><tr><td><a href="https://github.com/MeshMap/com.meshmap.sdk.xr/blob/main/Runtime/Interactors/XRPokeInteractorDriver.cs"><code>XRPokeInteractorDriver</code></a></td><td>Tracks and updates the pose of the <code>XRPokeInteractor</code>.</td></tr><tr><td><a href="https://github.com/MeshMap/com.meshmap.sdk.xr/blob/main/Runtime/Interactors/XRPokeInteractorSound.cs"><code>XRPokeInteractorSound</code></a></td><td>Controls the sound effects of <code>XRPokeInteractor</code>.</td></tr><tr><td><a href="https://github.com/MeshMap/com.meshmap.sdk.xr/blob/main/Runtime/Interactors/XRPokeInteractorVisual.cs"><code>XRPokeInteractorVisual</code></a></td><td>Updates the position of a visual indicator to follow the <code>XRPokeInteractor</code>.</td></tr></tbody></table>


# UI

## Components

<table><thead><tr><th width="182.79998779296875">Class</th><th>Description</th></tr></thead><tbody><tr><td><a href="https://github.com/MeshMap/com.meshmap.sdk.xr/blob/main/Runtime/UI/CustomHandMenu.cs"><code>CustomHandMenu</code></a></td><td>A simplified hand menu that follows hand tracking and activates based on palm orientation and camera gaze.</td></tr><tr><td><a href="https://github.com/MeshMap/com.meshmap.sdk.xr/blob/main/Runtime/UI/SpatialButton.cs"><code>SpatialButton</code></a></td><td>A 3D "button" that inherits from <code>XRSimpleInteractable</code> to react to hover and select events from any XRI interactor.</td></tr></tbody></table>


# Marker Tracking

## Getting Started

The AR marker tracking feature is spread across several scripts that support multiple XR devices.

The best way to get started in an existing project is to import the `Marker Tracking` sample via `Window > Package Manager > MeshMap XR > Samples`.

Or, download one of our [example projects](/unity-sdk/overview/example-projects) that put marker tracking to use in location-based AR games and experiences.

## Components

### Core

<table><thead><tr><th width="240.39996337890625">Class</th><th>Description</th></tr></thead><tbody><tr><td><a href="https://github.com/MeshMap/com.meshmap.sdk.xr/blob/v0.0.5/Runtime/MarkerTracking/Core/IPlatformMarkerService.cs"><code>IPlatformMarkerService</code></a></td><td>Platform-agnostic marker detection service used by the front-end. Implementations must provide marker poses for a known set of marker IDs.</td></tr></tbody></table>

### Device-specific

#### Magic Leap 2

<table><thead><tr><th width="240.39996337890625">Class</th><th>Description</th></tr></thead><tbody><tr><td><a href="https://github.com/MeshMap/com.meshmap.sdk.xr/tree/v0.0.5/Runtime/MarkerTracking/MagicLeap"><code>MLMarkerService</code></a></td><td>Implements <code>IPlatformMarkerService</code> for Magic Leap 2.</td></tr></tbody></table>

#### Meta Quest 3

<table><thead><tr><th width="240.39996337890625">Class</th><th>Description</th></tr></thead><tbody><tr><td><a href="https://github.com/MeshMap/com.meshmap.sdk.xr/blob/v0.0.5/Runtime/MarkerTracking/MetaQuest/MQMarkerService.cs"><code>MQMarkerService</code></a></td><td>Implements <code>IPlatformMarkerService</code> for Meta Quest 3.</td></tr></tbody></table>

### Frontend

<table><thead><tr><th width="240.39996337890625">Class</th><th>Description</th></tr></thead><tbody><tr><td><a href="https://github.com/MeshMap/com.meshmap.sdk.xr/blob/v0.0.5/Runtime/MarkerTracking/Frontend/MarkerAnchor.cs"><code>MarkerAnchor</code></a></td><td>Pairs a fiducial marker's encoded ID (e.g., 0) with the <code>GameObject</code> that represents its pose and visual.</td></tr><tr><td><a href="https://github.com/MeshMap/com.meshmap.sdk.xr/blob/v0.0.5/Runtime/MarkerTracking/Frontend/MarkerContent.cs"><code>MarkerContent</code></a></td><td>Pairs a fiducial  marker's encoded ID (e.g., 0) with the <code>GameObject</code> that localizes to its position.</td></tr><tr><td><a href="https://github.com/MeshMap/com.meshmap.sdk.xr/blob/v0.0.5/Runtime/MarkerTracking/Frontend/MarkerOrientation.cs"><code>MarkerOrientation</code></a></td><td>Defines the orientation of a tracked marker relative to the environment. Flat or Upright.</td></tr><tr><td><a href="https://github.com/MeshMap/com.meshmap.sdk.xr/blob/v0.0.5/Runtime/MarkerTracking/Frontend/MarkerTrackerUI.cs"><code>MarkerTrackerUI</code></a></td><td>UI controller for managing the marker localization process, including progress panels, user input toggles, and orientation selection.</td></tr><tr><td><a href="https://github.com/MeshMap/com.meshmap.sdk.xr/blob/v0.0.5/Runtime/MarkerTracking/Frontend/MarkerTrackingControls.cs"><code>MarkerTrackingControls</code></a></td><td>Controls marker tracking and content localization. Handles marker detection, content activation, and user-driven localization flow.</td></tr><tr><td><a href="https://github.com/MeshMap/com.meshmap.sdk.xr/blob/v0.0.5/Runtime/MarkerTracking/Frontend/Utility/OriginalTransform.cs"><code>OriginalTransform</code></a></td><td>Represents the original position and rotation of a transform. Used to store and restore the initial state of objects during localization.</td></tr><tr><td><a href="https://github.com/MeshMap/com.meshmap.sdk.xr/blob/main/Runtime/MarkerTracking/Frontend/FX/ToggleMarkerVisuals.cs"><code>ToggleMarkerVisuals</code></a></td><td>Toggles the visibility of the mesh object (marker visual) children of the <code>MarkerAnchor</code>s.</td></tr></tbody></table>

#### Calibration

<table><thead><tr><th width="240.39996337890625">Class</th><th>Description</th></tr></thead><tbody><tr><td><a href="https://github.com/MeshMap/com.meshmap.sdk.xr/blob/v0.0.5/Runtime/MarkerTracking/Frontend/Calibration/CalibrationController.cs"><code>CalibrationController</code></a></td><td>Provides UI controls for adjusting positional and rotational calibration values, and resetting to a predefined state. Also allows toggling visibility of position controls.</td></tr><tr><td><a href="https://github.com/MeshMap/com.meshmap.sdk.xr/blob/v0.0.5/Runtime/MarkerTracking/Frontend/Calibration/CalibrationModel.cs"><code>CalibrationModel</code></a></td><td>Manages calibration adjustments for localized AR content.  Provides methods to manipulate position and rotation offsets and broadcasts changes via events.</td></tr><tr><td><a href="https://github.com/MeshMap/com.meshmap.sdk.xr/blob/v0.0.5/Runtime/MarkerTracking/Frontend/Calibration/CalibrationView.cs"><code>CalibrationView</code></a></td><td>Displays real-time calibration offset values in the UI for position and rotation.</td></tr></tbody></table>

#### Dependency Injection

<table><thead><tr><th width="269.199951171875">Class</th><th>Description</th></tr></thead><tbody><tr><td><a href="https://github.com/MeshMap/com.meshmap.sdk.xr/blob/v0.0.5/Runtime/MarkerTracking/Frontend/DI/MarkerTrackingLifetimeScope.cs"><code>MarkerTrackingLifetimeScope</code></a></td><td>Configures the dependency injection container for marker tracking components. Registers relevant components found in the scene hierarchy for injection.</td></tr></tbody></table>

## Prefabs

* **Marker Tracker** — Ready-to-use prefab with all marker tracking, calibration, dependency injection, and a simple UI preconfigured.
* **Marker Sheet** — Visual model for fiduciary markers. Includes a **blank** version and an **AprilTag0** version in A4 and letter size. Adding the visuals in scene makes it easier to validate the tracking accuracy.
* **Calibration Tutorial** — Demonstrates how to calibrate the marker tracking results. Miniature so it is easier to understand the spatial relationship between `GameObject`s and the physical environment.


# Setup and User Experience

{% hint style="info" %}
Follow along in the MeshMap XR > Marker Tracking sample.
{% endhint %}

## Scene Objects

<figure><img src="/files/OAtFuwKqDwrEquLgQ4Fx" alt=""><figcaption><p>Hierarchy window</p></figcaption></figure>

There are four required assets to use fiducial marker tracking:

* **XR rig** — Represents the player.
* **Marker Tracker** — Contains all the components for tracking markers, calibrating the results, and interacting with menus.
* **Content group(s)** — Parent `GameObject`s that contain the assets you want to localize. Each content group should correspond to at least one marker.
* **Marker visual(s)** — Visual indicators that show accuracy of tracking. One visual per marker.

Organize your scene around these four assets to keep the relationships clear.

{% hint style="info" %}
This example also uses the `Hand Menu`.prefab which makes the Marker Tracker UI follow your XR rig's hands/controllers.
{% endhint %}

## Marker Tracker Setup

<figure><img src="/files/TrElmL9UYSEQUXV84ewG" alt=""><figcaption><p>Marker Tracker with canvas and visuals toggle.</p></figcaption></figure>

The Marker Tracker object includes references to the marker anchors and content, a dependency injection manager, an action/button input to show/hide the marker visuals, and a `Canvas` for users to start and cancel the tracking process.

Refer to `Marker Tracker`.prefab for the preconfigured fields.

## Organize Content into Nearby Groups to Improve Accuracy

<figure><img src="/files/4LWIwPygOQPgraTuOScj" alt=""><figcaption><p>Content groups</p></figcaption></figure>

In the screenshot above, the "Content" section of the scene has four content groups, each with its respective markers and objects as children.

It's best practice to **use separate content groups for different areas of your location-based experience.** Each content group is localized by a unique fiducial marker (e.g., AprilTag).

**As you move further away from the marker, the localization accuracy may decrease**. This is because any small errors in the rotational angles of the marker's perceived pose increase with distance.

{% hint style="info" %}
The formula to calculate accuracy as you move further away is:

$$
\text{error distance} = 2r \cdot \sin\left(\frac{\text{error degree of rotational angle}}{2}\right)
$$
{% endhint %}

**Separating your content into local chunks helps maintain accuracy and keep your project organized.**

For example, if you are creating an experience for a park you can make one content group for the entrance area, a second for a walking path, and another for the central fountain.

You can use as many content groups as you have unique `Marker ID`s for, and put as many `GameObject`s within each as you like. Just keep in mind how often you want the player to have to localize (i.e., track) to another marker to continue the experience.

{% hint style="info" %}
There is a trade-off between the accuracy gained from having markers span shorter distances and the user friction of having to repeatedly track new markers.
{% endhint %}

## Marker-Visual-Content Relationship

<figure><img src="/files/2lFwiZDD36oru5nHWMTt" alt=""><figcaption><p>Section of the <code>Marker Tracking Controls</code> component in the Inspector window</p></figcaption></figure>

Connect the markers, visuals, and content groups in the `MarkerTrackingControls` component of the Marker Tracker prefab.

In the left column, list the `Marker ID`s (the number which will be read from the AprilTag, ArUco marker, or QR code) that you are using for your app. They do not have to be sequential, but that makes it easier.

In the right columns, list the corresponding marker visuals and the content groups.

{% hint style="info" %}
**Multiple fiducial markers can be used to localize the same content group.**

In the screenshot above, markers 3, 4, and 5 all localize Content3.

This many-to-one relationship let's you have backup marker locations in case one happens to be unavailable. For example, due to public works renovations or a group of people standing in the place.
{% endhint %}

## Marker Tracker UI/UX

<figure><img src="/files/upnM2iCM3zKBNK39pqI3" alt=""><figcaption><p>Localization panel of the Marker Tracker prefab canvas</p></figcaption></figure>

The Marker Tracker prefab's Localization panel includes an option to set whether the fiducial marker is flat (e.g., on the ground) or upright  (e.g., on a wall).

It also includes options to "freeze" any of the rotational axes. This can be useful if you only need to adjust the pitch (X), yaw (Y), or roll (Z). Often, if a marker is on the ground, freezing X and Z can remove error introduced by an unlevel surface.

The "Show/Hide Visuals" button in the bottom-right allows you to toggle any virtual visual indicators that you use for your fiducial markers in Unity.

Click "Calibrate" to go to the Calibration panel.

## Calibration UI/UX

<figure><img src="/files/ZX9XiXCrn8dRgVEfPGu2" alt=""><figcaption><p>Calibration panel of the Marker Tracker prefab canvas</p></figcaption></figure>

Since marker tracking-based localization gets less accurate as you move farther away, you can use the Calibration panel to tweak the position of content as you go.

Face in the Z-forward direction of the most recent fiducial marker that you localized (i.e., tracked) to and press the buttons to incrementally tweak the pose of its content group. You must localize to a marker before you can calibrate.


# Magic Leap 2 Support

com.meshmap.sdk.magicleap2

`v0.2.0-pre.6`   `24 Oct 2025`

The **MeshMap Magic Leap 2 Support** package contains features and sample assets for creating location-based AR experiences with the Magic Leap 2 headset.

{% hint style="info" %}
This package depends on a minimally modified version of the Magic Leap SDK to prevent compilation errors when building for multiple target XR devices with Unity OpenXR Plugin 1.15.0 (used in [MeshMap XR](/unity-sdk/xr)). In accordance with the [Magic Leap 2 Software License Agreement](https://www.magicleap.com/legal/software-license-agreement-ml2), the package is included in a [sample project](https://github.com/MeshMap/mmsdk-samples-magicleap2/), but not redistributed solely on its own.
{% endhint %}

[**Changelog**](https://github.com/MeshMap/com.meshmap.sdk.magicleap2/blob/main/CHANGELOG.md)

***

## Features

In addition to the samples below:

* **Dimming** — Adjust the Magic Leap Global Dimming setting with a UI slider.
* **Haptics** — Simple helper class for preset and custom haptic feedback.
* **Occlusion** — Apply occlusion shaders/materials to objects and UI elements.
* **Rigs** — A reliable XR rig to use in Magic Leap projects.

***

### Samples

To import a sample into an existing Unity project, go to `Windows > Package Manager > MeshMap Magic Leap 2 Support > Samples`.

* **Marker Tracking** — Demonstrates how to set up a simple scene with marker tracking and a calibration UI to localize content for location-based AR experiences.
* **Space Localization** — Demonstrates how to localize using Magic Leap 2 Spaces.
* **Spatial Anchors** — Demonstrates how to position AR content across sessions using Magic Leap 2 Spatial Anchors Subsystem.
* **Physical Occlusion** — Demonstrates how to occlude AR content using the physical environment, nearby objects, hands, and controllers using the experimental Magic Leap 2 Physical Occlusion Feature.
* **User Calibration** — Demonstrates how to check the user's headset fit and eye calibration status using the experimental Magic Leap 2 User Calibration Feature.
* **Eye Tracking** — Demonstrates how to get the user's eye tracker data using the experimental Magic Leap 2 Eye Tracking Feature. Recommended to pair with the User Calibration sample.
* **Voice Commands** — Demonstrates how to set up custom voice commands using Magic Leap Voice Intents.

For more samples, we recommend the official [Magic Leap Unity (OpenXR) Example Project](https://developer-docs.magicleap.cloud/docs/guides/unity-openxr/openxr-unity-samples/).

***

## Getting Started

The easiest way to get started is to clone the [**MeshMap Samples for Magic Leap 2** repository](https://github.com/MeshMap/mmsdk-samples-magicleap2/) and open the project using [Unity Hub](https://unity.com/unity-hub).

The samples are pre-imported and the project settings are already configured.

***

### Requirements

* Windows, Mac
* **Unity 2022.3.11 LTS** or **6000.0.58f2 LTS**
  * Android Build Support
  * Universal Render Pipeline (URP)

{% hint style="info" %}
Other Unity versions *may* work but they have **not** been tested. We strongly recommend using the one of the versions listed above.
{% endhint %}

***

### Package Dependencies

* VContainer v1.16.8 (scoped registry)
* Unity TextMeshPro v3.0.7
  * Make sure to [Import TMP Essential Resources](https://docs.unity3d.com/Packages/com.unity.textmeshpro@4.0/manual/index.html).
* Unity Input System v1.11.2
* Unity XR Interaction Toolkit v2.6.3
* Unity OpenXR v1.13.0
* Magic Leap SDK v2.6.0-pre.R15-meshmap
* [MeshMap Building Blocks v0.0.13](https://github.com/MeshMap/com.meshmap.sdk.bb)

***

### Import

The [com.meshmap.sdk.magicleap2](https://github.com/MeshMap/com.meshmap.sdk.magicleap2) package can be added to an existing project via Unity Package Manager (UPM) from Git. Follow the import [instructions](/unity-sdk/overview/using-our-packages).

***

### Basic Usage

After import, the files will be available in the Packages section of the Project window in Unity Editor.

For further support, we highly recommend the official [Magic Leap Developer Documentation](https://developer-docs.magicleap.cloud/docs/category/unity-openxr/) and [Forum](https://forum.magicleap.cloud/).

***

## Settings and Permissions in Unity

* In `Project Settings > Player Android > Other Settings`, make sure the Vulkan Graphics API, DXTC texture compression format, minimum API level Android 10.0 (API level 29), IL2CPP scripting backend, new Input System, x86-64 target architecture, and internal write permission are set correctly.
* Check `Project Settings > MagicLeap > Permissions > API Level 29`.
* In `Project Settings > XR Plug-in Management`, enable the OpenXR Plugin and Magic Leap 2 feature groups.
* In `Project Settings > XR Plug-in Management > OpenXR > Enabled Interaction Profiles & Feature Groups`, check that the required interaction profiles, feature groups, and subsystems are enabled.
  * Magic Leap 2 Controller Interaction Profile, Eye Gaze Interaction Profile, and Hand Interaction Profile (if using hand tracking).
* In `Project Settings > XR Plug-in Management > Project Validation`, review that all checks pass successfully.
* In `Project Settings > MagicLeap > Permissions`, include all Android permissions that you anticipate your app needing.

***

## Installing an App to Magic Leap 2

To install an .apk to your Magic Leap 2, enable [Developer Mode](https://developer-docs.magicleap.cloud/docs/guides/getting-started/enable-developer-mode/). Then, connect it to your computer via USB-C and use [Magic Leap Hub 3](https://developer-docs.magicleap.cloud/docs/guides/developer-tools/ml-hub-3/get-started/) > Device Bridge.

\**If your computer has a USB-A port, you may need to use a USB-A-to-C cable.*

***

## Settings and Permissions on Device

* Manually grant permissions (e.g., camera, voice input) in `Settings > Apps & Notifications > App info > your-app > Permissions`.
* For the Voice Commands sample, enable Voice Commands in the device at `Settings > Magic Leap Inputs > Voice`.


# Building Blocks

com.meshmap.sdk.bb

`v0.1.1-pre.1`   `23 Dec 2025`

The **MeshMap Building Blocks** package is a reusable Unity package shared across MeshMap projects.\
It contains common systems, utilities, and prefabs designed to accelerate gameplay and tool development.

[**Changelog**](https://github.com/MeshMap/com.meshmap.sdk.bb/blob/main/CHANGELOG.md)

***

## Features

* **Art** – Example image and video (2D and 3D stereo) prefabs.
* [**Audio**](/unity-sdk/building-blocks/audio) – Centralized audio management with registries, mixer support, and in-game settings.
* [**Events**](/unity-sdk/building-blocks/events) – Completable and repeatable trigger events for modular gameplay flow.
* [**Save System**](/unity-sdk/building-blocks/save-system) – Scene-wide saving/loading for `ISaveable` components.
* **Utilities** – Common helpers for transforms, strings, audio, particles, NavMesh, and component management.
* **Interactions** – Collectables, damageable interfaces, and other runtime interaction contracts.
* **Testing & Logging** – Debug logger prefabs and utilities for runtime inspection.
* **Tools** – Simple camera behaviours, UI helpers, and logging systems.

For details on minor features, see [Other Features](/unity-sdk/building-blocks/other-features).

***

## Getting Started

The easiest way to get started is to clone one of the [Example Projects](/unity-sdk/overview/example-projects) and open the project using [Unity Hub](https://unity.com/unity-hub).

***

### Requirements

* Windows, Mac
* **Unity 2022.3.11 LTS** or later
  * Android Build Support
  * Universal Render Pipeline (URP)

***

### Package Dependencies

* Unity AI Navigation v1.1.5
* Unity Input System v1.11.2
* Unity TextMeshPro v3.0.7
  * Make sure to [Import TMP Essential Resources](https://docs.unity3d.com/Packages/com.unity.textmeshpro@4.0/manual/index.html).

***

### Import

The [com.meshmap.sdk.bb](https://github.com/MeshMap/com.meshmap.sdk.bb) package can be added via Unity Package Manager (UPM) from Git. Follow the import [instructions](https://docs.meshmap.com/unity-sdk/overview/using-packages).

***

### Basic Usage

After import, the files will be available in the Packages section of the Project window in Unity Editor.


# Audio

The **Audio** system provides a centralized way to manage music, sound effects (SFX), and UI sounds.

## Components

### [AudioManager](https://github.com/MeshMap/com.meshmap.sdk.bb/blob/main/Runtime/Audio/AudioManager.cs)

* Singleton manager for playing Music, SFX, UI, and Other `AudioClips`.
* Supports:
  * Mixer group routing (`MusicGroup`, `SFXGroup`, `UIGroup`, `OtherGroup`).
  * Fade-in/out for Music.
  * Saving/restoring music playback positions.
  * Randomized pitch for SFX/UI.
  * Override previous Other audio clip.
* Requires an `AudioRegistry`.asset in a `Resources` folder in your project.

### [AudioRegistry](https://github.com/MeshMap/com.meshmap.sdk.bb/blob/main/Runtime/Audio/AudioRegistry.cs)

* `ScriptableObject` that maps `string` keys to `AudioClips`.
* Create one `AudioRegistry` in your Project via `Create > MeshMapLabs > Building Blocks > Audio > AudioRegistry`. Place it in a `Resources` folder to be loaded via `AudioRegistryCache.Instance`.
* Change load path dynamically with `AudioRegistryCache.SetResourcePath()`.

### [AudioSettings](https://github.com/MeshMap/com.meshmap.sdk.bb/blob/main/Runtime/Audio/AudioSettings.cs)

* UI slider bindings for mixer volume control.
* Saves user preferences to `PlayerPrefs`.
* Mixer parameters: `MusicVolume`, `SFXVolume`, `UIVolume`.

### [PlaySound](https://github.com/MeshMap/com.meshmap.sdk.bb/blob/main/Runtime/Audio/PlaySound.cs)

* Simple `MonoBehaviour` to play a clip on an `AudioSource`.
* Optional pitch randomization.
* Can trigger on `OnEnable` or manually.

## Example Workflow

1. Add the `AudioManagerWithSettings`.prefab to your scene.
2. Create an `AudioRegistry`.asset in a `Resources` folder.
3. Add unique `string` keys and `AudioClips` to the `AudioRegistry`.
4. Add three `Sliders` to your game UI and assign them in `AudioSettings`.
5. Add a `SliderValueText` component to each `Slider` and assign a `TextMeshProUGUI` to each.
6. Play the `AudioClips` from anywhere in your project by referencing `AudioManager.Instance`.

## Example Code

```csharp
// Play UI sound
AudioManager.Instance.PlayUI("UI.ButtonClick", randomizePitch: true);

// Play looping background music
AudioManager.Instance.PlayMusic("Music.MainTheme", loop: true, fadeIn: true);

// Adjust music volume via UI slider
public void OnMusicVolumeChanged(float value)
{
    AudioManager.Instance.SetMusicVolume(value);
}
```

## Prefabs

* **AudioManager** — Ready-to-use manager `GameObject` with mixer groups and `AudioSource`s preconfigured. Make sure there is an [`AudioRegistry`](#audioregistry-and-audioregistrycache) in your Project.
* **AudioManagerWithSettings** — Variant with `AudioSettings` UI bindings. Requires a UI with volume sliders to be setup.


# Events

The **Events** system defines modular gameplay triggers that can be configured in the editor.

## Event Types

### Repeatable Events

* Executes logic each time it is activated.
* Examples:
  * [**ShowObjectsTriggerEvent**](https://github.com/MeshMap/com.meshmap.sdk.bb/blob/main/Runtime/Events/Repeatable/Trigger/ShowObjectsTriggerEvent.cs) – Shows objects while inside the bounds of a trigger `Collider`. For example, to show decorative `GameObject`s that should only be visible when the player is nearby during a location-based experience.
  * [**PlayAudioTriggerEvent**](https://github.com/MeshMap/com.meshmap.sdk.bb/blob/main/Runtime/Events/Repeatable/Trigger/PlayAudioTriggerEvent.cs) – Plays an audio clip while inside the bounds of a trigger `Collider`. Requires an `AudioManager` in the scene and an `AudioRegistry` in a Resources folder. For example, to play narrative audio as a player walks around a self-guided AR tour.

### Completable Events

* Executes logic once when activated and marks it as complete.
* Implements `ISaveable` to optionally make the `CompletableEventState` persist across app launches using the [Save System](https://docs.meshmap.com/unity-sdk/building-blocks/save-system) feature.
* Examples:
  * [**SpawnObjectsTriggerEvent**](https://github.com/MeshMap/com.meshmap.sdk.bb/blob/main/Runtime/Events/Completable/Trigger/SpawnObjectsTriggerEvent.cs) – Spawns objects at intervals upon entering the bounds of a trigger `Collider`. For example, to spawn enemy `GameObject`s that a player must defeat in a location-based AR game.
  * [**SwitchObjectsTriggerEvent**](https://github.com/MeshMap/com.meshmap.sdk.bb/blob/main/Runtime/Events/Completable/Trigger/SwitchObjectsTriggerEvent.cs) – Switches the active/inactive states of objects upon entering the bounds of a trigger `Collider`. For example, to swap the content that is shown along the return path of a location-based AR tour experience.

## Prefabs

Ready-to-use examples with placeholder objects assigned for **ShowObjectsTriggerEvent**, **SpawnObjectsTriggerEvent**, and **SwitchObjectsTriggerEvent**.

{% embed url="<https://youtu.be/qxqGBvJandM>" fullWidth="false" %}
ShowObjectsTriggerEvent.prefab
{% endembed %}


# Save System

The **Save System** allows persistent saving and loading of component state across scenes.

## Components

### [ISaveable](https://github.com/MeshMap/com.meshmap.sdk.bb/blob/main/Runtime/SaveSystem/ISaveable.cs)

* Interface to make a behaviour's state persistent.
* Must define a `SaveId` (string) and state serialization logic.

### [SaveableMonoBehaviour](https://github.com/MeshMap/com.meshmap.sdk.bb/blob/main/Runtime/SaveSystem/SaveableMonoBehaviour.cs)

* Base `MonoBehaviour` implementing `ISaveable` for convenience.
* Override save/load methods to store component-specific data.

### [SaveManager](https://github.com/MeshMap/com.meshmap.sdk.bb/blob/main/Runtime/SaveSystem/SaveManager.cs)

* Central controller that manages all `ISaveable` components in the scene.
* Assigns unique IDs and coordinates save/load operations.
* Editor inspector includes:
  * **Assign SaveIds to All Saveables** – Generates IDs for unassigned components.
  * **Clear and Reassign** – Resets IDs and clears stored preferences.

### [SaveManagerUI](https://github.com/MeshMap/com.meshmap.sdk.bb/blob/main/Runtime/SaveSystem/SaveManagerUI.cs)

* Optional UI component to expose save/load/reset actions to the player.

### [SavePrefs](https://github.com/MeshMap/com.meshmap.sdk.bb/blob/main/Runtime/SaveSystem/SavePrefs.cs)

* Static helper for storing save data in `PlayerPrefs`.
* Includes `ResetAll()` to clear all stored state.

## Example Workflow

1. Add `SaveManager` to your scene.
2. Implement `ISaveable` on components whose state should persist.
   1. Tip: inherit from `SaveableMonoBehaviour` for clean integration with Unity callbacks.
3. Auto-assign unique `SaveId`s to each object using the `SaveManager` Inspector component.
4. Call `SavePrefs` methods or use `SaveManager` to save/load.
5. Add an optional button to your in-app UI to call `SaveManager.ResetAllSaveables()`.

## Example Code

```csharp
// Example implmentation of the Save System for a collectable object, like a pickup item in a game.
public class Collectable : SaveableMonoBehaviour<CollectableState>
{
    protected override CollectableState DefaultState => CollectableState.NotCollected;

    protected override void Awake()
    {
        base.Awake();
        
        // Deactive the collectable GameObject if it was already collected during a previous session
        if (_currentState == CollectableStatus.Collected && DeactivateOnComplete)
        {
             gameObject.SetActive(false);
        }
    }

    // Update the save status of the collectable GameObject so it is not active in the next session
    public void GetCollected()
    {
        State = CollectableState.Collected;
        SetSaveStatus();
        if (DeactivateOnComplete)
        {
            gameObject.SetActive(false);
        }
    }
}
```


# Other Features

The **Building Blocks** package also includes several smaller utilities and helpers. These features are lightweight and can be used independently.

## Art

* **Images** – Example non-UI image prefabs using URP Lit shader.
* **Videos**– Example 2D video prefab using URP Unlit shader and 3D stereo video prefab using a custom shader.

## Utilities

* **AudioUtils** – Decibel conversions, random pitch playback.
* **ColorUtils** - Compare RGB values.
* **ComponentUtils** – Safe component fetch/add methods.
* **EnumUtils** - Return unique values and exclude values.
* **NavMeshUtils** – Simplified NavMesh queries.
* **ParticleSystemUtils** – ParticleSystem group state management helpers.
* **StringUtils** – String operations and random string generation.
* **TransformUtils** – Common transform manipulation helpers.

## Interactions

* **Collectable** – Interfaces (`ICollectable`, `ICollector`) and behaviours for collectible items.
* **Damageable** – `IDamageable` interface for hit/damage handling.

## Testing & Logging

* **DebugLogger** prefab for in-game logging.
* **GameLogger** script for structured logging.

## Tools

* **FaceCamera** – Rotates an object to face the camera.
* **PlaceInFrontOfCamera** – Positions object relative to camera.
* **SetWorldCameraOnStart** – Ensures UI canvases target the main camera.
* **UI Helpers** – `SliderValueText`, `ToggleMenu`.


# FAQ

Frequently asked questions about the MeshMap Unity SDK.

### General

<details>

<summary>Which Unity version should I use?</summary>

* **MeshMap XR** targets **Unity 6000.0.50f1 LTS** (Unity 6).
* **Core** and **Building Blocks** work on **Unity 2022.3.11 LTS** or later.
* **ML2 Support** supports **2022.3.11 LTS** or later.

For new projects, we strongly recommend using Unity 6000.0.50f1 LTS.

Make sure to include the **Android Build Support** module.

For more information, see [Getting Started](/unity-sdk/overview/getting-started).

</details>

<details>

<summary>Which render pipeline should I use?</summary>

All of MeshMap's packages, samples, and example projects use the **Universal Render Pipeline** (URP).

For more information, see [Getting Started](/unity-sdk/overview/getting-started).

</details>

<details>

<summary>How do I add the packages to my project?</summary>

The recommended and easiest way to get started is by downloading one of our [Example Projects](/unity-sdk/overview/example-projects).

Alternatively, you can add individual packages by **Git URL** in the Package Manager window. Make sure to include the **VContainer** scoped registry first in the Project Settings, and satisfy all package dependencies.

The SDK assets are added to the Packages folder.

For more information, see [Using Our Packages](/unity-sdk/overview/using-our-packages).

</details>

<details>

<summary>Should I start from scratch or a sample?</summary>

We **highly encourage** starting with an [Example Project](/unity-sdk/overview/example-projects) so all the device settings, permissions, and profiles are pre-configured. Starting from a sample will reduce the likelihood of avoidable Project Settings errors arising.

From there, you can change the project to match your unique vision!

</details>

### Core

<details>

<summary>Why aren't Auth and Scans working?</summary>

You must create a MeshMap account and **configure your API key in the MeshMap Hub** (`MeshMap > MeshMap Hub`). Without that, Auth and Scans features won’t function.

{% hint style="warning" %}
Add `user.keystore` and `Assets/Resources/ApiKeyConfig.asset` to `.gitignore`. Otherwise API credentials or user keys may leak/change across versions and machines.
{% endhint %}

For more information, see [Auth](/unity-sdk/core/auth) and [Scans](/unity-sdk/core/scans).

</details>

<details>

<summary>What scan file types are supported?</summary>

Supported types are currently limited to **.glb/.gltf**.

For more information, see [Mapping](/contribute/mapping) and [Scans](/unity-sdk/core/scans).

</details>

<details>

<summary>Where in my project are the imported scans saved?</summary>

Using the Scan Importer tool saves imported files under `Assets/Resources/MeshMap/Scans/`.

For more information, see [Scans](/unity-sdk/core/scans/editor).

</details>

### XR

<details>

<summary>How do I safely run multiple XR device SDKs in one project?</summary>

Use the MeshMap XR packages' [Cross-platform Management](/unity-sdk/xr/cross-platform-management) system with Unity 6 build profiles to automatically wrap each SDK in scripting defines and use custom Android manifests and Gradle templates.

This will prevent duplicate native libs and wrong platform plugins from being included in the build process.

</details>

<details>

<summary>Why isn't my project building or running correctly?</summary>

Carefully follow the steps in [Cross-platform Management](/unity-sdk/xr/cross-platform-management) that are relevant to your target device(s). Refer to the screenshots to review each setting.

We **highly recommend** using version control, such as Git, for your project. If something goes wrong, you can reclone your project from the last stable version.

</details>

<details>

<summary>Which plugins and feature groups do I need to change for different target devices?</summary>

* [**Magic Leap 2**](/unity-sdk/xr/cross-platform-management/magic-leap-2)**:** Enable **OpenXR** and **Magic Leap 2 feature group.**
* [**Meta Quest 3**](/unity-sdk/xr/cross-platform-management/meta-quest-3)**:** Enable **OpenXR** and **Meta Quest** feature group. Some **Project Validation** checks should remain unfixed if you’re using the **Marker Tracker** feature.
* [**XREAL Air 2 Ultra**](/unity-sdk/xr/cross-platform-management/xreal-air-2-ultra)**:** Enable **XREAL plugin** and disable **OpenXR.** Failing to disable OpenXR may break runtime.

</details>

<details>

<summary>Which XR rig should I use?</summary>

Use the **Cross-platform XR Rig prefab**. `XRRigPlatformProvider` ensures the correct device-specific setup is active at runtime.

Custom rigs without this pattern may lose input/tracking per device.

For more information, see [Rigs](/unity-sdk/xr/rigs).

</details>

<details>

<summary>How do I enable hand tracking and controllers?</summary>

Use **CustomXRInputModalityManager** to smoothly switch between hand tracking and controllers. It includes quality of life features like fallback to controller input when a button is pressed.

Hand-only or controller-only input setup may strand users.

For more information, see [Rigs](/unity-sdk/xr/rigs) and [Interactors](/unity-sdk/xr/interactors).

</details>

#### XR - Marker Tracking

<details>

<summary>Which devices support Marker Tracking?</summary>

The XR package currently supports marker tracking on **Magic Leap 2** and **Meta Quest 3**.

[XREAL](https://docs.xreal.com/Image%20Tracking/Marker) has its own native image and marker tracking solution.

</details>

<details>

<summary>How do I start using the Marker Tracking feature?</summary>

The easiest way to get started is to download the [Location-based AR Sample](https://github.com/MeshMap/LocationBasedARSample) or import the **Marker Tracking sample** and use the **Marker Tracker prefab**.

The prefab manages marker detection, tracking, localization, and calibration, including dependency injection and UI.

For more information, see [Marker Tracking](/unity-sdk/xr/marker-tracking).

If you are only targeting Magic Leap 2, you can instead use the [Magic Leap 2 Support](/unity-sdk/magic-leap-2-support) package, but it is no longer being updated. We recommend new projects use the [XR](/unity-sdk/xr) package.

</details>

<details>

<summary>How do I link markers, visuals, and content groups correctly?</summary>

Use `MarkerTrackingControls` to map **Marker IDs → Visuals → Content Groups**.

It supports single and multiple marker tracking.

For more information, see [Marker Tracking Setup](/unity-sdk/xr/marker-tracking/setup-and-user-experience).

</details>

<details>

<summary>Which fiducial marker type should I use?</summary>

**AprilTags are recommended** for accurate pose detection and supported for Magic Leap 2 and Magic Quest 3. Print the tags at \~**17 cm** size with \~**2 cm** margins.

Magic Leap 2 also supports ArUco markers and QR codes. The latter can be encoded with a `?` parameter (e.g., `?marker=`) so that phone cameras open a custom URL while marker tracking decodes only the ending.

For more information, see [Marker Tracking](/unity-sdk/xr/marker-tracking).

</details>

<details>

<summary>How can I make custom fiducial markers?</summary>

The custom **AR Marker Generator** tool allows you to generate and save markers with your desired type, ID, size, and encoding.

For more information, see [AR Marker Generator](/unity-sdk/core/tools/ar-marker-generator).

</details>

### Magic Leap 2 Support

<details>

<summary>Why does my official Magic Leap Unity SDK not match the version that MeshMap uses?</summary>

The **ML2 Support** package uses a [**modified Magic Leap SDK**](https://github.com/MeshMap/com.magicleap.unitysdk) to **prevent compilation errors** with **Unity OpenXR 1.15.0**. Always use the modified package with the MeshMap SDK.

</details>

<details>

<summary>Will the Magic Leap 2 Support package continue to receive new features?</summary>

No, the ML2 Support package has been deprecated in favor of the MeshMap XR package. If you are starting a new cross-platform project, we recommend primarily using the XR package.

</details>

### Building Blocks

<details>

<summary>Why isn't my app's audio working?</summary>

There are several potential reasons why audio may be disabled in your app.

First, if you are using the Building Blocks's `Audio Manager`, make sure that you have an **`AudioRegistry` asset in a `Resources` folder**. Without it, key-to-clip lookups fail silently. See [Audio](/unity-sdk/building-blocks/audio).

If that doesn't solve it, make sure any platform-specific settings are correctly configured. See [Cross-platform Management](/unity-sdk/xr/cross-platform-management). Also refer to the official documentation for your target device.

{% hint style="warning" %}
There is a rare bug that mutes audio only in Meta Quest 3 and XREAL Air 2 Ultra app builds that we are still working to fix. Deleting and recloning your project will resolve this bug. As such, we highly recommend using Git version control to save your project.
{% endhint %}

</details>

<details>

<summary>Why aren't the trigger events in my scene being triggered?</summary>

Make sure that each trigger event (e.g., `ShowObjectsTriggerEvent`) has a `Collider` with `IsTrigger` set to True. If necessary, increase the bounds of the Collider to be large enough for the player to enter/exit.

Make sure the player's XR Rig has a `GameObject` with the tag (e.g., "Player") that is set in the event's `PlayerTag` field ("Player" by default). This same object should have a Collider with IsTrigger set to True and a `RigidBody` with `UseGravity` set to False.

Confirm in the Inspector that none of the other fields are empty, which would result in a NullReferenceException error code.

Review the setup of any of the Events prefabs in the Building Blocks package.

For more information, see [Events](/unity-sdk/building-blocks/events).

</details>

<details>

<summary>Why aren't my settings and progress being saved?</summary>

Make sure you have a **SaveManager** in your scene and the data you are trying to save is done so through a component that implements **ISaveable** (or inherits `SaveableMonoBehaviour`) with **unique `SaveId`s**.

Check that every ISaveable instance has a unique ID assigned in the Inspector. Use the **Assign IDs** and **Clear and Reassign IDs** buttons in the Inspector to auto-set these.

For more information, see [Save System](/unity-sdk/building-blocks/save-system).

</details>


# Overview

Meshmap's API is organized around **REST.** It uses two authentication methods: JWT session tokens and app api keys. Request bodies are standard JSON encoded formats and response bodies are contain standard REST response codes, authentication, and verbs&#x20;


# Core


# Authentication

## Authenticate with Privy token

> Verify a Privy token and return the user's info including roles and a Meshmap session token. Creates a new user if one does not exist.

```json
{"openapi":"3.0.0","info":{"title":"Meshmap API v1","version":"1.0.0"},"servers":[{"url":"/api/v1","description":"API v1 base URL"}],"paths":{"/auth/privy":{"post":{"summary":"Authenticate with Privy token","description":"Verify a Privy token and return the user's info including roles and a Meshmap session token. Creates a new user if one does not exist.","tags":["Authentication"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PrivyAuthRequest"}}}},"responses":{"200":{"description":"Authentication successful","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PrivyAuthResponse"}}}},"400":{"description":"Token is required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}},"components":{"schemas":{"PrivyAuthRequest":{"type":"object","required":["token"],"properties":{"token":{"type":"string","description":"Privy authentication token"}}},"PrivyAuthResponse":{"type":"object","required":["user","meshmapSessionToken"],"properties":{"user":{"type":"object","required":["id","email","firstName","lastName","roles","isAdmin","hasOpsAccess"],"properties":{"id":{"type":"string","description":"User ID"},"email":{"type":"string","nullable":true,"description":"User email"},"firstName":{"type":"string","nullable":true,"description":"User first name"},"lastName":{"type":"string","nullable":true,"description":"User last name"},"roles":{"type":"array","items":{"type":"string"},"description":"User roles"},"isAdmin":{"type":"boolean","description":"Whether the user has admin role"},"hasOpsAccess":{"type":"boolean","description":"Whether the user has ops or admin access"}}},"meshmapSessionToken":{"type":"string","description":"JWT session token for authenticating with Meshmap APIs"}}},"ErrorResponse":{"type":"object","required":["error"],"properties":{"error":{"oneOf":[{"type":"string","description":"Error message"},{"type":"array","items":{"type":"object","required":["code","message","path"],"properties":{"code":{"type":"string","description":"Error code"},"message":{"type":"string","description":"Error message"},"path":{"type":"array","items":{"oneOf":[{"type":"string"},{"type":"number"}]},"description":"Error path"}}}}]}}}}}}
```

## Get current user profile

> Get the profile information for the authenticated user

```json
{"openapi":"3.0.0","info":{"title":"Meshmap API v1","version":"1.0.0"},"servers":[{"url":"/api/v1","description":"API v1 base URL"}],"security":[{"SessionToken":[]}],"components":{"securitySchemes":{"SessionToken":{"type":"apiKey","in":"header","name":"Authorization","description":"Session token in Bearer format: Bearer <token>"}},"schemas":{"UserSchema":{"type":"object","required":["id","createdAt"],"properties":{"id":{"type":"string","description":"Unique user identifier"},"email":{"type":"string","nullable":true,"description":"User email address"},"firstName":{"type":"string","nullable":true,"description":"User first name"},"lastName":{"type":"string","nullable":true,"description":"User last name"},"phoneNumber":{"type":"string","nullable":true,"description":"User phone number"},"username":{"type":"string","nullable":true,"description":"User username"},"createdAt":{"type":"string","format":"date-time","description":"User creation timestamp"},"imageUrl":{"type":"string","nullable":true,"description":"User profile image URL"}}},"ErrorResponse":{"type":"object","required":["error"],"properties":{"error":{"oneOf":[{"type":"string","description":"Error message"},{"type":"array","items":{"type":"object","required":["code","message","path"],"properties":{"code":{"type":"string","description":"Error code"},"message":{"type":"string","description":"Error message"},"path":{"type":"array","items":{"oneOf":[{"type":"string"},{"type":"number"}]},"description":"Error path"}}}}]}}}}},"paths":{"/user":{"get":{"summary":"Get current user profile","description":"Get the profile information for the authenticated user","tags":["Authentication"],"responses":{"200":{"description":"User profile retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserSchema"}}}},"401":{"description":"Unauthorized - invalid or missing session token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}}}
```

## Update current user profile

> Update the username for the authenticated user

```json
{"openapi":"3.0.0","info":{"title":"Meshmap API v1","version":"1.0.0"},"servers":[{"url":"/api/v1","description":"API v1 base URL"}],"security":[{"SessionToken":[]}],"components":{"securitySchemes":{"SessionToken":{"type":"apiKey","in":"header","name":"Authorization","description":"Session token in Bearer format: Bearer <token>"}},"schemas":{"UserPatchRequest":{"type":"object","required":["username"],"properties":{"username":{"type":"string","minLength":1,"description":"New username for the user"}}},"UserSchema":{"type":"object","required":["id","createdAt"],"properties":{"id":{"type":"string","description":"Unique user identifier"},"email":{"type":"string","nullable":true,"description":"User email address"},"firstName":{"type":"string","nullable":true,"description":"User first name"},"lastName":{"type":"string","nullable":true,"description":"User last name"},"phoneNumber":{"type":"string","nullable":true,"description":"User phone number"},"username":{"type":"string","nullable":true,"description":"User username"},"createdAt":{"type":"string","format":"date-time","description":"User creation timestamp"},"imageUrl":{"type":"string","nullable":true,"description":"User profile image URL"}}},"ErrorResponse":{"type":"object","required":["error"],"properties":{"error":{"oneOf":[{"type":"string","description":"Error message"},{"type":"array","items":{"type":"object","required":["code","message","path"],"properties":{"code":{"type":"string","description":"Error code"},"message":{"type":"string","description":"Error message"},"path":{"type":"array","items":{"oneOf":[{"type":"string"},{"type":"number"}]},"description":"Error path"}}}}]}}}}},"paths":{"/user":{"patch":{"summary":"Update current user profile","description":"Update the username for the authenticated user","tags":["Authentication"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserPatchRequest"}}}},"responses":{"200":{"description":"User profile updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserSchema"}}}},"400":{"description":"Invalid request body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized - invalid or missing session token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Username is already taken","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}}}
```

## Remove user profile image

> Delete the profile image for the authenticated user

```json
{"openapi":"3.0.0","info":{"title":"Meshmap API v1","version":"1.0.0"},"servers":[{"url":"/api/v1","description":"API v1 base URL"}],"security":[{"SessionToken":[]}],"components":{"securitySchemes":{"SessionToken":{"type":"apiKey","in":"header","name":"Authorization","description":"Session token in Bearer format: Bearer <token>"}},"schemas":{"ErrorResponse":{"type":"object","required":["error"],"properties":{"error":{"oneOf":[{"type":"string","description":"Error message"},{"type":"array","items":{"type":"object","required":["code","message","path"],"properties":{"code":{"type":"string","description":"Error code"},"message":{"type":"string","description":"Error message"},"path":{"type":"array","items":{"oneOf":[{"type":"string"},{"type":"number"}]},"description":"Error path"}}}}]}}}}},"paths":{"/user/profile-image":{"delete":{"summary":"Remove user profile image","description":"Delete the profile image for the authenticated user","tags":["Authentication"],"responses":{"200":{"description":"Profile image removed successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}}}}}},"401":{"description":"Unauthorized - invalid or missing session token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}}}
```

## Upload user profile image

> Upload or replace the profile image for the authenticated user

```json
{"openapi":"3.0.0","info":{"title":"Meshmap API v1","version":"1.0.0"},"servers":[{"url":"/api/v1","description":"API v1 base URL"}],"security":[{"SessionToken":[]}],"components":{"securitySchemes":{"SessionToken":{"type":"apiKey","in":"header","name":"Authorization","description":"Session token in Bearer format: Bearer <token>"}},"schemas":{"UserSchema":{"type":"object","required":["id","createdAt"],"properties":{"id":{"type":"string","description":"Unique user identifier"},"email":{"type":"string","nullable":true,"description":"User email address"},"firstName":{"type":"string","nullable":true,"description":"User first name"},"lastName":{"type":"string","nullable":true,"description":"User last name"},"phoneNumber":{"type":"string","nullable":true,"description":"User phone number"},"username":{"type":"string","nullable":true,"description":"User username"},"createdAt":{"type":"string","format":"date-time","description":"User creation timestamp"},"imageUrl":{"type":"string","nullable":true,"description":"User profile image URL"}}},"ErrorResponse":{"type":"object","required":["error"],"properties":{"error":{"oneOf":[{"type":"string","description":"Error message"},{"type":"array","items":{"type":"object","required":["code","message","path"],"properties":{"code":{"type":"string","description":"Error code"},"message":{"type":"string","description":"Error message"},"path":{"type":"array","items":{"oneOf":[{"type":"string"},{"type":"number"}]},"description":"Error path"}}}}]}}}}},"paths":{"/user/profile-image":{"patch":{"summary":"Upload user profile image","description":"Upload or replace the profile image for the authenticated user","tags":["Authentication"],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["image"],"properties":{"image":{"type":"string","format":"binary","description":"Image file to upload"}}}}}},"responses":{"200":{"description":"Profile image uploaded successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserSchema"}}}},"400":{"description":"Invalid image file","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized - invalid or missing session token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}}}
```

## Generate authentication game code

> Generate a new 6-digit authentication code from headset side

```json
{"openapi":"3.0.0","info":{"title":"Meshmap API v1","version":"1.0.0"},"servers":[{"url":"/api/v1","description":"API v1 base URL"}],"security":[{"AppAPIKey":[]}],"components":{"securitySchemes":{"AppAPIKey":{"type":"apiKey","in":"header","name":"x-app-api-key","description":"App API key in format: <key>"}},"schemas":{"CreateCodeRequest":{"type":"object","required":["playerId"],"properties":{"playerId":{"type":"string","description":"Player ID to associate with the code. Used to link the player to the code. Optional.","nullable":true}}},"CodeSchema":{"type":"object","required":["code","expiryTime"],"properties":{"code":{"type":"string","description":"Generated authentication code","pattern":"^[0-9]{6}$"},"playerId":{"type":"string","nullable":true,"description":"Player ID associated with the code (if provided)"},"expiryTime":{"type":"string","format":"date-time","description":"Code expiration timestamp"}}},"ErrorResponse":{"type":"object","required":["error"],"properties":{"error":{"oneOf":[{"type":"string","description":"Error message"},{"type":"array","items":{"type":"object","required":["code","message","path"],"properties":{"code":{"type":"string","description":"Error code"},"message":{"type":"string","description":"Error message"},"path":{"type":"array","items":{"oneOf":[{"type":"string"},{"type":"number"}]},"description":"Error path"}}}}]}}}}},"paths":{"/code":{"post":{"summary":"Generate authentication game code","description":"Generate a new 6-digit authentication code from headset side","tags":["Authentication"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCodeRequest"}}}},"responses":{"200":{"description":"Code generated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodeSchema"}}}},"401":{"description":"Unauthorized - invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Unable to generate unique code or internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}}}
```

## Validate authentication code

> Validate an authentication code and optionally return a session token

```json
{"openapi":"3.0.0","info":{"title":"Meshmap API v1","version":"1.0.0"},"servers":[{"url":"/api/v1","description":"API v1 base URL"}],"security":[{"AppAPIKey":[]}],"components":{"securitySchemes":{"AppAPIKey":{"type":"apiKey","in":"header","name":"x-app-api-key","description":"App API key in format: <key>"}},"schemas":{"CreateTokenFromCodeRequest":{"type":"object","required":["code"],"properties":{"code":{"type":"string","description":"Authentication code","pattern":"^[0-9]{6}$"}}},"CreateTokenFromCodeResponse":{"type":"object","required":["status"],"properties":{"status":{"type":"string","enum":["VALID","INVALID","EXPIRED","ERROR"],"description":"Code validation status"},"sessionToken":{"type":"string","description":"JWT session token (only present when status is VALID and user is authenticated)"},"expiryTime":{"type":"string","format":"date-time","description":"Code expiry time (only present when status is VALID)"},"error":{"type":"string","description":"Error message (only present when status is not VALID)"}}},"ErrorResponse":{"type":"object","required":["error"],"properties":{"error":{"oneOf":[{"type":"string","description":"Error message"},{"type":"array","items":{"type":"object","required":["code","message","path"],"properties":{"code":{"type":"string","description":"Error code"},"message":{"type":"string","description":"Error message"},"path":{"type":"array","items":{"oneOf":[{"type":"string"},{"type":"number"}]},"description":"Error path"}}}}]}}}}},"paths":{"/auth/code":{"post":{"summary":"Validate authentication code","description":"Validate an authentication code and optionally return a session token","tags":["Authentication"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTokenFromCodeRequest"}}}},"responses":{"200":{"description":"Game code validation result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTokenFromCodeResponse"}}}},"400":{"description":"Invalid request data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized - invalid or missing App API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}}}
```


# Apps

## Get all apps

> Retrieve all apps for the authenticated user

```json
{"openapi":"3.0.0","info":{"title":"Meshmap API v1","version":"1.0.0"},"servers":[{"url":"/api/v1","description":"API v1 base URL"}],"security":[{"AppAPIKey":[]}],"components":{"securitySchemes":{"AppAPIKey":{"type":"apiKey","in":"header","name":"x-app-api-key","description":"App API key in format: <key>"}},"schemas":{"AppsListResponse":{"type":"object","required":["apps"],"properties":{"apps":{"type":"array","items":{"$ref":"#/components/schemas/AppSchema"}}}},"AppSchema":{"type":"object","required":["id","name","description","apiKey","createdAt","updatedAt","visibility"],"properties":{"id":{"type":"string","description":"Unique app identifier"},"name":{"type":"string","description":"App name"},"description":{"type":"string","description":"App description"},"imageUrl":{"type":"string","nullable":true,"description":"App image URL"},"apiKey":{"type":"string","description":"App API key"},"visibility":{"type":"string","enum":["PUBLIC","PRIVATE"],"description":"App visibility setting"},"createdAt":{"type":"string","format":"date-time","description":"Creation timestamp"},"updatedAt":{"type":"string","format":"date-time","description":"Last update timestamp"}}},"ErrorResponse":{"type":"object","required":["error"],"properties":{"error":{"oneOf":[{"type":"string","description":"Error message"},{"type":"array","items":{"type":"object","required":["code","message","path"],"properties":{"code":{"type":"string","description":"Error code"},"message":{"type":"string","description":"Error message"},"path":{"type":"array","items":{"oneOf":[{"type":"string"},{"type":"number"}]},"description":"Error path"}}}}]}}}}},"paths":{"/apps":{"get":{"summary":"Get all apps","description":"Retrieve all apps for the authenticated user","tags":["Apps"],"responses":{"200":{"description":"Apps retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppsListResponse"}}}},"401":{"description":"Unauthorized - invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}}}
```

## Create a new app

> Create a new app for the authenticated user

```json
{"openapi":"3.0.0","info":{"title":"Meshmap API v1","version":"1.0.0"},"servers":[{"url":"/api/v1","description":"API v1 base URL"}],"security":[{"AppAPIKey":[]}],"components":{"securitySchemes":{"AppAPIKey":{"type":"apiKey","in":"header","name":"x-app-api-key","description":"App API key in format: <key>"}},"schemas":{"CreateAppRequest":{"type":"object","required":["name","description"],"properties":{"name":{"type":"string","minLength":1,"maxLength":100,"description":"App name"},"description":{"type":"string","minLength":1,"maxLength":500,"description":"App description"},"imageUrl":{"type":"string","format":"uri","description":"Optional image URL"},"visibility":{"type":"string","enum":["PUBLIC","PRIVATE"],"default":"PRIVATE","description":"App visibility setting. PUBLIC apps are visible in public listings, PRIVATE apps are only accessible to the owner."}}},"AppSchema":{"type":"object","required":["id","name","description","apiKey","createdAt","updatedAt","visibility"],"properties":{"id":{"type":"string","description":"Unique app identifier"},"name":{"type":"string","description":"App name"},"description":{"type":"string","description":"App description"},"imageUrl":{"type":"string","nullable":true,"description":"App image URL"},"apiKey":{"type":"string","description":"App API key"},"visibility":{"type":"string","enum":["PUBLIC","PRIVATE"],"description":"App visibility setting"},"createdAt":{"type":"string","format":"date-time","description":"Creation timestamp"},"updatedAt":{"type":"string","format":"date-time","description":"Last update timestamp"}}},"ErrorResponse":{"type":"object","required":["error"],"properties":{"error":{"oneOf":[{"type":"string","description":"Error message"},{"type":"array","items":{"type":"object","required":["code","message","path"],"properties":{"code":{"type":"string","description":"Error code"},"message":{"type":"string","description":"Error message"},"path":{"type":"array","items":{"oneOf":[{"type":"string"},{"type":"number"}]},"description":"Error path"}}}}]}}}}},"paths":{"/apps":{"post":{"summary":"Create a new app","description":"Create a new app for the authenticated user","tags":["Apps"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAppRequest"}}}},"responses":{"201":{"description":"App created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppSchema"}}}},"400":{"description":"Invalid request data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized - invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}}}
```


# Players

## Get all players

> Retrieve all players for the authenticated app

```json
{"openapi":"3.0.0","info":{"title":"Meshmap API v1","version":"1.0.0"},"servers":[{"url":"/api/v1","description":"API v1 base URL"}],"security":[{"AppAPIKey":[]}],"components":{"securitySchemes":{"AppAPIKey":{"type":"apiKey","in":"header","name":"x-app-api-key","description":"App API key in format: <key>"}},"schemas":{"GetPlayersListResponse":{"type":"object","required":["players"],"properties":{"players":{"type":"array","items":{"$ref":"#/components/schemas/PlayerSchema"}}}},"PlayerSchema":{"type":"object","required":["id","displayName","createdAt","updatedAt"],"properties":{"id":{"type":"string","description":"Unique player identifier"},"displayName":{"type":"string","description":"Player display name"},"userId":{"type":"string","nullable":true,"description":"Associated user ID"},"imageUrl":{"type":"string","nullable":true,"description":"URL to player's profile image"},"createdAt":{"type":"string","format":"date-time","description":"Creation timestamp"},"updatedAt":{"type":"string","format":"date-time","description":"Last update timestamp"}}},"ErrorResponse":{"type":"object","required":["error"],"properties":{"error":{"oneOf":[{"type":"string","description":"Error message"},{"type":"array","items":{"type":"object","required":["code","message","path"],"properties":{"code":{"type":"string","description":"Error code"},"message":{"type":"string","description":"Error message"},"path":{"type":"array","items":{"oneOf":[{"type":"string"},{"type":"number"}]},"description":"Error path"}}}}]}}}}},"paths":{"/apps/players":{"get":{"summary":"Get all players","description":"Retrieve all players for the authenticated app","tags":["Players"],"responses":{"200":{"description":"Players retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetPlayersListResponse"}}}},"401":{"description":"Unauthorized - invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}}}
```

## Create a new player

> Create a new player for the authenticated app. Players with duplicate names are not allowed for the same app.

```json
{"openapi":"3.0.0","info":{"title":"Meshmap API v1","version":"1.0.0"},"servers":[{"url":"/api/v1","description":"API v1 base URL"}],"security":[{"AppAPIKey":[]}],"components":{"securitySchemes":{"AppAPIKey":{"type":"apiKey","in":"header","name":"x-app-api-key","description":"App API key in format: <key>"}},"schemas":{"CreatePlayerRequest":{"type":"object","required":["displayName"],"properties":{"userId":{"type":"string","nullable":true,"description":"Optional user ID"},"displayName":{"type":"string","description":"Player display name"}}},"PlayerSchema":{"type":"object","required":["id","displayName","createdAt","updatedAt"],"properties":{"id":{"type":"string","description":"Unique player identifier"},"displayName":{"type":"string","description":"Player display name"},"userId":{"type":"string","nullable":true,"description":"Associated user ID"},"imageUrl":{"type":"string","nullable":true,"description":"URL to player's profile image"},"createdAt":{"type":"string","format":"date-time","description":"Creation timestamp"},"updatedAt":{"type":"string","format":"date-time","description":"Last update timestamp"}}},"ErrorResponse":{"type":"object","required":["error"],"properties":{"error":{"oneOf":[{"type":"string","description":"Error message"},{"type":"array","items":{"type":"object","required":["code","message","path"],"properties":{"code":{"type":"string","description":"Error code"},"message":{"type":"string","description":"Error message"},"path":{"type":"array","items":{"oneOf":[{"type":"string"},{"type":"number"}]},"description":"Error path"}}}}]}}}}},"paths":{"/apps/players":{"post":{"summary":"Create a new player","description":"Create a new player for the authenticated app. Players with duplicate names are not allowed for the same app.","tags":["Players"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatePlayerRequest"}}}},"responses":{"201":{"description":"Player created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlayerSchema"}}}},"400":{"description":"Invalid request data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized - invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}}}
```

## Delete player profile image

> Remove the profile image for a player

```json
{"openapi":"3.0.0","info":{"title":"Meshmap API v1","version":"1.0.0"},"servers":[{"url":"/api/v1","description":"API v1 base URL"}],"security":[{"AppAPIKey":[]}],"components":{"securitySchemes":{"AppAPIKey":{"type":"apiKey","in":"header","name":"x-app-api-key","description":"App API key in format: <key>"}},"schemas":{"ErrorResponse":{"type":"object","required":["error"],"properties":{"error":{"oneOf":[{"type":"string","description":"Error message"},{"type":"array","items":{"type":"object","required":["code","message","path"],"properties":{"code":{"type":"string","description":"Error code"},"message":{"type":"string","description":"Error message"},"path":{"type":"array","items":{"oneOf":[{"type":"string"},{"type":"number"}]},"description":"Error path"}}}}]}}}}},"paths":{"/apps/players/{id}/profile-image":{"delete":{"summary":"Delete player profile image","description":"Remove the profile image for a player","tags":["Players"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Player ID"}],"responses":{"200":{"description":"Profile image deleted successfully","content":{"application/json":{"schema":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean"}}}}}},"401":{"description":"Unauthorized - invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Player not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}}}
```

## Upload or update player profile image

> Upload or update a player's profile image. The image must meet specific requirements:\
> \- Minimum dimensions: 512x512 pixels\
> \- Maximum dimensions: 1024x1024 pixels (larger images will be automatically resized)\
> \- Maximum file size: 5MB\
> \- Supported formats: JPEG, PNG, GIF, WebP\
> \- Square aspect ratio recommended for best results

```json
{"openapi":"3.0.0","info":{"title":"Meshmap API v1","version":"1.0.0"},"servers":[{"url":"/api/v1","description":"API v1 base URL"}],"security":[{"AppAPIKey":[]}],"components":{"securitySchemes":{"AppAPIKey":{"type":"apiKey","in":"header","name":"x-app-api-key","description":"App API key in format: <key>"}},"schemas":{"ErrorResponse":{"type":"object","required":["error"],"properties":{"error":{"oneOf":[{"type":"string","description":"Error message"},{"type":"array","items":{"type":"object","required":["code","message","path"],"properties":{"code":{"type":"string","description":"Error code"},"message":{"type":"string","description":"Error message"},"path":{"type":"array","items":{"oneOf":[{"type":"string"},{"type":"number"}]},"description":"Error path"}}}}]}}}}},"paths":{"/apps/players/{id}/profile-image":{"patch":{"summary":"Upload or update player profile image","description":"Upload or update a player's profile image. The image must meet specific requirements:\n- Minimum dimensions: 512x512 pixels\n- Maximum dimensions: 1024x1024 pixels (larger images will be automatically resized)\n- Maximum file size: 5MB\n- Supported formats: JPEG, PNG, GIF, WebP\n- Square aspect ratio recommended for best results","tags":["Players"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Player ID"}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["image"],"properties":{"image":{"type":"string","format":"binary","description":"Profile image file"}}}}}},"responses":{"200":{"description":"Profile image updated successfully","content":{"application/json":{"schema":{"type":"object","required":["imageUrl"],"properties":{"imageUrl":{"type":"string","description":"URL of the uploaded image"}}}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Error message"}}}}}},"401":{"description":"Unauthorized - invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Player not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}}}
```

## Get a player by ID

> Retrieve a specific player by their ID. Optionally include global rankings across all mini-game types.

```json
{"openapi":"3.0.0","info":{"title":"Meshmap API v1","version":"1.0.0"},"servers":[{"url":"/api/v1","description":"API v1 base URL"}],"security":[{"AppAPIKey":[]}],"components":{"securitySchemes":{"AppAPIKey":{"type":"apiKey","in":"header","name":"x-app-api-key","description":"App API key in format: <key>"}},"schemas":{"PlayerSchema":{"type":"object","required":["id","displayName","createdAt","updatedAt"],"properties":{"id":{"type":"string","description":"Unique player identifier"},"displayName":{"type":"string","description":"Player display name"},"userId":{"type":"string","nullable":true,"description":"Associated user ID"},"imageUrl":{"type":"string","nullable":true,"description":"URL to player's profile image"},"createdAt":{"type":"string","format":"date-time","description":"Creation timestamp"},"updatedAt":{"type":"string","format":"date-time","description":"Last update timestamp"}}},"PlayerWithGlobalRankingsSchema":{"type":"object","required":["id","displayName","createdAt","updatedAt","globalRankings"],"properties":{"id":{"type":"string","description":"Unique player identifier"},"displayName":{"type":"string","description":"Player display name"},"userId":{"type":"string","nullable":true,"description":"Associated user ID"},"imageUrl":{"type":"string","nullable":true,"description":"URL to player's profile image"},"createdAt":{"type":"string","format":"date-time","description":"Creation timestamp"},"updatedAt":{"type":"string","format":"date-time","description":"Last update timestamp"},"globalRankings":{"type":"array","items":{"$ref":"#/components/schemas/GlobalRankingSchema"},"description":"Array of global rankings for each mini-game type the player has participated in"},"appName":{"type":"string","description":"Name of the app the player belongs to (optional)"}}},"GlobalRankingSchema":{"type":"object","required":["highestScore","ranking"],"properties":{"highestScore":{"type":"number","description":"Player's highest score for this mini-game type"},"ranking":{"type":"integer","minimum":1,"description":"Player's global ranking for this mini-game type"},"miniGameTypeName":{"type":"string","nullable":true,"description":"Mini game type name (null for default game type)"},"miniGameTypeId":{"type":"string","nullable":true,"description":"Mini game type ID (null for default game type)"}}},"ErrorResponse":{"type":"object","required":["error"],"properties":{"error":{"oneOf":[{"type":"string","description":"Error message"},{"type":"array","items":{"type":"object","required":["code","message","path"],"properties":{"code":{"type":"string","description":"Error code"},"message":{"type":"string","description":"Error message"},"path":{"type":"array","items":{"oneOf":[{"type":"string"},{"type":"number"}]},"description":"Error path"}}}}]}}}}},"paths":{"/apps/players/{id}":{"get":{"summary":"Get a player by ID","description":"Retrieve a specific player by their ID. Optionally include global rankings across all mini-game types.","tags":["Players"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Player ID"},{"name":"includeGlobalRankings","in":"query","required":false,"schema":{"type":"boolean","default":false},"description":"If true, includes the player's global rankings for all mini-game types they have participated in"}],"responses":{"200":{"description":"Player retrieved successfully","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/PlayerSchema"},{"$ref":"#/components/schemas/PlayerWithGlobalRankingsSchema"}]}}}},"401":{"description":"Unauthorized - invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Player not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}}}
```

## Update a player

> Update a player's information (display name and user association)

```json
{"openapi":"3.0.0","info":{"title":"Meshmap API v1","version":"1.0.0"},"servers":[{"url":"/api/v1","description":"API v1 base URL"}],"security":[{"AppAPIKey":[]}],"components":{"securitySchemes":{"AppAPIKey":{"type":"apiKey","in":"header","name":"x-app-api-key","description":"App API key in format: <key>"}},"schemas":{"CreatePlayerRequest":{"type":"object","required":["displayName"],"properties":{"userId":{"type":"string","nullable":true,"description":"Optional user ID"},"displayName":{"type":"string","description":"Player display name"}}},"PlayerSchema":{"type":"object","required":["id","displayName","createdAt","updatedAt"],"properties":{"id":{"type":"string","description":"Unique player identifier"},"displayName":{"type":"string","description":"Player display name"},"userId":{"type":"string","nullable":true,"description":"Associated user ID"},"imageUrl":{"type":"string","nullable":true,"description":"URL to player's profile image"},"createdAt":{"type":"string","format":"date-time","description":"Creation timestamp"},"updatedAt":{"type":"string","format":"date-time","description":"Last update timestamp"}}},"ErrorResponse":{"type":"object","required":["error"],"properties":{"error":{"oneOf":[{"type":"string","description":"Error message"},{"type":"array","items":{"type":"object","required":["code","message","path"],"properties":{"code":{"type":"string","description":"Error code"},"message":{"type":"string","description":"Error message"},"path":{"type":"array","items":{"oneOf":[{"type":"string"},{"type":"number"}]},"description":"Error path"}}}}]}}}}},"paths":{"/apps/players/{id}":{"patch":{"summary":"Update a player","description":"Update a player's information (display name and user association)","tags":["Players"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Player ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatePlayerRequest"}}}},"responses":{"200":{"description":"Player updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlayerSchema"}}}},"400":{"description":"Invalid request data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized - invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden - can only update players belonging to your app","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Player not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}}}
```

## Get player's global ranking

> Get a player's global ranking across all default games or all games for a specific mini game type

```json
{"openapi":"3.0.0","info":{"title":"Meshmap API v1","version":"1.0.0"},"servers":[{"url":"/api/v1","description":"API v1 base URL"}],"security":[{"AppAPIKey":[]}],"components":{"securitySchemes":{"AppAPIKey":{"type":"apiKey","in":"header","name":"x-app-api-key","description":"App API key in format: <key>"}},"schemas":{"PlayerRankingResponse":{"type":"object","required":["highestScore","ranking"],"properties":{"highestScore":{"type":"number","description":"Player's highest score"},"ranking":{"type":"integer","minimum":1,"description":"Player's global ranking"},"miniGameTypeId":{"type":"string","nullable":true,"description":"Mini game type ID (null for default game type)"},"miniGameTypeName":{"type":"string","nullable":true,"description":"Mini game type name (null for default game type)"}}},"ErrorResponse":{"type":"object","required":["error"],"properties":{"error":{"oneOf":[{"type":"string","description":"Error message"},{"type":"array","items":{"type":"object","required":["code","message","path"],"properties":{"code":{"type":"string","description":"Error code"},"message":{"type":"string","description":"Error message"},"path":{"type":"array","items":{"oneOf":[{"type":"string"},{"type":"number"}]},"description":"Error path"}}}}]}}}}},"paths":{"/apps/players/{playerId}/global-ranking":{"get":{"summary":"Get player's global ranking","description":"Get a player's global ranking across all default games or all games for a specific mini game type","tags":["Players"],"parameters":[{"name":"playerId","in":"path","required":true,"schema":{"type":"string"},"description":"Player ID"},{"name":"miniGameTypeId","in":"query","required":false,"schema":{"type":"string"},"description":"Optional mini game type ID to get ranking for a specific game type. If not provided, the ranking for all default games will be returned."},{"name":"miniGameTypeName","in":"query","required":false,"schema":{"type":"string"},"description":"Optional mini game type name to get ranking for a specific game type. If not provided, the ranking for all default games will be returned."}],"responses":{"200":{"description":"Player ranking retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlayerRankingResponse"}}}},"401":{"description":"Unauthorized - invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Player not found or has no ranking","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}}}
```


# Game Sessions

## Get all game sessions of an app

> Retrieve all game sessions for the authenticated app

```json
{"openapi":"3.0.0","info":{"title":"Meshmap API v1","version":"1.0.0"},"servers":[{"url":"/api/v1","description":"API v1 base URL"}],"security":[{"AppAPIKey":[]}],"components":{"securitySchemes":{"AppAPIKey":{"type":"apiKey","in":"header","name":"x-app-api-key","description":"App API key in format: <key>"}},"schemas":{"GameSessionsSchemaListResponse":{"type":"object","required":["gameSessions"],"properties":{"gameSessions":{"type":"array","items":{"$ref":"#/components/schemas/GameSessionSchema"}}}},"GameSessionSchema":{"type":"object","required":["id","status","startTime","createdAt","updatedAt","playerCount"],"properties":{"id":{"type":"string","description":"Unique game session identifier"},"name":{"type":"string","nullable":true,"description":"Game session name"},"description":{"type":"string","nullable":true,"description":"Game session description"},"status":{"type":"string","enum":["ACTIVE","COMPLETED"],"description":"Game session status"},"startTime":{"type":"string","format":"date-time","description":"Game session start time"},"endTime":{"type":"string","format":"date-time","nullable":true,"description":"Game session end time"},"createdAt":{"type":"string","format":"date-time","description":"Creation timestamp"},"updatedAt":{"type":"string","format":"date-time","description":"Last update timestamp"},"miniGameType":{"allOf":[{"$ref":"#/components/schemas/MiniGameTypeSchema"}],"nullable":true,"description":"Associated mini game type"},"playerCount":{"type":"number","default":0,"description":"Number of players in the game session"}}},"MiniGameTypeSchema":{"type":"object","required":["id","name","description","createdAt"],"properties":{"id":{"type":"string","description":"Unique mini game type identifier"},"name":{"type":"string","description":"Mini game type name"},"description":{"type":"string","description":"Mini game type description"},"imageUrl":{"type":"string","nullable":true,"description":"Mini game type image URL"},"createdAt":{"type":"string","format":"date-time","description":"Creation timestamp"}}},"ErrorResponse":{"type":"object","required":["error"],"properties":{"error":{"oneOf":[{"type":"string","description":"Error message"},{"type":"array","items":{"type":"object","required":["code","message","path"],"properties":{"code":{"type":"string","description":"Error code"},"message":{"type":"string","description":"Error message"},"path":{"type":"array","items":{"oneOf":[{"type":"string"},{"type":"number"}]},"description":"Error path"}}}}]}}}}},"paths":{"/apps/game-sessions":{"get":{"summary":"Get all game sessions of an app","description":"Retrieve all game sessions for the authenticated app","tags":["Game Sessions"],"responses":{"200":{"description":"Game sessions retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GameSessionsSchemaListResponse"}}}},"401":{"description":"Unauthorized - invalid or missing session token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}}}
```

## Create a new game session

> Create a new game session for the authenticated app. Optionally specify a mini game type name or ID to associate with the game session. Cannot be both name and id.

```json
{"openapi":"3.0.0","info":{"title":"Meshmap API v1","version":"1.0.0"},"servers":[{"url":"/api/v1","description":"API v1 base URL"}],"security":[{"AppAPIKey":[]}],"components":{"securitySchemes":{"AppAPIKey":{"type":"apiKey","in":"header","name":"x-app-api-key","description":"App API key in format: <key>"}},"schemas":{"CreateGameSessionSchema":{"type":"object","properties":{"name":{"type":"string","nullable":true,"description":"Game session name"},"description":{"type":"string","nullable":true,"description":"Game session description"},"status":{"type":"string","enum":["ACTIVE","COMPLETED","PAUSED"],"default":"ACTIVE","description":"Game session status"},"miniGameTypeId":{"type":"string","nullable":true,"description":"Optional mini game type ID to associate with this game session."},"miniGameTypeName":{"type":"string","nullable":true,"description":"Optional mini game type name to associate with this game session."}}},"GameSessionSchema":{"type":"object","required":["id","status","startTime","createdAt","updatedAt","playerCount"],"properties":{"id":{"type":"string","description":"Unique game session identifier"},"name":{"type":"string","nullable":true,"description":"Game session name"},"description":{"type":"string","nullable":true,"description":"Game session description"},"status":{"type":"string","enum":["ACTIVE","COMPLETED"],"description":"Game session status"},"startTime":{"type":"string","format":"date-time","description":"Game session start time"},"endTime":{"type":"string","format":"date-time","nullable":true,"description":"Game session end time"},"createdAt":{"type":"string","format":"date-time","description":"Creation timestamp"},"updatedAt":{"type":"string","format":"date-time","description":"Last update timestamp"},"miniGameType":{"allOf":[{"$ref":"#/components/schemas/MiniGameTypeSchema"}],"nullable":true,"description":"Associated mini game type"},"playerCount":{"type":"number","default":0,"description":"Number of players in the game session"}}},"MiniGameTypeSchema":{"type":"object","required":["id","name","description","createdAt"],"properties":{"id":{"type":"string","description":"Unique mini game type identifier"},"name":{"type":"string","description":"Mini game type name"},"description":{"type":"string","description":"Mini game type description"},"imageUrl":{"type":"string","nullable":true,"description":"Mini game type image URL"},"createdAt":{"type":"string","format":"date-time","description":"Creation timestamp"}}},"ErrorResponse":{"type":"object","required":["error"],"properties":{"error":{"oneOf":[{"type":"string","description":"Error message"},{"type":"array","items":{"type":"object","required":["code","message","path"],"properties":{"code":{"type":"string","description":"Error code"},"message":{"type":"string","description":"Error message"},"path":{"type":"array","items":{"oneOf":[{"type":"string"},{"type":"number"}]},"description":"Error path"}}}}]}}}}},"paths":{"/apps/game-sessions":{"post":{"summary":"Create a new game session","description":"Create a new game session for the authenticated app. Optionally specify a mini game type name or ID to associate with the game session. Cannot be both name and id.","tags":["Game Sessions"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateGameSessionSchema"}}}},"responses":{"201":{"description":"Game session created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GameSessionSchema"}}}},"400":{"description":"Invalid request data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized - invalid or missing session token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}}}
```

## Get a game session by ID

> Retrieve a specific game session by its ID

```json
{"openapi":"3.0.0","info":{"title":"Meshmap API v1","version":"1.0.0"},"servers":[{"url":"/api/v1","description":"API v1 base URL"}],"security":[{"AppAPIKey":[]}],"components":{"securitySchemes":{"AppAPIKey":{"type":"apiKey","in":"header","name":"x-app-api-key","description":"App API key in format: <key>"}},"schemas":{"GameSessionSchema":{"type":"object","required":["id","status","startTime","createdAt","updatedAt","playerCount"],"properties":{"id":{"type":"string","description":"Unique game session identifier"},"name":{"type":"string","nullable":true,"description":"Game session name"},"description":{"type":"string","nullable":true,"description":"Game session description"},"status":{"type":"string","enum":["ACTIVE","COMPLETED"],"description":"Game session status"},"startTime":{"type":"string","format":"date-time","description":"Game session start time"},"endTime":{"type":"string","format":"date-time","nullable":true,"description":"Game session end time"},"createdAt":{"type":"string","format":"date-time","description":"Creation timestamp"},"updatedAt":{"type":"string","format":"date-time","description":"Last update timestamp"},"miniGameType":{"allOf":[{"$ref":"#/components/schemas/MiniGameTypeSchema"}],"nullable":true,"description":"Associated mini game type"},"playerCount":{"type":"number","default":0,"description":"Number of players in the game session"}}},"MiniGameTypeSchema":{"type":"object","required":["id","name","description","createdAt"],"properties":{"id":{"type":"string","description":"Unique mini game type identifier"},"name":{"type":"string","description":"Mini game type name"},"description":{"type":"string","description":"Mini game type description"},"imageUrl":{"type":"string","nullable":true,"description":"Mini game type image URL"},"createdAt":{"type":"string","format":"date-time","description":"Creation timestamp"}}},"ErrorResponse":{"type":"object","required":["error"],"properties":{"error":{"oneOf":[{"type":"string","description":"Error message"},{"type":"array","items":{"type":"object","required":["code","message","path"],"properties":{"code":{"type":"string","description":"Error code"},"message":{"type":"string","description":"Error message"},"path":{"type":"array","items":{"oneOf":[{"type":"string"},{"type":"number"}]},"description":"Error path"}}}}]}}}}},"paths":{"/apps/game-sessions/{sessionId}":{"get":{"summary":"Get a game session by ID","description":"Retrieve a specific game session by its ID","tags":["Game Sessions"],"parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string"},"description":"Game session ID"}],"responses":{"200":{"description":"Game session retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GameSessionSchema"}}}},"401":{"description":"Unauthorized - invalid or missing session token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Game session not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}}}
```

## Get all players in a game session

> Retrieve all players registered to a specific game session

```json
{"openapi":"3.0.0","info":{"title":"Meshmap API v1","version":"1.0.0"},"servers":[{"url":"/api/v1","description":"API v1 base URL"}],"security":[{"AppAPIKey":[]}],"components":{"securitySchemes":{"AppAPIKey":{"type":"apiKey","in":"header","name":"x-app-api-key","description":"App API key in format: <key>"}},"schemas":{"GameSessionPlayersListResponse":{"type":"object","required":["players"],"properties":{"players":{"type":"array","items":{"$ref":"#/components/schemas/GameSessionPlayerSchema"}}}},"GameSessionPlayerSchema":{"type":"object","required":["score","createdAt","updatedAt","playerId","displayName"],"properties":{"score":{"type":"number","description":"Player score"},"createdAt":{"type":"string","format":"date-time","description":"Creation timestamp"},"updatedAt":{"type":"string","format":"date-time","description":"Last update timestamp"},"playerId":{"type":"string","description":"Associated player ID"},"displayName":{"type":"string","description":"Player display name"},"imageUrl":{"type":"string","nullable":true,"description":"URL to player's profile image"},"user":{"type":"object","nullable":true,"description":"Associated user information","properties":{"id":{"type":"string","description":"User ID"},"username":{"type":"string","nullable":true,"description":"User's username"}}}}},"ErrorResponse":{"type":"object","required":["error"],"properties":{"error":{"oneOf":[{"type":"string","description":"Error message"},{"type":"array","items":{"type":"object","required":["code","message","path"],"properties":{"code":{"type":"string","description":"Error code"},"message":{"type":"string","description":"Error message"},"path":{"type":"array","items":{"oneOf":[{"type":"string"},{"type":"number"}]},"description":"Error path"}}}}]}}}}},"paths":{"/apps/game-sessions/{sessionId}/players":{"get":{"summary":"Get all players in a game session","description":"Retrieve all players registered to a specific game session","tags":["Game Sessions"],"parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string"},"description":"Game session ID"}],"responses":{"200":{"description":"Game session players retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GameSessionPlayersListResponse"}}}},"401":{"description":"Unauthorized - invalid or missing session token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Game session not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}}}
```

## Register a player to a game session

> Register an existing player to a specific game session

```json
{"openapi":"3.0.0","info":{"title":"Meshmap API v1","version":"1.0.0"},"servers":[{"url":"/api/v1","description":"API v1 base URL"}],"security":[{"AppAPIKey":[]}],"components":{"securitySchemes":{"AppAPIKey":{"type":"apiKey","in":"header","name":"x-app-api-key","description":"App API key in format: <key>"}},"schemas":{"RegisterPlayerToSessionRequest":{"type":"object","required":["playerId"],"properties":{"playerId":{"type":"string","description":"Player ID to register"}}},"GameSessionPlayerSchema":{"type":"object","required":["score","createdAt","updatedAt","playerId","displayName"],"properties":{"score":{"type":"number","description":"Player score"},"createdAt":{"type":"string","format":"date-time","description":"Creation timestamp"},"updatedAt":{"type":"string","format":"date-time","description":"Last update timestamp"},"playerId":{"type":"string","description":"Associated player ID"},"displayName":{"type":"string","description":"Player display name"},"imageUrl":{"type":"string","nullable":true,"description":"URL to player's profile image"},"user":{"type":"object","nullable":true,"description":"Associated user information","properties":{"id":{"type":"string","description":"User ID"},"username":{"type":"string","nullable":true,"description":"User's username"}}}}},"ErrorResponse":{"type":"object","required":["error"],"properties":{"error":{"oneOf":[{"type":"string","description":"Error message"},{"type":"array","items":{"type":"object","required":["code","message","path"],"properties":{"code":{"type":"string","description":"Error code"},"message":{"type":"string","description":"Error message"},"path":{"type":"array","items":{"oneOf":[{"type":"string"},{"type":"number"}]},"description":"Error path"}}}}]}}}}},"paths":{"/apps/game-sessions/{sessionId}/players":{"post":{"summary":"Register a player to a game session","description":"Register an existing player to a specific game session","tags":["Game Sessions"],"parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string"},"description":"Game session ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterPlayerToSessionRequest"}}}},"responses":{"201":{"description":"Player registered to game session successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GameSessionPlayerSchema"}}}},"400":{"description":"Invalid request data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized - invalid or missing session token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}}}
```

## Update a player's score in a game session

> Update the score of a specific player in a game session

```json
{"openapi":"3.0.0","info":{"title":"Meshmap API v1","version":"1.0.0"},"servers":[{"url":"/api/v1","description":"API v1 base URL"}],"security":[{"AppAPIKey":[]}],"components":{"securitySchemes":{"AppAPIKey":{"type":"apiKey","in":"header","name":"x-app-api-key","description":"App API key in format: <key>"}},"schemas":{"UpdateGameSessionPlayerRequest":{"type":"object","required":["score"],"properties":{"score":{"type":"number","description":"Player score"}}},"GameSessionPlayerSchema":{"type":"object","required":["score","createdAt","updatedAt","playerId","displayName"],"properties":{"score":{"type":"number","description":"Player score"},"createdAt":{"type":"string","format":"date-time","description":"Creation timestamp"},"updatedAt":{"type":"string","format":"date-time","description":"Last update timestamp"},"playerId":{"type":"string","description":"Associated player ID"},"displayName":{"type":"string","description":"Player display name"},"imageUrl":{"type":"string","nullable":true,"description":"URL to player's profile image"},"user":{"type":"object","nullable":true,"description":"Associated user information","properties":{"id":{"type":"string","description":"User ID"},"username":{"type":"string","nullable":true,"description":"User's username"}}}}},"ErrorResponse":{"type":"object","required":["error"],"properties":{"error":{"oneOf":[{"type":"string","description":"Error message"},{"type":"array","items":{"type":"object","required":["code","message","path"],"properties":{"code":{"type":"string","description":"Error code"},"message":{"type":"string","description":"Error message"},"path":{"type":"array","items":{"oneOf":[{"type":"string"},{"type":"number"}]},"description":"Error path"}}}}]}}}}},"paths":{"/apps/game-sessions/{sessionId}/players/{playerId}":{"patch":{"summary":"Update a player's score in a game session","description":"Update the score of a specific player in a game session","tags":["Game Sessions"],"parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string"},"description":"Game session ID"},{"name":"playerId","in":"path","required":true,"schema":{"type":"string"},"description":"Player ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateGameSessionPlayerRequest"}}}},"responses":{"200":{"description":"Player score updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GameSessionPlayerSchema"}}}},"400":{"description":"Invalid request data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized - invalid or missing session token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Game session or player not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}}}
```


# Mini Game Types

## Get all mini game types

> Retrieve all mini game types for the authenticated app. Optionally filter by name.

```json
{"openapi":"3.0.0","info":{"title":"Meshmap API v1","version":"1.0.0"},"servers":[{"url":"/api/v1","description":"API v1 base URL"}],"security":[{"AppAPIKey":[]}],"components":{"securitySchemes":{"AppAPIKey":{"type":"apiKey","in":"header","name":"x-app-api-key","description":"App API key in format: <key>"}},"schemas":{"GetMiniGameTypesList":{"type":"object","required":["miniGameTypes"],"properties":{"miniGameTypes":{"type":"array","items":{"$ref":"#/components/schemas/MiniGameTypeSchema"}}}},"MiniGameTypeSchema":{"type":"object","required":["id","name","description","createdAt"],"properties":{"id":{"type":"string","description":"Unique mini game type identifier"},"name":{"type":"string","description":"Mini game type name"},"description":{"type":"string","description":"Mini game type description"},"imageUrl":{"type":"string","nullable":true,"description":"Mini game type image URL"},"createdAt":{"type":"string","format":"date-time","description":"Creation timestamp"}}},"ErrorResponse":{"type":"object","required":["error"],"properties":{"error":{"oneOf":[{"type":"string","description":"Error message"},{"type":"array","items":{"type":"object","required":["code","message","path"],"properties":{"code":{"type":"string","description":"Error code"},"message":{"type":"string","description":"Error message"},"path":{"type":"array","items":{"oneOf":[{"type":"string"},{"type":"number"}]},"description":"Error path"}}}}]}}}}},"paths":{"/apps/minigame-types":{"get":{"summary":"Get all mini game types","description":"Retrieve all mini game types for the authenticated app. Optionally filter by name.","tags":["Mini Game Types"],"parameters":[{"name":"miniGameTypeName","in":"query","required":false,"schema":{"type":"string"},"description":"Filter by mini game type name"}],"responses":{"200":{"description":"Mini game types retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetMiniGameTypesList"}}}},"401":{"description":"Unauthorized - invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}}}
```

## Create a new mini game type

> Create a new mini game type for the authenticated app. Mini game type names must be unique per app.

```json
{"openapi":"3.0.0","info":{"title":"Meshmap API v1","version":"1.0.0"},"servers":[{"url":"/api/v1","description":"API v1 base URL"}],"security":[{"AppAPIKey":[]}],"components":{"securitySchemes":{"AppAPIKey":{"type":"apiKey","in":"header","name":"x-app-api-key","description":"App API key in format: <key>"}},"schemas":{"CreateMiniGameTypeRequest":{"type":"object","required":["name"],"properties":{"name":{"type":"string","description":"Mini game type name (must be unique per app)"},"description":{"type":"string","description":"Mini game type description","default":""},"imageUrl":{"type":"string","format":"uri","nullable":true,"description":"Optional image URL for the mini game type"}}},"MiniGameTypeSchema":{"type":"object","required":["id","name","description","createdAt"],"properties":{"id":{"type":"string","description":"Unique mini game type identifier"},"name":{"type":"string","description":"Mini game type name"},"description":{"type":"string","description":"Mini game type description"},"imageUrl":{"type":"string","nullable":true,"description":"Mini game type image URL"},"createdAt":{"type":"string","format":"date-time","description":"Creation timestamp"}}},"ErrorResponse":{"type":"object","required":["error"],"properties":{"error":{"oneOf":[{"type":"string","description":"Error message"},{"type":"array","items":{"type":"object","required":["code","message","path"],"properties":{"code":{"type":"string","description":"Error code"},"message":{"type":"string","description":"Error message"},"path":{"type":"array","items":{"oneOf":[{"type":"string"},{"type":"number"}]},"description":"Error path"}}}}]}}}}},"paths":{"/apps/minigame-types":{"post":{"summary":"Create a new mini game type","description":"Create a new mini game type for the authenticated app. Mini game type names must be unique per app.","tags":["Mini Game Types"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateMiniGameTypeRequest"}}}},"responses":{"201":{"description":"Mini game type created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MiniGameTypeSchema"}}}},"400":{"description":"Invalid request data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized - invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}}}
```


# Leaderboards

## Get app global leaderboards

> Get leaderboards for all mini game types and default games for the authenticated app. Optionally filter by a specific mini game type ID. For games without a mini game type, the miniGameTypeName is not present.

```json
{"openapi":"3.0.0","info":{"title":"Meshmap API v1","version":"1.0.0"},"servers":[{"url":"/api/v1","description":"API v1 base URL"}],"security":[{"AppAPIKey":[]}],"components":{"securitySchemes":{"AppAPIKey":{"type":"apiKey","in":"header","name":"x-app-api-key","description":"App API key in format: <key>"}},"schemas":{"GlobalLeaderboardSchema":{"type":"object","required":["leaderboards"],"properties":{"leaderboards":{"type":"array","items":{"$ref":"#/components/schemas/MiniGameLeaderboardSchema"},"nullable":true,"description":"List of leaderboards for each mini game type and default. Each leaderboard includes player profile images and additional player details."}}},"MiniGameLeaderboardSchema":{"type":"object","properties":{"miniGameTypeId":{"type":"string","nullable":true,"description":"Mini game type ID (null for default leaderboard)"},"miniGameTypeName":{"type":"string","nullable":true,"description":"Mini game type name (null for default leaderboard)"},"leaderboard":{"type":"array","items":{"$ref":"#/components/schemas/LeaderboardPlayerSchema"},"nullable":true,"description":"List of top players for this mini game type"}}},"LeaderboardPlayerSchema":{"type":"object","required":["playerId","playerDisplayName","playerScore","createdAt","updatedAt","gameSessionId"],"properties":{"playerId":{"type":"string","description":"Player ID"},"playerDisplayName":{"type":"string","description":"Player display name"},"playerScore":{"type":"number","description":"Player score"},"playerImageUrl":{"type":"string","nullable":true,"description":"URL to player's profile image"},"createdAt":{"type":"string","format":"date-time","description":"Score creation timestamp"},"updatedAt":{"type":"string","format":"date-time","description":"Score last update timestamp"},"gameSessionId":{"type":"string","description":"Game session ID"},"userId":{"type":"string","nullable":true,"description":"Associated user ID"}}},"ErrorResponse":{"type":"object","required":["error"],"properties":{"error":{"oneOf":[{"type":"string","description":"Error message"},{"type":"array","items":{"type":"object","required":["code","message","path"],"properties":{"code":{"type":"string","description":"Error code"},"message":{"type":"string","description":"Error message"},"path":{"type":"array","items":{"oneOf":[{"type":"string"},{"type":"number"}]},"description":"Error path"}}}}]}}}}},"paths":{"/apps/global-leaderboards":{"get":{"summary":"Get app global leaderboards","description":"Get leaderboards for all mini game types and default games for the authenticated app. Optionally filter by a specific mini game type ID. For games without a mini game type, the miniGameTypeName is not present.","tags":["Leaderboards"],"parameters":[{"name":"miniGameTypeId","in":"query","required":false,"schema":{"type":"string"},"description":"Optional mini game type ID to filter leaderboards for a specific game type. If not provided, leaderboards for all game types will be returned."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":10},"description":"Maximum number of players to return per leaderboard. Defaults to 10 if not specified."}],"responses":{"200":{"description":"Leaderboards retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GlobalLeaderboardSchema"}}}},"401":{"description":"Unauthorized - invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"App not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}}}
```


# Scans

## Get user scans

> Get all scans for the authenticated user with pagination and sorting

```json
{"openapi":"3.0.0","info":{"title":"Meshmap API v1","version":"1.0.0"},"servers":[{"url":"/api/v1","description":"API v1 base URL"}],"security":[{"SessionToken":[]}],"components":{"securitySchemes":{"SessionToken":{"type":"apiKey","in":"header","name":"Authorization","description":"Session token in Bearer format: Bearer <token>"}},"schemas":{"GetUserScansResponse":{"type":"object","required":["scans","pagination"],"properties":{"scans":{"type":"array","items":{"$ref":"#/components/schemas/ScanSchema"},"description":"List of user scans"},"pagination":{"$ref":"#/components/schemas/PaginationInfo"}}},"ScanSchema":{"type":"object","required":["id","timestamp","status"],"properties":{"id":{"type":"string","description":"Unique scan identifier"},"lat":{"type":"number","nullable":true,"description":"Latitude coordinate"},"long":{"type":"number","nullable":true,"description":"Longitude coordinate"},"scanUrl":{"type":"string","nullable":true,"description":"URL to the scan file"},"timestamp":{"type":"string","format":"date-time","description":"Scan timestamp"},"status":{"type":"string","description":"Scan processing status"},"title":{"type":"string","nullable":true,"description":"Scan title"},"category":{"type":"string","nullable":true,"description":"Scan category"},"location":{"type":"string","nullable":true,"description":"Scan location description"},"fileType":{"type":"string","nullable":true,"description":"Scan file type"},"previewImageUrl":{"type":"string","nullable":true,"description":"URL to scan preview image"},"timeOfDay":{"type":"string","nullable":true,"description":"Time of day when scan was taken"},"weather":{"type":"string","nullable":true,"description":"Weather conditions during scan"}}},"PaginationInfo":{"type":"object","required":["total","page","limit","totalPages"],"properties":{"total":{"type":"integer","description":"Total number of items"},"page":{"type":"integer","description":"Current page number"},"limit":{"type":"integer","description":"Items per page"},"totalPages":{"type":"integer","description":"Total number of pages"}}},"ErrorResponse":{"type":"object","required":["error"],"properties":{"error":{"oneOf":[{"type":"string","description":"Error message"},{"type":"array","items":{"type":"object","required":["code","message","path"],"properties":{"code":{"type":"string","description":"Error code"},"message":{"type":"string","description":"Error message"},"path":{"type":"array","items":{"oneOf":[{"type":"string"},{"type":"number"}]},"description":"Error path"}}}}]}}}}},"paths":{"/user/scans":{"get":{"summary":"Get user scans","description":"Get all scans for the authenticated user with pagination and sorting","tags":["Scans"],"parameters":[{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1},"description":"Page number for pagination"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":10},"description":"Number of items per page"},{"name":"sort","in":"query","required":false,"schema":{"type":"string","enum":["asc","desc"],"default":"desc"},"description":"Sort order by timestamp"}],"responses":{"200":{"description":"User scans retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetUserScansResponse"}}}},"401":{"description":"Unauthorized - invalid or missing session token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}}}
```

## Get public scans

> Get all public scans with pagination

```json
{"openapi":"3.0.0","info":{"title":"Meshmap API v1","version":"1.0.0"},"servers":[{"url":"/api/v1","description":"API v1 base URL"}],"security":[{"AppAPIKey":[]}],"components":{"securitySchemes":{"AppAPIKey":{"type":"apiKey","in":"header","name":"x-app-api-key","description":"App API key in format: <key>"}},"schemas":{"ScanSchema":{"type":"object","required":["id","timestamp","status"],"properties":{"id":{"type":"string","description":"Unique scan identifier"},"lat":{"type":"number","nullable":true,"description":"Latitude coordinate"},"long":{"type":"number","nullable":true,"description":"Longitude coordinate"},"scanUrl":{"type":"string","nullable":true,"description":"URL to the scan file"},"timestamp":{"type":"string","format":"date-time","description":"Scan timestamp"},"status":{"type":"string","description":"Scan processing status"},"title":{"type":"string","nullable":true,"description":"Scan title"},"category":{"type":"string","nullable":true,"description":"Scan category"},"location":{"type":"string","nullable":true,"description":"Scan location description"},"fileType":{"type":"string","nullable":true,"description":"Scan file type"},"previewImageUrl":{"type":"string","nullable":true,"description":"URL to scan preview image"},"timeOfDay":{"type":"string","nullable":true,"description":"Time of day when scan was taken"},"weather":{"type":"string","nullable":true,"description":"Weather conditions during scan"}}},"PaginationInfo":{"type":"object","required":["total","page","limit","totalPages"],"properties":{"total":{"type":"integer","description":"Total number of items"},"page":{"type":"integer","description":"Current page number"},"limit":{"type":"integer","description":"Items per page"},"totalPages":{"type":"integer","description":"Total number of pages"}}},"ErrorResponse":{"type":"object","required":["error"],"properties":{"error":{"oneOf":[{"type":"string","description":"Error message"},{"type":"array","items":{"type":"object","required":["code","message","path"],"properties":{"code":{"type":"string","description":"Error code"},"message":{"type":"string","description":"Error message"},"path":{"type":"array","items":{"oneOf":[{"type":"string"},{"type":"number"}]},"description":"Error path"}}}}]}}}}},"paths":{"/scans/public-scans":{"get":{"summary":"Get public scans","description":"Get all public scans with pagination","tags":["Scans"],"parameters":[{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1},"description":"Page number for pagination"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":10},"description":"Number of items per page"}],"responses":{"200":{"description":"Public scans retrieved successfully","content":{"application/json":{"schema":{"type":"object","required":["scans","pagination"],"properties":{"scans":{"type":"array","items":{"$ref":"#/components/schemas/ScanSchema"},"description":"List of public scans"},"pagination":{"$ref":"#/components/schemas/PaginationInfo"}}}}}},"401":{"description":"Unauthorized - invalid or missing session token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}}}
```


# Models

## The PlayerRankingResponse object

```json
{"openapi":"3.0.0","info":{"title":"Meshmap API v1","version":"1.0.0"},"components":{"schemas":{"PlayerRankingResponse":{"type":"object","required":["highestScore","ranking"],"properties":{"highestScore":{"type":"number","description":"Player's highest score"},"ranking":{"type":"integer","minimum":1,"description":"Player's global ranking"},"miniGameTypeId":{"type":"string","nullable":true,"description":"Mini game type ID (null for default game type)"},"miniGameTypeName":{"type":"string","nullable":true,"description":"Mini game type name (null for default game type)"}}}}}}
```

## The CreateAppRequest object

```json
{"openapi":"3.0.0","info":{"title":"Meshmap API v1","version":"1.0.0"},"components":{"schemas":{"CreateAppRequest":{"type":"object","required":["name","description"],"properties":{"name":{"type":"string","minLength":1,"maxLength":100,"description":"App name"},"description":{"type":"string","minLength":1,"maxLength":500,"description":"App description"},"imageUrl":{"type":"string","format":"uri","description":"Optional image URL"},"visibility":{"type":"string","enum":["PUBLIC","PRIVATE"],"default":"PRIVATE","description":"App visibility setting. PUBLIC apps are visible in public listings, PRIVATE apps are only accessible to the owner."}}}}}}
```

## The AppSchema object

```json
{"openapi":"3.0.0","info":{"title":"Meshmap API v1","version":"1.0.0"},"components":{"schemas":{"AppSchema":{"type":"object","required":["id","name","description","apiKey","createdAt","updatedAt","visibility"],"properties":{"id":{"type":"string","description":"Unique app identifier"},"name":{"type":"string","description":"App name"},"description":{"type":"string","description":"App description"},"imageUrl":{"type":"string","nullable":true,"description":"App image URL"},"apiKey":{"type":"string","description":"App API key"},"visibility":{"type":"string","enum":["PUBLIC","PRIVATE"],"description":"App visibility setting"},"createdAt":{"type":"string","format":"date-time","description":"Creation timestamp"},"updatedAt":{"type":"string","format":"date-time","description":"Last update timestamp"}}}}}}
```

## The AppsListResponse object

```json
{"openapi":"3.0.0","info":{"title":"Meshmap API v1","version":"1.0.0"},"components":{"schemas":{"AppsListResponse":{"type":"object","required":["apps"],"properties":{"apps":{"type":"array","items":{"$ref":"#/components/schemas/AppSchema"}}}},"AppSchema":{"type":"object","required":["id","name","description","apiKey","createdAt","updatedAt","visibility"],"properties":{"id":{"type":"string","description":"Unique app identifier"},"name":{"type":"string","description":"App name"},"description":{"type":"string","description":"App description"},"imageUrl":{"type":"string","nullable":true,"description":"App image URL"},"apiKey":{"type":"string","description":"App API key"},"visibility":{"type":"string","enum":["PUBLIC","PRIVATE"],"description":"App visibility setting"},"createdAt":{"type":"string","format":"date-time","description":"Creation timestamp"},"updatedAt":{"type":"string","format":"date-time","description":"Last update timestamp"}}}}}}
```

## The CreatePlayerRequest object

```json
{"openapi":"3.0.0","info":{"title":"Meshmap API v1","version":"1.0.0"},"components":{"schemas":{"CreatePlayerRequest":{"type":"object","required":["displayName"],"properties":{"userId":{"type":"string","nullable":true,"description":"Optional user ID"},"displayName":{"type":"string","description":"Player display name"}}}}}}
```

## The PlayerSchema object

```json
{"openapi":"3.0.0","info":{"title":"Meshmap API v1","version":"1.0.0"},"components":{"schemas":{"PlayerSchema":{"type":"object","required":["id","displayName","createdAt","updatedAt"],"properties":{"id":{"type":"string","description":"Unique player identifier"},"displayName":{"type":"string","description":"Player display name"},"userId":{"type":"string","nullable":true,"description":"Associated user ID"},"imageUrl":{"type":"string","nullable":true,"description":"URL to player's profile image"},"createdAt":{"type":"string","format":"date-time","description":"Creation timestamp"},"updatedAt":{"type":"string","format":"date-time","description":"Last update timestamp"}}}}}}
```

## The GetPlayersListResponse object

```json
{"openapi":"3.0.0","info":{"title":"Meshmap API v1","version":"1.0.0"},"components":{"schemas":{"GetPlayersListResponse":{"type":"object","required":["players"],"properties":{"players":{"type":"array","items":{"$ref":"#/components/schemas/PlayerSchema"}}}},"PlayerSchema":{"type":"object","required":["id","displayName","createdAt","updatedAt"],"properties":{"id":{"type":"string","description":"Unique player identifier"},"displayName":{"type":"string","description":"Player display name"},"userId":{"type":"string","nullable":true,"description":"Associated user ID"},"imageUrl":{"type":"string","nullable":true,"description":"URL to player's profile image"},"createdAt":{"type":"string","format":"date-time","description":"Creation timestamp"},"updatedAt":{"type":"string","format":"date-time","description":"Last update timestamp"}}}}}}
```

## The GlobalRankingSchema object

```json
{"openapi":"3.0.0","info":{"title":"Meshmap API v1","version":"1.0.0"},"components":{"schemas":{"GlobalRankingSchema":{"type":"object","required":["highestScore","ranking"],"properties":{"highestScore":{"type":"number","description":"Player's highest score for this mini-game type"},"ranking":{"type":"integer","minimum":1,"description":"Player's global ranking for this mini-game type"},"miniGameTypeName":{"type":"string","nullable":true,"description":"Mini game type name (null for default game type)"},"miniGameTypeId":{"type":"string","nullable":true,"description":"Mini game type ID (null for default game type)"}}}}}}
```

## The PlayerWithGlobalRankingsSchema object

```json
{"openapi":"3.0.0","info":{"title":"Meshmap API v1","version":"1.0.0"},"components":{"schemas":{"PlayerWithGlobalRankingsSchema":{"type":"object","required":["id","displayName","createdAt","updatedAt","globalRankings"],"properties":{"id":{"type":"string","description":"Unique player identifier"},"displayName":{"type":"string","description":"Player display name"},"userId":{"type":"string","nullable":true,"description":"Associated user ID"},"imageUrl":{"type":"string","nullable":true,"description":"URL to player's profile image"},"createdAt":{"type":"string","format":"date-time","description":"Creation timestamp"},"updatedAt":{"type":"string","format":"date-time","description":"Last update timestamp"},"globalRankings":{"type":"array","items":{"$ref":"#/components/schemas/GlobalRankingSchema"},"description":"Array of global rankings for each mini-game type the player has participated in"},"appName":{"type":"string","description":"Name of the app the player belongs to (optional)"}}},"GlobalRankingSchema":{"type":"object","required":["highestScore","ranking"],"properties":{"highestScore":{"type":"number","description":"Player's highest score for this mini-game type"},"ranking":{"type":"integer","minimum":1,"description":"Player's global ranking for this mini-game type"},"miniGameTypeName":{"type":"string","nullable":true,"description":"Mini game type name (null for default game type)"},"miniGameTypeId":{"type":"string","nullable":true,"description":"Mini game type ID (null for default game type)"}}}}}}
```

## The CreateMiniGameTypeRequest object

```json
{"openapi":"3.0.0","info":{"title":"Meshmap API v1","version":"1.0.0"},"components":{"schemas":{"CreateMiniGameTypeRequest":{"type":"object","required":["name"],"properties":{"name":{"type":"string","description":"Mini game type name (must be unique per app)"},"description":{"type":"string","description":"Mini game type description","default":""},"imageUrl":{"type":"string","format":"uri","nullable":true,"description":"Optional image URL for the mini game type"}}}}}}
```

## The MiniGameTypeSchema object

```json
{"openapi":"3.0.0","info":{"title":"Meshmap API v1","version":"1.0.0"},"components":{"schemas":{"MiniGameTypeSchema":{"type":"object","required":["id","name","description","createdAt"],"properties":{"id":{"type":"string","description":"Unique mini game type identifier"},"name":{"type":"string","description":"Mini game type name"},"description":{"type":"string","description":"Mini game type description"},"imageUrl":{"type":"string","nullable":true,"description":"Mini game type image URL"},"createdAt":{"type":"string","format":"date-time","description":"Creation timestamp"}}}}}}
```

## The GetMiniGameTypesList object

```json
{"openapi":"3.0.0","info":{"title":"Meshmap API v1","version":"1.0.0"},"components":{"schemas":{"GetMiniGameTypesList":{"type":"object","required":["miniGameTypes"],"properties":{"miniGameTypes":{"type":"array","items":{"$ref":"#/components/schemas/MiniGameTypeSchema"}}}},"MiniGameTypeSchema":{"type":"object","required":["id","name","description","createdAt"],"properties":{"id":{"type":"string","description":"Unique mini game type identifier"},"name":{"type":"string","description":"Mini game type name"},"description":{"type":"string","description":"Mini game type description"},"imageUrl":{"type":"string","nullable":true,"description":"Mini game type image URL"},"createdAt":{"type":"string","format":"date-time","description":"Creation timestamp"}}}}}}
```

## The CreateGameSessionSchema object

```json
{"openapi":"3.0.0","info":{"title":"Meshmap API v1","version":"1.0.0"},"components":{"schemas":{"CreateGameSessionSchema":{"type":"object","properties":{"name":{"type":"string","nullable":true,"description":"Game session name"},"description":{"type":"string","nullable":true,"description":"Game session description"},"status":{"type":"string","enum":["ACTIVE","COMPLETED","PAUSED"],"default":"ACTIVE","description":"Game session status"},"miniGameTypeId":{"type":"string","nullable":true,"description":"Optional mini game type ID to associate with this game session."},"miniGameTypeName":{"type":"string","nullable":true,"description":"Optional mini game type name to associate with this game session."}}}}}}
```

## The GameSessionSchema object

```json
{"openapi":"3.0.0","info":{"title":"Meshmap API v1","version":"1.0.0"},"components":{"schemas":{"GameSessionSchema":{"type":"object","required":["id","status","startTime","createdAt","updatedAt","playerCount"],"properties":{"id":{"type":"string","description":"Unique game session identifier"},"name":{"type":"string","nullable":true,"description":"Game session name"},"description":{"type":"string","nullable":true,"description":"Game session description"},"status":{"type":"string","enum":["ACTIVE","COMPLETED"],"description":"Game session status"},"startTime":{"type":"string","format":"date-time","description":"Game session start time"},"endTime":{"type":"string","format":"date-time","nullable":true,"description":"Game session end time"},"createdAt":{"type":"string","format":"date-time","description":"Creation timestamp"},"updatedAt":{"type":"string","format":"date-time","description":"Last update timestamp"},"miniGameType":{"allOf":[{"$ref":"#/components/schemas/MiniGameTypeSchema"}],"nullable":true,"description":"Associated mini game type"},"playerCount":{"type":"number","default":0,"description":"Number of players in the game session"}}},"MiniGameTypeSchema":{"type":"object","required":["id","name","description","createdAt"],"properties":{"id":{"type":"string","description":"Unique mini game type identifier"},"name":{"type":"string","description":"Mini game type name"},"description":{"type":"string","description":"Mini game type description"},"imageUrl":{"type":"string","nullable":true,"description":"Mini game type image URL"},"createdAt":{"type":"string","format":"date-time","description":"Creation timestamp"}}}}}}
```

## The GameSessionsSchemaListResponse object

```json
{"openapi":"3.0.0","info":{"title":"Meshmap API v1","version":"1.0.0"},"components":{"schemas":{"GameSessionsSchemaListResponse":{"type":"object","required":["gameSessions"],"properties":{"gameSessions":{"type":"array","items":{"$ref":"#/components/schemas/GameSessionSchema"}}}},"GameSessionSchema":{"type":"object","required":["id","status","startTime","createdAt","updatedAt","playerCount"],"properties":{"id":{"type":"string","description":"Unique game session identifier"},"name":{"type":"string","nullable":true,"description":"Game session name"},"description":{"type":"string","nullable":true,"description":"Game session description"},"status":{"type":"string","enum":["ACTIVE","COMPLETED"],"description":"Game session status"},"startTime":{"type":"string","format":"date-time","description":"Game session start time"},"endTime":{"type":"string","format":"date-time","nullable":true,"description":"Game session end time"},"createdAt":{"type":"string","format":"date-time","description":"Creation timestamp"},"updatedAt":{"type":"string","format":"date-time","description":"Last update timestamp"},"miniGameType":{"allOf":[{"$ref":"#/components/schemas/MiniGameTypeSchema"}],"nullable":true,"description":"Associated mini game type"},"playerCount":{"type":"number","default":0,"description":"Number of players in the game session"}}},"MiniGameTypeSchema":{"type":"object","required":["id","name","description","createdAt"],"properties":{"id":{"type":"string","description":"Unique mini game type identifier"},"name":{"type":"string","description":"Mini game type name"},"description":{"type":"string","description":"Mini game type description"},"imageUrl":{"type":"string","nullable":true,"description":"Mini game type image URL"},"createdAt":{"type":"string","format":"date-time","description":"Creation timestamp"}}}}}}
```

## The RegisterPlayerToSessionRequest object

```json
{"openapi":"3.0.0","info":{"title":"Meshmap API v1","version":"1.0.0"},"components":{"schemas":{"RegisterPlayerToSessionRequest":{"type":"object","required":["playerId"],"properties":{"playerId":{"type":"string","description":"Player ID to register"}}}}}}
```

## The UpdateGameSessionPlayerRequest object

```json
{"openapi":"3.0.0","info":{"title":"Meshmap API v1","version":"1.0.0"},"components":{"schemas":{"UpdateGameSessionPlayerRequest":{"type":"object","required":["score"],"properties":{"score":{"type":"number","description":"Player score"}}}}}}
```

## The GameSessionPlayerSchema object

```json
{"openapi":"3.0.0","info":{"title":"Meshmap API v1","version":"1.0.0"},"components":{"schemas":{"GameSessionPlayerSchema":{"type":"object","required":["score","createdAt","updatedAt","playerId","displayName"],"properties":{"score":{"type":"number","description":"Player score"},"createdAt":{"type":"string","format":"date-time","description":"Creation timestamp"},"updatedAt":{"type":"string","format":"date-time","description":"Last update timestamp"},"playerId":{"type":"string","description":"Associated player ID"},"displayName":{"type":"string","description":"Player display name"},"imageUrl":{"type":"string","nullable":true,"description":"URL to player's profile image"},"user":{"type":"object","nullable":true,"description":"Associated user information","properties":{"id":{"type":"string","description":"User ID"},"username":{"type":"string","nullable":true,"description":"User's username"}}}}}}}}
```

## The GameSessionPlayersListResponse object

```json
{"openapi":"3.0.0","info":{"title":"Meshmap API v1","version":"1.0.0"},"components":{"schemas":{"GameSessionPlayersListResponse":{"type":"object","required":["players"],"properties":{"players":{"type":"array","items":{"$ref":"#/components/schemas/GameSessionPlayerSchema"}}}},"GameSessionPlayerSchema":{"type":"object","required":["score","createdAt","updatedAt","playerId","displayName"],"properties":{"score":{"type":"number","description":"Player score"},"createdAt":{"type":"string","format":"date-time","description":"Creation timestamp"},"updatedAt":{"type":"string","format":"date-time","description":"Last update timestamp"},"playerId":{"type":"string","description":"Associated player ID"},"displayName":{"type":"string","description":"Player display name"},"imageUrl":{"type":"string","nullable":true,"description":"URL to player's profile image"},"user":{"type":"object","nullable":true,"description":"Associated user information","properties":{"id":{"type":"string","description":"User ID"},"username":{"type":"string","nullable":true,"description":"User's username"}}}}}}}}
```

## The ErrorResponse object

```json
{"openapi":"3.0.0","info":{"title":"Meshmap API v1","version":"1.0.0"},"components":{"schemas":{"ErrorResponse":{"type":"object","required":["error"],"properties":{"error":{"oneOf":[{"type":"string","description":"Error message"},{"type":"array","items":{"type":"object","required":["code","message","path"],"properties":{"code":{"type":"string","description":"Error code"},"message":{"type":"string","description":"Error message"},"path":{"type":"array","items":{"oneOf":[{"type":"string"},{"type":"number"}]},"description":"Error path"}}}}]}}}}}}
```

## The CreateCodeRequest object

```json
{"openapi":"3.0.0","info":{"title":"Meshmap API v1","version":"1.0.0"},"components":{"schemas":{"CreateCodeRequest":{"type":"object","required":["playerId"],"properties":{"playerId":{"type":"string","description":"Player ID to associate with the code. Used to link the player to the code. Optional.","nullable":true}}}}}}
```

## The CodeSchema object

```json
{"openapi":"3.0.0","info":{"title":"Meshmap API v1","version":"1.0.0"},"components":{"schemas":{"CodeSchema":{"type":"object","required":["code","expiryTime"],"properties":{"code":{"type":"string","description":"Generated authentication code","pattern":"^[0-9]{6}$"},"playerId":{"type":"string","nullable":true,"description":"Player ID associated with the code (if provided)"},"expiryTime":{"type":"string","format":"date-time","description":"Code expiration timestamp"}}}}}}
```

## The CreateTokenFromCodeRequest object

```json
{"openapi":"3.0.0","info":{"title":"Meshmap API v1","version":"1.0.0"},"components":{"schemas":{"CreateTokenFromCodeRequest":{"type":"object","required":["code"],"properties":{"code":{"type":"string","description":"Authentication code","pattern":"^[0-9]{6}$"}}}}}}
```

## The CreateTokenFromCodeResponse object

```json
{"openapi":"3.0.0","info":{"title":"Meshmap API v1","version":"1.0.0"},"components":{"schemas":{"CreateTokenFromCodeResponse":{"type":"object","required":["status"],"properties":{"status":{"type":"string","enum":["VALID","INVALID","EXPIRED","ERROR"],"description":"Code validation status"},"sessionToken":{"type":"string","description":"JWT session token (only present when status is VALID and user is authenticated)"},"expiryTime":{"type":"string","format":"date-time","description":"Code expiry time (only present when status is VALID)"},"error":{"type":"string","description":"Error message (only present when status is not VALID)"}}}}}}
```

## The LeaderboardPlayerSchema object

```json
{"openapi":"3.0.0","info":{"title":"Meshmap API v1","version":"1.0.0"},"components":{"schemas":{"LeaderboardPlayerSchema":{"type":"object","required":["playerId","playerDisplayName","playerScore","createdAt","updatedAt","gameSessionId"],"properties":{"playerId":{"type":"string","description":"Player ID"},"playerDisplayName":{"type":"string","description":"Player display name"},"playerScore":{"type":"number","description":"Player score"},"playerImageUrl":{"type":"string","nullable":true,"description":"URL to player's profile image"},"createdAt":{"type":"string","format":"date-time","description":"Score creation timestamp"},"updatedAt":{"type":"string","format":"date-time","description":"Score last update timestamp"},"gameSessionId":{"type":"string","description":"Game session ID"},"userId":{"type":"string","nullable":true,"description":"Associated user ID"}}}}}}
```

## The MiniGameLeaderboardSchema object

```json
{"openapi":"3.0.0","info":{"title":"Meshmap API v1","version":"1.0.0"},"components":{"schemas":{"MiniGameLeaderboardSchema":{"type":"object","properties":{"miniGameTypeId":{"type":"string","nullable":true,"description":"Mini game type ID (null for default leaderboard)"},"miniGameTypeName":{"type":"string","nullable":true,"description":"Mini game type name (null for default leaderboard)"},"leaderboard":{"type":"array","items":{"$ref":"#/components/schemas/LeaderboardPlayerSchema"},"nullable":true,"description":"List of top players for this mini game type"}}},"LeaderboardPlayerSchema":{"type":"object","required":["playerId","playerDisplayName","playerScore","createdAt","updatedAt","gameSessionId"],"properties":{"playerId":{"type":"string","description":"Player ID"},"playerDisplayName":{"type":"string","description":"Player display name"},"playerScore":{"type":"number","description":"Player score"},"playerImageUrl":{"type":"string","nullable":true,"description":"URL to player's profile image"},"createdAt":{"type":"string","format":"date-time","description":"Score creation timestamp"},"updatedAt":{"type":"string","format":"date-time","description":"Score last update timestamp"},"gameSessionId":{"type":"string","description":"Game session ID"},"userId":{"type":"string","nullable":true,"description":"Associated user ID"}}}}}}
```

## The GlobalLeaderboardSchema object

```json
{"openapi":"3.0.0","info":{"title":"Meshmap API v1","version":"1.0.0"},"components":{"schemas":{"GlobalLeaderboardSchema":{"type":"object","required":["leaderboards"],"properties":{"leaderboards":{"type":"array","items":{"$ref":"#/components/schemas/MiniGameLeaderboardSchema"},"nullable":true,"description":"List of leaderboards for each mini game type and default. Each leaderboard includes player profile images and additional player details."}}},"MiniGameLeaderboardSchema":{"type":"object","properties":{"miniGameTypeId":{"type":"string","nullable":true,"description":"Mini game type ID (null for default leaderboard)"},"miniGameTypeName":{"type":"string","nullable":true,"description":"Mini game type name (null for default leaderboard)"},"leaderboard":{"type":"array","items":{"$ref":"#/components/schemas/LeaderboardPlayerSchema"},"nullable":true,"description":"List of top players for this mini game type"}}},"LeaderboardPlayerSchema":{"type":"object","required":["playerId","playerDisplayName","playerScore","createdAt","updatedAt","gameSessionId"],"properties":{"playerId":{"type":"string","description":"Player ID"},"playerDisplayName":{"type":"string","description":"Player display name"},"playerScore":{"type":"number","description":"Player score"},"playerImageUrl":{"type":"string","nullable":true,"description":"URL to player's profile image"},"createdAt":{"type":"string","format":"date-time","description":"Score creation timestamp"},"updatedAt":{"type":"string","format":"date-time","description":"Score last update timestamp"},"gameSessionId":{"type":"string","description":"Game session ID"},"userId":{"type":"string","nullable":true,"description":"Associated user ID"}}}}}}
```

## The UserSchema object

```json
{"openapi":"3.0.0","info":{"title":"Meshmap API v1","version":"1.0.0"},"components":{"schemas":{"UserSchema":{"type":"object","required":["id","createdAt"],"properties":{"id":{"type":"string","description":"Unique user identifier"},"email":{"type":"string","nullable":true,"description":"User email address"},"firstName":{"type":"string","nullable":true,"description":"User first name"},"lastName":{"type":"string","nullable":true,"description":"User last name"},"phoneNumber":{"type":"string","nullable":true,"description":"User phone number"},"username":{"type":"string","nullable":true,"description":"User username"},"createdAt":{"type":"string","format":"date-time","description":"User creation timestamp"},"imageUrl":{"type":"string","nullable":true,"description":"User profile image URL"}}}}}}
```

## The UserPatchRequest object

```json
{"openapi":"3.0.0","info":{"title":"Meshmap API v1","version":"1.0.0"},"components":{"schemas":{"UserPatchRequest":{"type":"object","required":["username"],"properties":{"username":{"type":"string","minLength":1,"description":"New username for the user"}}}}}}
```

## The ScanSchema object

```json
{"openapi":"3.0.0","info":{"title":"Meshmap API v1","version":"1.0.0"},"components":{"schemas":{"ScanSchema":{"type":"object","required":["id","timestamp","status"],"properties":{"id":{"type":"string","description":"Unique scan identifier"},"lat":{"type":"number","nullable":true,"description":"Latitude coordinate"},"long":{"type":"number","nullable":true,"description":"Longitude coordinate"},"scanUrl":{"type":"string","nullable":true,"description":"URL to the scan file"},"timestamp":{"type":"string","format":"date-time","description":"Scan timestamp"},"status":{"type":"string","description":"Scan processing status"},"title":{"type":"string","nullable":true,"description":"Scan title"},"category":{"type":"string","nullable":true,"description":"Scan category"},"location":{"type":"string","nullable":true,"description":"Scan location description"},"fileType":{"type":"string","nullable":true,"description":"Scan file type"},"previewImageUrl":{"type":"string","nullable":true,"description":"URL to scan preview image"},"timeOfDay":{"type":"string","nullable":true,"description":"Time of day when scan was taken"},"weather":{"type":"string","nullable":true,"description":"Weather conditions during scan"}}}}}}
```

## The PaginationInfo object

```json
{"openapi":"3.0.0","info":{"title":"Meshmap API v1","version":"1.0.0"},"components":{"schemas":{"PaginationInfo":{"type":"object","required":["total","page","limit","totalPages"],"properties":{"total":{"type":"integer","description":"Total number of items"},"page":{"type":"integer","description":"Current page number"},"limit":{"type":"integer","description":"Items per page"},"totalPages":{"type":"integer","description":"Total number of pages"}}}}}}
```

## The GetUserScansResponse object

```json
{"openapi":"3.0.0","info":{"title":"Meshmap API v1","version":"1.0.0"},"components":{"schemas":{"GetUserScansResponse":{"type":"object","required":["scans","pagination"],"properties":{"scans":{"type":"array","items":{"$ref":"#/components/schemas/ScanSchema"},"description":"List of user scans"},"pagination":{"$ref":"#/components/schemas/PaginationInfo"}}},"ScanSchema":{"type":"object","required":["id","timestamp","status"],"properties":{"id":{"type":"string","description":"Unique scan identifier"},"lat":{"type":"number","nullable":true,"description":"Latitude coordinate"},"long":{"type":"number","nullable":true,"description":"Longitude coordinate"},"scanUrl":{"type":"string","nullable":true,"description":"URL to the scan file"},"timestamp":{"type":"string","format":"date-time","description":"Scan timestamp"},"status":{"type":"string","description":"Scan processing status"},"title":{"type":"string","nullable":true,"description":"Scan title"},"category":{"type":"string","nullable":true,"description":"Scan category"},"location":{"type":"string","nullable":true,"description":"Scan location description"},"fileType":{"type":"string","nullable":true,"description":"Scan file type"},"previewImageUrl":{"type":"string","nullable":true,"description":"URL to scan preview image"},"timeOfDay":{"type":"string","nullable":true,"description":"Time of day when scan was taken"},"weather":{"type":"string","nullable":true,"description":"Weather conditions during scan"}}},"PaginationInfo":{"type":"object","required":["total","page","limit","totalPages"],"properties":{"total":{"type":"integer","description":"Total number of items"},"page":{"type":"integer","description":"Current page number"},"limit":{"type":"integer","description":"Items per page"},"totalPages":{"type":"integer","description":"Total number of pages"}}}}}}
```

## The PrivyAuthRequest object

```json
{"openapi":"3.0.0","info":{"title":"Meshmap API v1","version":"1.0.0"},"components":{"schemas":{"PrivyAuthRequest":{"type":"object","required":["token"],"properties":{"token":{"type":"string","description":"Privy authentication token"}}}}}}
```

## The PrivyAuthResponse object

```json
{"openapi":"3.0.0","info":{"title":"Meshmap API v1","version":"1.0.0"},"components":{"schemas":{"PrivyAuthResponse":{"type":"object","required":["user","meshmapSessionToken"],"properties":{"user":{"type":"object","required":["id","email","firstName","lastName","roles","isAdmin","hasOpsAccess"],"properties":{"id":{"type":"string","description":"User ID"},"email":{"type":"string","nullable":true,"description":"User email"},"firstName":{"type":"string","nullable":true,"description":"User first name"},"lastName":{"type":"string","nullable":true,"description":"User last name"},"roles":{"type":"array","items":{"type":"string"},"description":"User roles"},"isAdmin":{"type":"boolean","description":"Whether the user has admin role"},"hasOpsAccess":{"type":"boolean","description":"Whether the user has ops or admin access"}}},"meshmapSessionToken":{"type":"string","description":"JWT session token for authenticating with Meshmap APIs"}}}}}}
```


# Zones


# Authentication

User authentication and management

## Sync authentication token

> Syncs a Privy access token with the MeshMap auth service to obtain a session token.

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"tags":[{"name":"Authentication","description":"User authentication and management"}],"servers":[{"url":"/api","description":"API base path"}],"paths":{"/auth/sync":{"post":{"summary":"Sync authentication token","description":"Syncs a Privy access token with the MeshMap auth service to obtain a session token.","tags":["Authentication"],"parameters":[{"name":"Authorization","in":"header","required":true,"schema":{"type":"string"},"description":"Bearer token (Privy access token)"}],"responses":{"200":{"description":"Token synced successfully","content":{"application/json":{"schema":{"type":"object","description":"Session token response from auth service"}}}},"401":{"description":"Missing or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Auth service not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Auth service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"schemas":{"Error":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"object"}}}}}}
```

## GET /auth/user

> Get user by ID

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"tags":[{"name":"Authentication","description":"User authentication and management"}],"servers":[{"url":"/api","description":"API base path"}],"paths":{"/auth/user":{"get":{"summary":"Get user by ID","tags":["Authentication"],"parameters":[{"name":"userId","in":"query","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"User found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/User"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}}},"components":{"schemas":{"User":{"type":"object","properties":{"id":{"type":"string"},"walletAddress":{"type":"string","nullable":true},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"Error":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"object"}}}},"responses":{"BadRequest":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InternalServerError":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}
```

## POST /auth/user

> Create or retrieve a user

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"tags":[{"name":"Authentication","description":"User authentication and management"}],"servers":[{"url":"/api","description":"API base path"}],"paths":{"/auth/user":{"post":{"summary":"Create or retrieve a user","tags":["Authentication"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["dynamicUserId"],"properties":{"dynamicUserId":{"type":"string"},"walletAddress":{"type":"string","nullable":true}}}}}},"responses":{"200":{"description":"Existing user returned","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponse"}}}},"201":{"description":"New user created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"500":{"$ref":"#/components/responses/InternalServerError"}}}}},"components":{"schemas":{"UserResponse":{"allOf":[{"$ref":"#/components/schemas/User"},{"type":"object","properties":{"isNewUser":{"type":"boolean"}}}]},"User":{"type":"object","properties":{"id":{"type":"string"},"walletAddress":{"type":"string","nullable":true},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"Error":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"object"}}}},"responses":{"BadRequest":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InternalServerError":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}
```


# Categories

Category management for zones

## GET /categories

> List all categories

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"tags":[{"name":"Categories","description":"Category management for zones"}],"servers":[{"url":"/api","description":"API base path"}],"paths":{"/categories":{"get":{"summary":"List all categories","tags":["Categories"],"responses":{"200":{"description":"List of categories","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Category"}}}}},"500":{"$ref":"#/components/responses/InternalServerError"}}}}},"components":{"schemas":{"Category":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"description":{"type":"string","nullable":true},"color":{"type":"string"},"createdAt":{"type":"string","format":"date-time"}}},"Error":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"object"}}}},"responses":{"InternalServerError":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}
```

## POST /categories

> Create a new category

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"tags":[{"name":"Categories","description":"Category management for zones"}],"servers":[{"url":"/api","description":"API base path"}],"paths":{"/categories":{"post":{"summary":"Create a new category","tags":["Categories"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string"},"description":{"type":"string","nullable":true},"color":{"type":"string","default":"#3B82F6"}}}}}},"responses":{"201":{"description":"Category created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Category"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"409":{"description":"Category already exists"},"500":{"$ref":"#/components/responses/InternalServerError"}}}}},"components":{"schemas":{"Category":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"description":{"type":"string","nullable":true},"color":{"type":"string"},"createdAt":{"type":"string","format":"date-time"}}},"Error":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"object"}}}},"responses":{"BadRequest":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InternalServerError":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}
```

## PUT /categories

> Update a category

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"tags":[{"name":"Categories","description":"Category management for zones"}],"servers":[{"url":"/api","description":"API base path"}],"paths":{"/categories":{"put":{"summary":"Update a category","tags":["Categories"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["id","name"],"properties":{"id":{"type":"integer"},"name":{"type":"string"},"description":{"type":"string","nullable":true},"color":{"type":"string"}}}}}},"responses":{"200":{"description":"Category updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Category"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"409":{"description":"Category name conflict"},"500":{"$ref":"#/components/responses/InternalServerError"}}}}},"components":{"schemas":{"Category":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"description":{"type":"string","nullable":true},"color":{"type":"string"},"createdAt":{"type":"string","format":"date-time"}}},"Error":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"object"}}}},"responses":{"BadRequest":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InternalServerError":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}
```

## DELETE /categories

> Delete a category

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"tags":[{"name":"Categories","description":"Category management for zones"}],"servers":[{"url":"/api","description":"API base path"}],"paths":{"/categories":{"delete":{"summary":"Delete a category","tags":["Categories"],"parameters":[{"name":"id","in":"query","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Category deleted"},"400":{"$ref":"#/components/responses/BadRequest"},"500":{"$ref":"#/components/responses/InternalServerError"}}}}},"components":{"responses":{"BadRequest":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InternalServerError":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"object"}}}}}}
```


# Files

File upload and download operations

## GET /download

> Generate signed download URL for a file

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"tags":[{"name":"Files","description":"File upload and download operations"}],"servers":[{"url":"/api","description":"API base path"}],"paths":{"/download":{"get":{"summary":"Generate signed download URL for a file","tags":["Files"],"parameters":[{"name":"url","in":"query","required":true,"schema":{"type":"string"},"description":"The file URL to generate download link for"}],"responses":{"200":{"description":"Download URL generated","content":{"application/json":{"schema":{"type":"object","properties":{"downloadUrl":{"type":"string"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"500":{"$ref":"#/components/responses/InternalServerError"}}}}},"components":{"responses":{"BadRequest":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InternalServerError":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"object"}}}}}}
```

## Upload files

> Upload files including images, videos, AR World files, and other content types. MVP: Public access, no authentication required.

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"tags":[{"name":"Files","description":"File upload and download operations"}],"servers":[{"url":"/api","description":"API base path"}],"paths":{"/upload":{"post":{"summary":"Upload files","description":"Upload files including images, videos, AR World files, and other content types. MVP: Public access, no authentication required.","tags":["Files"],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary","description":"Single file upload"},"files":{"type":"array","items":{"type":"string","format":"binary"},"description":"Multiple file uploads"}}}}}},"responses":{"200":{"description":"Upload successful","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["success"]},"results":{"type":"array","items":{"$ref":"#/components/schemas/FileUploadResult"}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"500":{"$ref":"#/components/responses/InternalServerError"}}}}},"components":{"schemas":{"FileUploadResult":{"type":"object","description":"Result of a successful file upload","properties":{"status":{"type":"string","enum":["success"],"description":"Upload status"},"message":{"type":"string","description":"Success message"},"url":{"type":"string","format":"uri","description":"S3 URL of the uploaded file"},"fileId":{"type":"integer","description":"Database ID of the uploaded file record"},"fileName":{"type":"string","description":"Original filename"},"fileSize":{"type":"integer","nullable":true,"description":"File size in bytes"},"mimeType":{"type":"string","nullable":true,"description":"MIME type of the file"},"arWorldUrl":{"type":"string","format":"uri","nullable":true,"description":"S3 URL for AR World files (.bin), null for other file types"}}},"Error":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"object"}}}},"responses":{"BadRequest":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InternalServerError":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}
```

## Upload relocalization reference photo

> Uploads an image to be used as a relocalization reference photo for AR experiences. Only image files are accepted.

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"tags":[{"name":"Files","description":"File upload and download operations"},{"name":"Upload","description":"File upload operations"}],"servers":[{"url":"/api","description":"API base path"}],"security":[{"BearerAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","description":"Bearer token authentication. Pass the session token obtained from /auth/sync."}},"responses":{"BadRequest":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unauthorized":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InternalServerError":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"object"}}}}},"paths":{"/upload/relocalization":{"post":{"summary":"Upload relocalization reference photo","description":"Uploads an image to be used as a relocalization reference photo for AR experiences. Only image files are accepted.","tags":["Files","Upload"],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"string","format":"binary","description":"Image file (jpg, jpeg, png, webp, heic, heif)"}}}}}},"responses":{"200":{"description":"Photo uploaded successfully","content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","format":"uri","description":"Full URL to the uploaded image"},"key":{"type":"string","description":"S3 file key (e.g., reloc/uuid-filename.jpg)"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/InternalServerError"}}}}}}
```

## Retrieve or trigger GLB to USDZ conversion

> Retrieves the conversion result for a file, or triggers a new GLB to USDZ conversion if not already converted.

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"tags":[{"name":"Files","description":"File upload and download operations"},{"name":"Upload","description":"File upload operations"}],"servers":[{"url":"/api","description":"API base path"}],"paths":{"/upload/{fileId}/converted_file":{"post":{"summary":"Retrieve or trigger GLB to USDZ conversion","description":"Retrieves the conversion result for a file, or triggers a new GLB to USDZ conversion if not already converted.","tags":["Files","Upload"],"parameters":[{"name":"fileId","in":"path","required":true,"schema":{"type":"integer"},"description":"File ID"}],"responses":{"200":{"description":"Conversion result","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"},"originalFile":{"type":"object","properties":{"id":{"type":"integer"},"fileName":{"type":"string"},"fileType":{"type":"string","enum":["GLB"]},"fileUrl":{"type":"string","format":"uri"}}},"convertedFile":{"type":"object","properties":{"id":{"type":"integer"},"fileName":{"type":"string"},"fileType":{"type":"string","enum":["USDZ"]},"fileUrl":{"type":"string","format":"uri"},"fileSize":{"type":"integer"}}},"conversionTime":{"type":"string","description":"Time taken for conversion (e.g., '2.34s')"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"},"408":{"description":"Conversion timeout"},"500":{"$ref":"#/components/responses/InternalServerError"}}}}},"components":{"responses":{"BadRequest":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InternalServerError":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"object"}}}}}}
```

## Generate presigned upload URL

> MVP: Public access, no authentication required.

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"tags":[{"name":"Files","description":"File upload and download operations"}],"servers":[{"url":"/api","description":"API base path"}],"paths":{"/upload/presigned":{"post":{"summary":"Generate presigned upload URL","description":"MVP: Public access, no authentication required.","tags":["Files"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["fileName","contentType"],"properties":{"fileName":{"type":"string"},"contentType":{"type":"string"}}}}}},"responses":{"200":{"description":"Presigned URL generated","content":{"application/json":{"schema":{"type":"object","properties":{"uploadUrl":{"type":"string"},"key":{"type":"string"},"url":{"type":"string"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"500":{"$ref":"#/components/responses/InternalServerError"}}}}},"components":{"responses":{"BadRequest":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InternalServerError":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"object"}}}}}}
```

## Retrieve file information including converted USDZ if available

> Retrieves file metadata by fileId. If the file has been converted to USDZ, returns both original and converted file information. Useful for visualizing already processed files without re-uploading.

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"tags":[{"name":"Files","description":"File upload and download operations"},{"name":"Upload","description":"File upload operations"}],"servers":[{"url":"/api","description":"API base path"}],"paths":{"/upload/convert":{"get":{"summary":"Retrieve file information including converted USDZ if available","description":"Retrieves file metadata by fileId. If the file has been converted to USDZ, returns both original and converted file information. Useful for visualizing already processed files without re-uploading.","tags":["Files","Upload"],"parameters":[{"name":"fileId","in":"query","required":true,"schema":{"type":"integer"},"description":"ID of the file to retrieve"}],"responses":{"200":{"description":"File information retrieved successfully","content":{"application/json":{"schema":{"oneOf":[{"type":"object","title":"File with Conversion","properties":{"message":{"type":"string"},"originalFile":{"type":"object","properties":{"id":{"type":"integer","description":"File ID"},"fileName":{"type":"string","description":"Original file name"},"fileType":{"type":"string","enum":["GLB","USDZ"],"description":"Original file type"},"fileUrl":{"type":"string","format":"uri","description":"URL of the original file"}}},"convertedFile":{"type":"object","properties":{"id":{"type":"integer","description":"File ID (same as original)"},"fileName":{"type":"string","description":"Converted USDZ file name"},"fileType":{"type":"string","enum":["USDZ"]},"fileUrl":{"type":"string","format":"uri","description":"URL of the converted USDZ file"},"fileSize":{"type":"integer","description":"Size of the converted file in bytes"}}}}},{"type":"object","title":"File without Conversion","properties":{"message":{"type":"string"},"file":{"type":"object","properties":{"id":{"type":"integer","description":"File ID"},"fileName":{"type":"string","description":"File name"},"fileUrl":{"type":"string","format":"uri","description":"URL of the file"},"fileSize":{"type":"integer","description":"File size in bytes"},"mimeType":{"type":"string","description":"MIME type of the file"},"fileType":{"type":"string","enum":["GLB","USDZ"],"description":"File type"},"conversionStatus":{"type":"string","enum":["NOT_CONVERTED","CONVERTING","CONVERTED","FAILED"],"description":"Current conversion status"}}}}}]}}}},"400":{"description":"fileId is required and must be a number","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}},"404":{"description":"File not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}},"500":{"description":"Failed to retrieve file","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"string","description":"Error details"}}}}}}}}}}}
```

## Upload file with automatic GLB to USDZ conversion

> Uploads a file via multipart/form-data, creates a database record, and automatically converts GLB files to USDZ format. Returns file information including converted USDZ URL if conversion was performed.

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"tags":[{"name":"Files","description":"File upload and download operations"},{"name":"Upload","description":"File upload operations"}],"servers":[{"url":"/api","description":"API base path"}],"paths":{"/upload/convert":{"post":{"summary":"Upload file with automatic GLB to USDZ conversion","description":"Uploads a file via multipart/form-data, creates a database record, and automatically converts GLB files to USDZ format. Returns file information including converted USDZ URL if conversion was performed.","tags":["Files","Upload"],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"string","format":"binary","description":"File to upload. GLB files will be automatically converted to USDZ."}}}}}},"responses":{"200":{"description":"File uploaded successfully. If GLB file, conversion completed.","content":{"application/json":{"schema":{"oneOf":[{"type":"object","title":"GLB Conversion Response","properties":{"message":{"type":"string"},"originalFile":{"type":"object","properties":{"id":{"type":"integer","description":"File ID"},"fileName":{"type":"string","description":"Original GLB file name"},"fileType":{"type":"string","enum":["GLB"]},"fileUrl":{"type":"string","format":"uri","description":"URL of the original GLB file"}}},"convertedFile":{"type":"object","properties":{"id":{"type":"integer","description":"File ID (same as original)"},"fileName":{"type":"string","description":"Converted USDZ file name"},"fileType":{"type":"string","enum":["USDZ"]},"fileUrl":{"type":"string","format":"uri","description":"URL of the converted USDZ file"},"fileSize":{"type":"integer","description":"Size of the converted file in bytes"}}},"conversionTime":{"type":"string","description":"Time taken for conversion"}}},{"type":"object","title":"Non-GLB Upload Response","properties":{"message":{"type":"string"},"file":{"type":"object","properties":{"id":{"type":"integer","description":"File ID"},"fileName":{"type":"string","description":"File name"},"fileUrl":{"type":"string","format":"uri","description":"URL of the uploaded file"},"fileSize":{"type":"integer","description":"File size in bytes"},"mimeType":{"type":"string","description":"MIME type of the file"},"fileType":{"type":"string","enum":["GLB","USDZ"],"description":"Detected file type"}}}}}]}}}},"400":{"description":"File is required or invalid","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}},"500":{"description":"Upload or conversion failed","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"string","description":"Error details"}}}}}}}}}}}
```


# GCP

Ground Control Point management

## GET /gcp

> List GCPs

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"tags":[{"name":"GCP","description":"Ground Control Point management"}],"servers":[{"url":"/api","description":"API base path"}],"paths":{"/gcp":{"get":{"summary":"List GCPs","tags":["GCP"],"parameters":[{"name":"zoneId","in":"query","schema":{"type":"integer"}}],"responses":{"200":{"description":"List of GCPs","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/GCP"}}}}},"500":{"$ref":"#/components/responses/InternalServerError"}}}}},"components":{"schemas":{"GCP":{"type":"object","properties":{"id":{"type":"integer"},"title":{"type":"string"},"description":{"type":"string","nullable":true},"gcpType":{"type":"string","enum":["SINGLE","MULTIPLE"]},"altitude":{"type":"number","nullable":true},"aprilTagId":{"type":"integer","nullable":true,"minimum":0,"maximum":50},"gcpData":{"type":"string","nullable":true},"photoUrls":{"type":"array","items":{"type":"string"}},"zoneId":{"type":"integer","nullable":true},"pin":{"$ref":"#/components/schemas/Pin","nullable":true},"zone":{"type":"object","nullable":true,"properties":{"id":{"type":"integer"},"title":{"type":"string"}}},"uploadedFiles":{"type":"array","items":{"$ref":"#/components/schemas/UploadedFile"}},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"Pin":{"type":"object","properties":{"id":{"type":"integer"},"title":{"type":"string"},"latitude":{"type":"number","nullable":true},"longitude":{"type":"number","nullable":true},"geoPose":{"type":"object","description":"JSON object containing geographic position and orientation data"},"contentType":{"type":"string","enum":["APK","GLB","IMAGE","VIDEO","URL","LENS","GCP","AUDIO","USDZ","BIN"]},"contentUrl":{"type":"string"},"localizationData":{"type":"object","nullable":true,"description":"ARWorld localization data for iOS AR experiences","properties":{"trackingMode":{"type":"string","enum":["WORLD","GEO"],"description":"AR tracking mode for the pin"},"worldmapURL":{"type":"string","format":"uri","nullable":true,"description":"S3 URL of the ARWorld binary data"}}},"zoneId":{"type":"integer","nullable":true},"gcpId":{"type":"integer","nullable":true},"userId":{"type":"string","nullable":true},"visibility":{"type":"string","enum":["public","private","friends"],"description":"Pin visibility setting"},"relocalizationPhotoUrl":{"type":"string","nullable":true,"description":"URL of the relocalization reference photo"},"scanInstructions":{"type":"string","nullable":true,"description":"Instructions for scanning/relocalizing"},"cameraPoseHint":{"type":"object","nullable":true,"description":"Camera pose hint for AR relocalization"},"blockchainSignature":{"type":"string","nullable":true},"blockchainTxUrl":{"type":"string","nullable":true},"blockchainSyncedAt":{"type":"string","format":"date-time","nullable":true},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"UploadedFile":{"type":"object","properties":{"id":{"type":"integer"},"fileName":{"type":"string"},"fileUrl":{"type":"string"},"fileSize":{"type":"integer","nullable":true},"mimeType":{"type":"string","nullable":true},"bucketPath":{"type":"string","nullable":true},"gcpId":{"type":"integer","nullable":true},"userId":{"type":"string","nullable":true},"uploadedAt":{"type":"string","format":"date-time"}}},"Error":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"object"}}}},"responses":{"InternalServerError":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}
```

## POST /gcp

> Create a new GCP (Ground Control Point)

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"tags":[{"name":"GCP","description":"Ground Control Point management"}],"servers":[{"url":"/api","description":"API base path"}],"paths":{"/gcp":{"post":{"summary":"Create a new GCP (Ground Control Point)","tags":["GCP"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GCPCreateRequest"}}}},"responses":{"201":{"description":"GCP created","content":{"application/json":{"schema":{"type":"object","properties":{"gcp":{"$ref":"#/components/schemas/GCP"},"pin":{"$ref":"#/components/schemas/Pin"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"500":{"$ref":"#/components/responses/InternalServerError"}}}}},"components":{"schemas":{"GCPCreateRequest":{"type":"object","required":["title","gcpType","geoPose"],"properties":{"title":{"type":"string"},"description":{"type":"string"},"gcpType":{"type":"string","enum":["SINGLE","MULTIPLE"]},"geoPose":{"$ref":"#/components/schemas/GeoPose"},"altitude":{"type":"number"},"aprilTagId":{"type":"integer","minimum":0,"maximum":50},"gcpData":{"type":"string"},"photoUrls":{"type":"array","items":{"type":"string","format":"uri"},"maxItems":3},"uploadedFileIds":{"type":"array","items":{"type":"integer"}},"zoneId":{"type":"integer"}}},"GeoPose":{"type":"object","required":["position","angles"],"properties":{"position":{"$ref":"#/components/schemas/Position"},"angles":{"$ref":"#/components/schemas/YprAngles"}}},"Position":{"type":"object","required":["lat","lon"],"properties":{"lat":{"type":"number","description":"Latitude in decimal degrees"},"lon":{"type":"number","description":"Longitude in decimal degrees"},"h":{"type":"number","description":"Height/altitude in meters","default":0}}},"YprAngles":{"type":"object","required":["yaw","pitch","roll"],"properties":{"yaw":{"type":"number","description":"Yaw angle in degrees","default":0},"pitch":{"type":"number","description":"Pitch angle in degrees","default":0},"roll":{"type":"number","description":"Roll angle in degrees","default":0}}},"GCP":{"type":"object","properties":{"id":{"type":"integer"},"title":{"type":"string"},"description":{"type":"string","nullable":true},"gcpType":{"type":"string","enum":["SINGLE","MULTIPLE"]},"altitude":{"type":"number","nullable":true},"aprilTagId":{"type":"integer","nullable":true,"minimum":0,"maximum":50},"gcpData":{"type":"string","nullable":true},"photoUrls":{"type":"array","items":{"type":"string"}},"zoneId":{"type":"integer","nullable":true},"pin":{"$ref":"#/components/schemas/Pin","nullable":true},"zone":{"type":"object","nullable":true,"properties":{"id":{"type":"integer"},"title":{"type":"string"}}},"uploadedFiles":{"type":"array","items":{"$ref":"#/components/schemas/UploadedFile"}},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"Pin":{"type":"object","properties":{"id":{"type":"integer"},"title":{"type":"string"},"latitude":{"type":"number","nullable":true},"longitude":{"type":"number","nullable":true},"geoPose":{"type":"object","description":"JSON object containing geographic position and orientation data"},"contentType":{"type":"string","enum":["APK","GLB","IMAGE","VIDEO","URL","LENS","GCP","AUDIO","USDZ","BIN"]},"contentUrl":{"type":"string"},"localizationData":{"type":"object","nullable":true,"description":"ARWorld localization data for iOS AR experiences","properties":{"trackingMode":{"type":"string","enum":["WORLD","GEO"],"description":"AR tracking mode for the pin"},"worldmapURL":{"type":"string","format":"uri","nullable":true,"description":"S3 URL of the ARWorld binary data"}}},"zoneId":{"type":"integer","nullable":true},"gcpId":{"type":"integer","nullable":true},"userId":{"type":"string","nullable":true},"visibility":{"type":"string","enum":["public","private","friends"],"description":"Pin visibility setting"},"relocalizationPhotoUrl":{"type":"string","nullable":true,"description":"URL of the relocalization reference photo"},"scanInstructions":{"type":"string","nullable":true,"description":"Instructions for scanning/relocalizing"},"cameraPoseHint":{"type":"object","nullable":true,"description":"Camera pose hint for AR relocalization"},"blockchainSignature":{"type":"string","nullable":true},"blockchainTxUrl":{"type":"string","nullable":true},"blockchainSyncedAt":{"type":"string","format":"date-time","nullable":true},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"UploadedFile":{"type":"object","properties":{"id":{"type":"integer"},"fileName":{"type":"string"},"fileUrl":{"type":"string"},"fileSize":{"type":"integer","nullable":true},"mimeType":{"type":"string","nullable":true},"bucketPath":{"type":"string","nullable":true},"gcpId":{"type":"integer","nullable":true},"userId":{"type":"string","nullable":true},"uploadedAt":{"type":"string","format":"date-time"}}},"Error":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"object"}}}},"responses":{"BadRequest":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InternalServerError":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}
```

## PUT /gcp/{id}

> Update a GCP

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"tags":[{"name":"GCP","description":"Ground Control Point management"}],"servers":[{"url":"/api","description":"API base path"}],"paths":{"/gcp/{id}":{"put":{"summary":"Update a GCP","tags":["GCP"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GCPUpdateRequest"}}}},"responses":{"200":{"description":"GCP updated","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"},"gcp":{"$ref":"#/components/schemas/GCP"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"April tag conflict"},"500":{"$ref":"#/components/responses/InternalServerError"}}}}},"components":{"schemas":{"GCPUpdateRequest":{"type":"object","required":["title","geoPose"],"properties":{"title":{"type":"string"},"description":{"type":"string","nullable":true},"geoPose":{"$ref":"#/components/schemas/GeoPose"},"altitude":{"type":"number","nullable":true},"aprilTagId":{"type":"integer","nullable":true,"minimum":0,"maximum":50},"photoUrls":{"type":"array","items":{"type":"string","format":"uri"}}}},"GeoPose":{"type":"object","required":["position","angles"],"properties":{"position":{"$ref":"#/components/schemas/Position"},"angles":{"$ref":"#/components/schemas/YprAngles"}}},"Position":{"type":"object","required":["lat","lon"],"properties":{"lat":{"type":"number","description":"Latitude in decimal degrees"},"lon":{"type":"number","description":"Longitude in decimal degrees"},"h":{"type":"number","description":"Height/altitude in meters","default":0}}},"YprAngles":{"type":"object","required":["yaw","pitch","roll"],"properties":{"yaw":{"type":"number","description":"Yaw angle in degrees","default":0},"pitch":{"type":"number","description":"Pitch angle in degrees","default":0},"roll":{"type":"number","description":"Roll angle in degrees","default":0}}},"GCP":{"type":"object","properties":{"id":{"type":"integer"},"title":{"type":"string"},"description":{"type":"string","nullable":true},"gcpType":{"type":"string","enum":["SINGLE","MULTIPLE"]},"altitude":{"type":"number","nullable":true},"aprilTagId":{"type":"integer","nullable":true,"minimum":0,"maximum":50},"gcpData":{"type":"string","nullable":true},"photoUrls":{"type":"array","items":{"type":"string"}},"zoneId":{"type":"integer","nullable":true},"pin":{"$ref":"#/components/schemas/Pin","nullable":true},"zone":{"type":"object","nullable":true,"properties":{"id":{"type":"integer"},"title":{"type":"string"}}},"uploadedFiles":{"type":"array","items":{"$ref":"#/components/schemas/UploadedFile"}},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"Pin":{"type":"object","properties":{"id":{"type":"integer"},"title":{"type":"string"},"latitude":{"type":"number","nullable":true},"longitude":{"type":"number","nullable":true},"geoPose":{"type":"object","description":"JSON object containing geographic position and orientation data"},"contentType":{"type":"string","enum":["APK","GLB","IMAGE","VIDEO","URL","LENS","GCP","AUDIO","USDZ","BIN"]},"contentUrl":{"type":"string"},"localizationData":{"type":"object","nullable":true,"description":"ARWorld localization data for iOS AR experiences","properties":{"trackingMode":{"type":"string","enum":["WORLD","GEO"],"description":"AR tracking mode for the pin"},"worldmapURL":{"type":"string","format":"uri","nullable":true,"description":"S3 URL of the ARWorld binary data"}}},"zoneId":{"type":"integer","nullable":true},"gcpId":{"type":"integer","nullable":true},"userId":{"type":"string","nullable":true},"visibility":{"type":"string","enum":["public","private","friends"],"description":"Pin visibility setting"},"relocalizationPhotoUrl":{"type":"string","nullable":true,"description":"URL of the relocalization reference photo"},"scanInstructions":{"type":"string","nullable":true,"description":"Instructions for scanning/relocalizing"},"cameraPoseHint":{"type":"object","nullable":true,"description":"Camera pose hint for AR relocalization"},"blockchainSignature":{"type":"string","nullable":true},"blockchainTxUrl":{"type":"string","nullable":true},"blockchainSyncedAt":{"type":"string","format":"date-time","nullable":true},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"UploadedFile":{"type":"object","properties":{"id":{"type":"integer"},"fileName":{"type":"string"},"fileUrl":{"type":"string"},"fileSize":{"type":"integer","nullable":true},"mimeType":{"type":"string","nullable":true},"bucketPath":{"type":"string","nullable":true},"gcpId":{"type":"integer","nullable":true},"userId":{"type":"string","nullable":true},"uploadedAt":{"type":"string","format":"date-time"}}},"Error":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"object"}}}},"responses":{"BadRequest":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InternalServerError":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}
```

## DELETE /gcp/{id}/remove-photo

> Remove a photo from a GCP

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"tags":[{"name":"GCP","description":"Ground Control Point management"}],"servers":[{"url":"/api","description":"API base path"}],"paths":{"/gcp/{id}/remove-photo":{"delete":{"summary":"Remove a photo from a GCP","tags":["GCP"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}},{"name":"photoUrl","in":"query","required":true,"schema":{"type":"string","format":"uri"}}],"responses":{"200":{"description":"Photo removed successfully"},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}}},"components":{"responses":{"BadRequest":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InternalServerError":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"object"}}}}}}
```

## GET /gcp/check-april-tag

> Check April tag availability

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"tags":[{"name":"GCP","description":"Ground Control Point management"}],"servers":[{"url":"/api","description":"API base path"}],"paths":{"/gcp/check-april-tag":{"get":{"summary":"Check April tag availability","tags":["GCP"],"parameters":[{"name":"aprilTagId","in":"query","required":true,"schema":{"type":"integer","minimum":0,"maximum":50}},{"name":"latitude","in":"query","schema":{"type":"number"}},{"name":"longitude","in":"query","schema":{"type":"number"}},{"name":"excludeGcpId","in":"query","schema":{"type":"integer"}}],"responses":{"200":{"description":"April tag validation result","content":{"application/json":{"schema":{"type":"object","properties":{"available":{"type":"boolean"},"conflicts":{"type":"array","items":{"type":"object"}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"500":{"$ref":"#/components/responses/InternalServerError"}}}}},"components":{"responses":{"BadRequest":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InternalServerError":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"object"}}}}}}
```

## POST /gcp/upload

> Upload GCP CSV file

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"tags":[{"name":"GCP","description":"Ground Control Point management"}],"servers":[{"url":"/api","description":"API base path"}],"paths":{"/gcp/upload":{"post":{"summary":"Upload GCP CSV file","tags":["GCP"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["fileName","fileUrl","zoneId"],"properties":{"fileName":{"type":"string"},"fileUrl":{"type":"string"},"zoneId":{"type":"integer"},"rowCount":{"type":"integer"},"userId":{"type":"string"}}}}}},"responses":{"200":{"description":"CSV upload processed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CSVUpload"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}}},"components":{"schemas":{"CSVUpload":{"type":"object","properties":{"id":{"type":"string"},"fileName":{"type":"string"},"fileUrl":{"type":"string"},"status":{"type":"string","enum":["PENDING","COMPLETED","FAILED"]},"errorMessage":{"type":"string","nullable":true},"rowCount":{"type":"integer","nullable":true},"processedRows":{"type":"integer","nullable":true},"userId":{"type":"string"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"Error":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"object"}}}},"responses":{"BadRequest":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unauthorized":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InternalServerError":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}
```

## GET /gcp/zone/{zoneId}

> Get CSV uploads for a zone

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"tags":[{"name":"GCP","description":"Ground Control Point management"}],"servers":[{"url":"/api","description":"API base path"}],"paths":{"/gcp/zone/{zoneId}":{"get":{"summary":"Get CSV uploads for a zone","tags":["GCP"],"parameters":[{"name":"zoneId","in":"path","required":true,"schema":{"type":"integer"}},{"name":"userId","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"List of CSV files for zone","content":{"application/json":{"schema":{"type":"object","properties":{"zoneId":{"type":"integer"},"zoneName":{"type":"string"},"csvFiles":{"type":"array","items":{"$ref":"#/components/schemas/CSVUpload"}},"totalFiles":{"type":"integer"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}}},"components":{"schemas":{"CSVUpload":{"type":"object","properties":{"id":{"type":"string"},"fileName":{"type":"string"},"fileUrl":{"type":"string"},"status":{"type":"string","enum":["PENDING","COMPLETED","FAILED"]},"errorMessage":{"type":"string","nullable":true},"rowCount":{"type":"integer","nullable":true},"processedRows":{"type":"integer","nullable":true},"userId":{"type":"string"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"Error":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"object"}}}},"responses":{"BadRequest":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InternalServerError":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}
```

## DELETE /gcp/zone/{zoneId}

> Delete a CSV file from a zone

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"tags":[{"name":"GCP","description":"Ground Control Point management"}],"servers":[{"url":"/api","description":"API base path"}],"paths":{"/gcp/zone/{zoneId}":{"delete":{"summary":"Delete a CSV file from a zone","tags":["GCP"],"parameters":[{"name":"zoneId","in":"path","required":true,"schema":{"type":"integer"}},{"name":"userId","in":"query","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["csvUploadId"],"properties":{"csvUploadId":{"type":"string"}}}}}},"responses":{"200":{"description":"CSV file deleted"},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}}},"components":{"responses":{"BadRequest":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InternalServerError":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"object"}}}}}}
```

## GET /upload/gcp-photos

> Get uploaded files for a GCP

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"tags":[{"name":"GCP","description":"Ground Control Point management"}],"servers":[{"url":"/api","description":"API base path"}],"paths":{"/upload/gcp-photos":{"get":{"summary":"Get uploaded files for a GCP","tags":["GCP"],"parameters":[{"name":"gcpId","in":"query","required":true,"schema":{"type":"integer"}},{"name":"userId","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"List of uploaded files","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string"},"files":{"type":"array","items":{"$ref":"#/components/schemas/UploadedFile"}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"500":{"$ref":"#/components/responses/InternalServerError"}}}}},"components":{"schemas":{"UploadedFile":{"type":"object","properties":{"id":{"type":"integer"},"fileName":{"type":"string"},"fileUrl":{"type":"string"},"fileSize":{"type":"integer","nullable":true},"mimeType":{"type":"string","nullable":true},"bucketPath":{"type":"string","nullable":true},"gcpId":{"type":"integer","nullable":true},"userId":{"type":"string","nullable":true},"uploadedAt":{"type":"string","format":"date-time"}}},"Error":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"object"}}}},"responses":{"BadRequest":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InternalServerError":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}
```

## POST /upload/gcp-photos

> Upload GCP photos

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"tags":[{"name":"GCP","description":"Ground Control Point management"}],"servers":[{"url":"/api","description":"API base path"}],"paths":{"/upload/gcp-photos":{"post":{"summary":"Upload GCP photos","tags":["GCP"],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["files"],"properties":{"files":{"type":"array","items":{"type":"string","format":"binary"},"maxItems":3},"gcpId":{"type":"string"},"userId":{"type":"string"}}}}}},"responses":{"200":{"description":"Photos uploaded successfully","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string"},"message":{"type":"string"},"files":{"type":"array","items":{"$ref":"#/components/schemas/UploadedFile"}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"500":{"$ref":"#/components/responses/InternalServerError"}}}}},"components":{"schemas":{"UploadedFile":{"type":"object","properties":{"id":{"type":"integer"},"fileName":{"type":"string"},"fileUrl":{"type":"string"},"fileSize":{"type":"integer","nullable":true},"mimeType":{"type":"string","nullable":true},"bucketPath":{"type":"string","nullable":true},"gcpId":{"type":"integer","nullable":true},"userId":{"type":"string","nullable":true},"uploadedAt":{"type":"string","format":"date-time"}}},"Error":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"object"}}}},"responses":{"BadRequest":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InternalServerError":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}
```


# Pins

Pin management

## GET /pins

> List all pins

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"tags":[{"name":"Pins","description":"Pin management"}],"servers":[{"url":"/api","description":"API base path"}],"paths":{"/pins":{"get":{"summary":"List all pins","tags":["Pins"],"parameters":[{"name":"zoneId","in":"query","schema":{"type":"integer"}}],"responses":{"200":{"description":"List of pins","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Pin"}}}}},"500":{"$ref":"#/components/responses/InternalServerError"}}}}},"components":{"schemas":{"Pin":{"type":"object","properties":{"id":{"type":"integer"},"title":{"type":"string"},"latitude":{"type":"number","nullable":true},"longitude":{"type":"number","nullable":true},"geoPose":{"type":"object","description":"JSON object containing geographic position and orientation data"},"contentType":{"type":"string","enum":["APK","GLB","IMAGE","VIDEO","URL","LENS","GCP","AUDIO","USDZ","BIN"]},"contentUrl":{"type":"string"},"localizationData":{"type":"object","nullable":true,"description":"ARWorld localization data for iOS AR experiences","properties":{"trackingMode":{"type":"string","enum":["WORLD","GEO"],"description":"AR tracking mode for the pin"},"worldmapURL":{"type":"string","format":"uri","nullable":true,"description":"S3 URL of the ARWorld binary data"}}},"zoneId":{"type":"integer","nullable":true},"gcpId":{"type":"integer","nullable":true},"userId":{"type":"string","nullable":true},"visibility":{"type":"string","enum":["public","private","friends"],"description":"Pin visibility setting"},"relocalizationPhotoUrl":{"type":"string","nullable":true,"description":"URL of the relocalization reference photo"},"scanInstructions":{"type":"string","nullable":true,"description":"Instructions for scanning/relocalizing"},"cameraPoseHint":{"type":"object","nullable":true,"description":"Camera pose hint for AR relocalization"},"blockchainSignature":{"type":"string","nullable":true},"blockchainTxUrl":{"type":"string","nullable":true},"blockchainSyncedAt":{"type":"string","format":"date-time","nullable":true},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"Error":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"object"}}}},"responses":{"InternalServerError":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}
```

## Create a global pin (outside any zone)

> Creates a pin that is not associated with any specific zone. This allows pinning objects anywhere on the map without being restricted to defined zones.

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"tags":[{"name":"Pins","description":"Pin management"}],"servers":[{"url":"/api","description":"API base path"}],"paths":{"/pins":{"post":{"summary":"Create a global pin (outside any zone)","description":"Creates a pin that is not associated with any specific zone. This allows pinning objects anywhere on the map without being restricted to defined zones.","tags":["Pins"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["title","contentUrl","contentType","geoPose"],"properties":{"title":{"type":"string","description":"Title of the pin"},"contentUrl":{"type":"string","format":"uri","description":"URL of the content associated with the pin"},"contentType":{"type":"string","enum":["IMAGE","VIDEO","GLB","USDZ","BIN","URL","APK","LENS","GCP","AUDIO"],"description":"Type of content associated with the pin"},"geoPose":{"$ref":"#/components/schemas/GeoPose","description":"Geographic position and orientation of the pin"},"latitude":{"type":"number","nullable":true,"description":"Latitude coordinate (optional, can be derived from geoPose)"},"longitude":{"type":"number","nullable":true,"description":"Longitude coordinate (optional, can be derived from geoPose)"}}}}}},"responses":{"201":{"description":"Global pin created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Pin"}}}},"400":{"description":"Invalid pin data","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"array","items":{"type":"object"}}}}}}},"500":{"description":"Failed to create pin","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}}}}}},"components":{"schemas":{"GeoPose":{"type":"object","required":["position","angles"],"properties":{"position":{"$ref":"#/components/schemas/Position"},"angles":{"$ref":"#/components/schemas/YprAngles"}}},"Position":{"type":"object","required":["lat","lon"],"properties":{"lat":{"type":"number","description":"Latitude in decimal degrees"},"lon":{"type":"number","description":"Longitude in decimal degrees"},"h":{"type":"number","description":"Height/altitude in meters","default":0}}},"YprAngles":{"type":"object","required":["yaw","pitch","roll"],"properties":{"yaw":{"type":"number","description":"Yaw angle in degrees","default":0},"pitch":{"type":"number","description":"Pitch angle in degrees","default":0},"roll":{"type":"number","description":"Roll angle in degrees","default":0}}},"Pin":{"type":"object","properties":{"id":{"type":"integer"},"title":{"type":"string"},"latitude":{"type":"number","nullable":true},"longitude":{"type":"number","nullable":true},"geoPose":{"type":"object","description":"JSON object containing geographic position and orientation data"},"contentType":{"type":"string","enum":["APK","GLB","IMAGE","VIDEO","URL","LENS","GCP","AUDIO","USDZ","BIN"]},"contentUrl":{"type":"string"},"localizationData":{"type":"object","nullable":true,"description":"ARWorld localization data for iOS AR experiences","properties":{"trackingMode":{"type":"string","enum":["WORLD","GEO"],"description":"AR tracking mode for the pin"},"worldmapURL":{"type":"string","format":"uri","nullable":true,"description":"S3 URL of the ARWorld binary data"}}},"zoneId":{"type":"integer","nullable":true},"gcpId":{"type":"integer","nullable":true},"userId":{"type":"string","nullable":true},"visibility":{"type":"string","enum":["public","private","friends"],"description":"Pin visibility setting"},"relocalizationPhotoUrl":{"type":"string","nullable":true,"description":"URL of the relocalization reference photo"},"scanInstructions":{"type":"string","nullable":true,"description":"Instructions for scanning/relocalizing"},"cameraPoseHint":{"type":"object","nullable":true,"description":"Camera pose hint for AR relocalization"},"blockchainSignature":{"type":"string","nullable":true},"blockchainTxUrl":{"type":"string","nullable":true},"blockchainSyncedAt":{"type":"string","format":"date-time","nullable":true},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}}}}}
```

## Delete a global pin

> Deletes a global pin by ID and removes any associated uploaded files. This operation cascades to clean up related file records.

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"tags":[{"name":"Pins","description":"Pin management"}],"servers":[{"url":"/api","description":"API base path"}],"paths":{"/pins":{"delete":{"summary":"Delete a global pin","description":"Deletes a global pin by ID and removes any associated uploaded files. This operation cascades to clean up related file records.","tags":["Pins"],"parameters":[{"name":"id","in":"query","required":true,"schema":{"type":"string"},"description":"The ID of the pin to delete"}],"responses":{"200":{"description":"Pin deleted successfully","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"},"deletedPinId":{"type":"integer","description":"ID of the deleted pin"},"deletedFilesCount":{"type":"integer","description":"Number of associated uploaded files that were also deleted"}}}}}},"400":{"description":"Invalid pin ID or validation failed","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"array","items":{"type":"object"},"description":"Validation error details"}}}}}},"404":{"description":"Pin not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}},"500":{"description":"Failed to delete pin","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}}}}}}}
```

## Get a pin by ID

> Retrieves a single pin by ID with map associations. MVP: Public access, no authentication required.

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"tags":[{"name":"Pins","description":"Pin management"}],"servers":[{"url":"/api","description":"API base path"}],"paths":{"/pins/{id}":{"get":{"summary":"Get a pin by ID","description":"Retrieves a single pin by ID with map associations. MVP: Public access, no authentication required.","tags":["Pins"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"},"description":"Pin ID"}],"responses":{"200":{"description":"Pin retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Pin"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Forbidden - pin not accessible due to visibility settings","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}}},"components":{"schemas":{"Pin":{"type":"object","properties":{"id":{"type":"integer"},"title":{"type":"string"},"latitude":{"type":"number","nullable":true},"longitude":{"type":"number","nullable":true},"geoPose":{"type":"object","description":"JSON object containing geographic position and orientation data"},"contentType":{"type":"string","enum":["APK","GLB","IMAGE","VIDEO","URL","LENS","GCP","AUDIO","USDZ","BIN"]},"contentUrl":{"type":"string"},"localizationData":{"type":"object","nullable":true,"description":"ARWorld localization data for iOS AR experiences","properties":{"trackingMode":{"type":"string","enum":["WORLD","GEO"],"description":"AR tracking mode for the pin"},"worldmapURL":{"type":"string","format":"uri","nullable":true,"description":"S3 URL of the ARWorld binary data"}}},"zoneId":{"type":"integer","nullable":true},"gcpId":{"type":"integer","nullable":true},"userId":{"type":"string","nullable":true},"visibility":{"type":"string","enum":["public","private","friends"],"description":"Pin visibility setting"},"relocalizationPhotoUrl":{"type":"string","nullable":true,"description":"URL of the relocalization reference photo"},"scanInstructions":{"type":"string","nullable":true,"description":"Instructions for scanning/relocalizing"},"cameraPoseHint":{"type":"object","nullable":true,"description":"Camera pose hint for AR relocalization"},"blockchainSignature":{"type":"string","nullable":true},"blockchainTxUrl":{"type":"string","nullable":true},"blockchainSyncedAt":{"type":"string","format":"date-time","nullable":true},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"Error":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"object"}}}},"responses":{"BadRequest":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unauthorized":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InternalServerError":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}
```

## PUT /pins/{id}

> Update a pin

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"tags":[{"name":"Pins","description":"Pin management"}],"servers":[{"url":"/api","description":"API base path"}],"paths":{"/pins/{id}":{"put":{"summary":"Update a pin","tags":["Pins"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["title","contentUrl","contentType"],"properties":{"title":{"type":"string"},"contentUrl":{"type":"string","format":"uri"},"contentType":{"type":"string","enum":["IMAGE","VIDEO","GLB","USDZ","BIN","URL","APK","LENS","GCP","AUDIO","AR_WORLD"]},"gcpData":{"type":"string","nullable":true}}}}}},"responses":{"200":{"description":"Pin updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Pin"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"500":{"$ref":"#/components/responses/InternalServerError"}}}}},"components":{"schemas":{"Pin":{"type":"object","properties":{"id":{"type":"integer"},"title":{"type":"string"},"latitude":{"type":"number","nullable":true},"longitude":{"type":"number","nullable":true},"geoPose":{"type":"object","description":"JSON object containing geographic position and orientation data"},"contentType":{"type":"string","enum":["APK","GLB","IMAGE","VIDEO","URL","LENS","GCP","AUDIO","USDZ","BIN"]},"contentUrl":{"type":"string"},"localizationData":{"type":"object","nullable":true,"description":"ARWorld localization data for iOS AR experiences","properties":{"trackingMode":{"type":"string","enum":["WORLD","GEO"],"description":"AR tracking mode for the pin"},"worldmapURL":{"type":"string","format":"uri","nullable":true,"description":"S3 URL of the ARWorld binary data"}}},"zoneId":{"type":"integer","nullable":true},"gcpId":{"type":"integer","nullable":true},"userId":{"type":"string","nullable":true},"visibility":{"type":"string","enum":["public","private","friends"],"description":"Pin visibility setting"},"relocalizationPhotoUrl":{"type":"string","nullable":true,"description":"URL of the relocalization reference photo"},"scanInstructions":{"type":"string","nullable":true,"description":"Instructions for scanning/relocalizing"},"cameraPoseHint":{"type":"object","nullable":true,"description":"Camera pose hint for AR relocalization"},"blockchainSignature":{"type":"string","nullable":true},"blockchainTxUrl":{"type":"string","nullable":true},"blockchainSyncedAt":{"type":"string","format":"date-time","nullable":true},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"Error":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"object"}}}},"responses":{"BadRequest":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InternalServerError":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}
```

## DELETE /pins/{id}

> Delete a pin

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"tags":[{"name":"Pins","description":"Pin management"}],"servers":[{"url":"/api","description":"API base path"}],"paths":{"/pins/{id}":{"delete":{"summary":"Delete a pin","tags":["Pins"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"204":{"description":"Pin deleted"},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}}},"components":{"responses":{"BadRequest":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InternalServerError":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"object"}}}}}}
```

## Record pin visit

> Marks a pin as visited for the authenticated user across all maps containing it. Returns updated progress for each map.

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"tags":[{"name":"Pins","description":"Pin management"}],"servers":[{"url":"/api","description":"API base path"}],"security":[{"BearerAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","description":"Bearer token authentication. Pass the session token obtained from /auth/sync."}},"responses":{"BadRequest":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unauthorized":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InternalServerError":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"object"}}}}},"paths":{"/pins/{id}/visit":{"post":{"summary":"Record pin visit","description":"Marks a pin as visited for the authenticated user across all maps containing it. Returns updated progress for each map.","tags":["Pins"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"},"description":"Pin ID"}],"responses":{"200":{"description":"Pin visit recorded","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"visitedMaps":{"type":"array","items":{"type":"integer"},"description":"Map IDs where the visit was recorded"},"progress":{"type":"array","items":{"type":"object","properties":{"mapId":{"type":"integer"},"totalPins":{"type":"integer"},"visitedPins":{"type":"integer"},"completionPercentage":{"type":"number"}}}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}}}}
```

## Update pin visibility

> Updates the visibility setting of a pin (public, private, or friends).

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"tags":[{"name":"Pins","description":"Pin management"}],"servers":[{"url":"/api","description":"API base path"}],"security":[{"BearerAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","description":"Bearer token authentication. Pass the session token obtained from /auth/sync."}},"schemas":{"Pin":{"type":"object","properties":{"id":{"type":"integer"},"title":{"type":"string"},"latitude":{"type":"number","nullable":true},"longitude":{"type":"number","nullable":true},"geoPose":{"type":"object","description":"JSON object containing geographic position and orientation data"},"contentType":{"type":"string","enum":["APK","GLB","IMAGE","VIDEO","URL","LENS","GCP","AUDIO","USDZ","BIN"]},"contentUrl":{"type":"string"},"localizationData":{"type":"object","nullable":true,"description":"ARWorld localization data for iOS AR experiences","properties":{"trackingMode":{"type":"string","enum":["WORLD","GEO"],"description":"AR tracking mode for the pin"},"worldmapURL":{"type":"string","format":"uri","nullable":true,"description":"S3 URL of the ARWorld binary data"}}},"zoneId":{"type":"integer","nullable":true},"gcpId":{"type":"integer","nullable":true},"userId":{"type":"string","nullable":true},"visibility":{"type":"string","enum":["public","private","friends"],"description":"Pin visibility setting"},"relocalizationPhotoUrl":{"type":"string","nullable":true,"description":"URL of the relocalization reference photo"},"scanInstructions":{"type":"string","nullable":true,"description":"Instructions for scanning/relocalizing"},"cameraPoseHint":{"type":"object","nullable":true,"description":"Camera pose hint for AR relocalization"},"blockchainSignature":{"type":"string","nullable":true},"blockchainTxUrl":{"type":"string","nullable":true},"blockchainSyncedAt":{"type":"string","format":"date-time","nullable":true},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"Error":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"object"}}}},"responses":{"BadRequest":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unauthorized":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InternalServerError":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/pins/{id}/visibility":{"patch":{"summary":"Update pin visibility","description":"Updates the visibility setting of a pin (public, private, or friends).","tags":["Pins"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"},"description":"Pin ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["visibility"],"properties":{"visibility":{"type":"string","enum":["public","private","friends"]}}}}}},"responses":{"200":{"description":"Visibility updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Pin"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}}}}
```

## Update pin localization data

> Updates the AR relocalization data for a pin, including the reference photo URL, scan instructions, and camera pose hint.

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"tags":[{"name":"Pins","description":"Pin management"}],"servers":[{"url":"/api","description":"API base path"}],"security":[{"BearerAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","description":"Bearer token authentication. Pass the session token obtained from /auth/sync."}},"schemas":{"Pin":{"type":"object","properties":{"id":{"type":"integer"},"title":{"type":"string"},"latitude":{"type":"number","nullable":true},"longitude":{"type":"number","nullable":true},"geoPose":{"type":"object","description":"JSON object containing geographic position and orientation data"},"contentType":{"type":"string","enum":["APK","GLB","IMAGE","VIDEO","URL","LENS","GCP","AUDIO","USDZ","BIN"]},"contentUrl":{"type":"string"},"localizationData":{"type":"object","nullable":true,"description":"ARWorld localization data for iOS AR experiences","properties":{"trackingMode":{"type":"string","enum":["WORLD","GEO"],"description":"AR tracking mode for the pin"},"worldmapURL":{"type":"string","format":"uri","nullable":true,"description":"S3 URL of the ARWorld binary data"}}},"zoneId":{"type":"integer","nullable":true},"gcpId":{"type":"integer","nullable":true},"userId":{"type":"string","nullable":true},"visibility":{"type":"string","enum":["public","private","friends"],"description":"Pin visibility setting"},"relocalizationPhotoUrl":{"type":"string","nullable":true,"description":"URL of the relocalization reference photo"},"scanInstructions":{"type":"string","nullable":true,"description":"Instructions for scanning/relocalizing"},"cameraPoseHint":{"type":"object","nullable":true,"description":"Camera pose hint for AR relocalization"},"blockchainSignature":{"type":"string","nullable":true},"blockchainTxUrl":{"type":"string","nullable":true},"blockchainSyncedAt":{"type":"string","format":"date-time","nullable":true},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"Error":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"object"}}}},"responses":{"BadRequest":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unauthorized":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InternalServerError":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/pins/{id}/localization":{"patch":{"summary":"Update pin localization data","description":"Updates the AR relocalization data for a pin, including the reference photo URL, scan instructions, and camera pose hint.","tags":["Pins"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"},"description":"Pin ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"relocalizationPhotoUrl":{"type":"string","format":"uri"},"scanInstructions":{"type":"string"},"cameraPoseHint":{"type":"object","properties":{"fov":{"type":"number"},"orientation":{"type":"string","enum":["portrait","landscape"]}},"additionalProperties":true}}}}}},"responses":{"200":{"description":"Localization data updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Pin"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}}}}
```

## Get blockchain status for a pin

> Retrieves the blockchain sync status, transaction signature, explorer URL, and transaction history for a specific pin. Authentication required.

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"tags":[{"name":"Pins","description":"Pin management"},{"name":"Blockchain","description":"Blockchain synchronization operations for syncing entities to Solana blockchain"}],"servers":[{"url":"/api","description":"API base path"}],"security":[{"DynamicAuth":[]}],"components":{"securitySchemes":{"DynamicAuth":{"type":"apiKey","in":"header","name":"X-Dynamic-User-Id","description":"Dynamic authentication header. Required for blockchain endpoints and other authenticated operations."}},"schemas":{"PinBlockchainStatus":{"type":"object","properties":{"id":{"type":"integer","description":"Pin ID"},"blockchainSignature":{"type":"string","nullable":true,"description":"Blockchain transaction signature"},"blockchainTxUrl":{"type":"string","nullable":true,"format":"uri","description":"URL to view transaction on Solana Explorer"},"blockchainSyncedAt":{"type":"string","nullable":true,"format":"date-time","description":"When the pin was synced to blockchain"}}},"BlockchainTransaction":{"type":"object","properties":{"id":{"type":"integer","description":"Transaction record ID"},"entityType":{"type":"string","enum":["Zone","Pin","Gcp","File"],"description":"Type of entity"},"entityId":{"type":"integer","description":"ID of the entity"},"signature":{"type":"string","description":"Transaction signature"},"network":{"type":"string","enum":["devnet","mainnet","testnet"],"description":"Solana network"},"status":{"type":"string","enum":["pending","confirmed","failed"],"description":"Transaction status"},"blockHeight":{"type":"integer","nullable":true,"description":"Block height at which transaction was confirmed"},"createdAt":{"type":"string","format":"date-time","description":"When the transaction was created"}}},"Error":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"object"}}}},"responses":{"Unauthorized":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/pins/{id}/blockchain":{"get":{"summary":"Get blockchain status for a pin","description":"Retrieves the blockchain sync status, transaction signature, explorer URL, and transaction history for a specific pin. Authentication required.","tags":["Pins","Blockchain"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"},"description":"Pin ID"}],"responses":{"200":{"description":"Blockchain status retrieved successfully","content":{"application/json":{"schema":{"type":"object","required":["pin","transactions"],"properties":{"pin":{"$ref":"#/components/schemas/PinBlockchainStatus"},"transactions":{"type":"array","description":"Transaction history (last 10 transactions)","items":{"$ref":"#/components/schemas/BlockchainTransaction"}}}}}}},"400":{"description":"Invalid pin ID","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Pin not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Failed to fetch blockchain status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```


# Users

User-related operations

## Get zones for a user

> MVP: Public access, no authentication required.

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"tags":[{"name":"Users","description":"User-related operations"}],"servers":[{"url":"/api","description":"API base path"}],"paths":{"/users/{id}/zones":{"get":{"summary":"Get zones for a user","description":"MVP: Public access, no authentication required.","tags":["Users"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"List of user zones","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Zone"}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Forbidden"},"500":{"$ref":"#/components/responses/InternalServerError"}}}}},"components":{"schemas":{"Zone":{"type":"object","properties":{"id":{"type":"integer"},"title":{"type":"string"},"description":{"type":"string","nullable":true},"geojson":{"type":"object"},"userId":{"type":"string","nullable":true},"isGlobal":{"type":"boolean"},"map3dType":{"type":"string","nullable":true},"map3dUrl":{"type":"string","nullable":true},"map3dConfig":{"type":"object","nullable":true},"previewImageUrl":{"type":"string","nullable":true},"area":{"type":"number","nullable":true},"blockchainSignature":{"type":"string","nullable":true},"blockchainTxUrl":{"type":"string","nullable":true},"blockchainSyncedAt":{"type":"string","format":"date-time","nullable":true},"blockchainSyncStatus":{"type":"string","enum":["not_synced","syncing","synced","failed"]},"categories":{"type":"array","items":{"type":"object","properties":{"category":{"$ref":"#/components/schemas/Category"}}}},"pins":{"type":"array","items":{"$ref":"#/components/schemas/Pin"}},"_count":{"type":"object","properties":{"pins":{"type":"integer"}}},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"Category":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"description":{"type":"string","nullable":true},"color":{"type":"string"},"createdAt":{"type":"string","format":"date-time"}}},"Pin":{"type":"object","properties":{"id":{"type":"integer"},"title":{"type":"string"},"latitude":{"type":"number","nullable":true},"longitude":{"type":"number","nullable":true},"geoPose":{"type":"object","description":"JSON object containing geographic position and orientation data"},"contentType":{"type":"string","enum":["APK","GLB","IMAGE","VIDEO","URL","LENS","GCP","AUDIO","USDZ","BIN"]},"contentUrl":{"type":"string"},"localizationData":{"type":"object","nullable":true,"description":"ARWorld localization data for iOS AR experiences","properties":{"trackingMode":{"type":"string","enum":["WORLD","GEO"],"description":"AR tracking mode for the pin"},"worldmapURL":{"type":"string","format":"uri","nullable":true,"description":"S3 URL of the ARWorld binary data"}}},"zoneId":{"type":"integer","nullable":true},"gcpId":{"type":"integer","nullable":true},"userId":{"type":"string","nullable":true},"visibility":{"type":"string","enum":["public","private","friends"],"description":"Pin visibility setting"},"relocalizationPhotoUrl":{"type":"string","nullable":true,"description":"URL of the relocalization reference photo"},"scanInstructions":{"type":"string","nullable":true,"description":"Instructions for scanning/relocalizing"},"cameraPoseHint":{"type":"object","nullable":true,"description":"Camera pose hint for AR relocalization"},"blockchainSignature":{"type":"string","nullable":true},"blockchainTxUrl":{"type":"string","nullable":true},"blockchainSyncedAt":{"type":"string","format":"date-time","nullable":true},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"Error":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"object"}}}},"responses":{"Unauthorized":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InternalServerError":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}
```


# Zones

Zone management

## GET /zones

> List all zones

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"tags":[{"name":"Zones","description":"Zone management"}],"servers":[{"url":"/api","description":"API base path"}],"paths":{"/zones":{"get":{"summary":"List all zones","tags":["Zones"],"parameters":[{"name":"search","in":"query","schema":{"type":"string"}},{"name":"categoryIds","in":"query","schema":{"type":"string"},"description":"Comma-separated category IDs"}],"responses":{"200":{"description":"List of zones","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Zone"}}}}},"500":{"$ref":"#/components/responses/InternalServerError"}}}}},"components":{"schemas":{"Zone":{"type":"object","properties":{"id":{"type":"integer"},"title":{"type":"string"},"description":{"type":"string","nullable":true},"geojson":{"type":"object"},"userId":{"type":"string","nullable":true},"isGlobal":{"type":"boolean"},"map3dType":{"type":"string","nullable":true},"map3dUrl":{"type":"string","nullable":true},"map3dConfig":{"type":"object","nullable":true},"previewImageUrl":{"type":"string","nullable":true},"area":{"type":"number","nullable":true},"blockchainSignature":{"type":"string","nullable":true},"blockchainTxUrl":{"type":"string","nullable":true},"blockchainSyncedAt":{"type":"string","format":"date-time","nullable":true},"blockchainSyncStatus":{"type":"string","enum":["not_synced","syncing","synced","failed"]},"categories":{"type":"array","items":{"type":"object","properties":{"category":{"$ref":"#/components/schemas/Category"}}}},"pins":{"type":"array","items":{"$ref":"#/components/schemas/Pin"}},"_count":{"type":"object","properties":{"pins":{"type":"integer"}}},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"Category":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"description":{"type":"string","nullable":true},"color":{"type":"string"},"createdAt":{"type":"string","format":"date-time"}}},"Pin":{"type":"object","properties":{"id":{"type":"integer"},"title":{"type":"string"},"latitude":{"type":"number","nullable":true},"longitude":{"type":"number","nullable":true},"geoPose":{"type":"object","description":"JSON object containing geographic position and orientation data"},"contentType":{"type":"string","enum":["APK","GLB","IMAGE","VIDEO","URL","LENS","GCP","AUDIO","USDZ","BIN"]},"contentUrl":{"type":"string"},"localizationData":{"type":"object","nullable":true,"description":"ARWorld localization data for iOS AR experiences","properties":{"trackingMode":{"type":"string","enum":["WORLD","GEO"],"description":"AR tracking mode for the pin"},"worldmapURL":{"type":"string","format":"uri","nullable":true,"description":"S3 URL of the ARWorld binary data"}}},"zoneId":{"type":"integer","nullable":true},"gcpId":{"type":"integer","nullable":true},"userId":{"type":"string","nullable":true},"visibility":{"type":"string","enum":["public","private","friends"],"description":"Pin visibility setting"},"relocalizationPhotoUrl":{"type":"string","nullable":true,"description":"URL of the relocalization reference photo"},"scanInstructions":{"type":"string","nullable":true,"description":"Instructions for scanning/relocalizing"},"cameraPoseHint":{"type":"object","nullable":true,"description":"Camera pose hint for AR relocalization"},"blockchainSignature":{"type":"string","nullable":true},"blockchainTxUrl":{"type":"string","nullable":true},"blockchainSyncedAt":{"type":"string","format":"date-time","nullable":true},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"Error":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"object"}}}},"responses":{"InternalServerError":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}
```

## POST /zones

> Create a new zone

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"tags":[{"name":"Zones","description":"Zone management"}],"servers":[{"url":"/api","description":"API base path"}],"paths":{"/zones":{"post":{"summary":"Create a new zone","tags":["Zones"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["title","geojson"],"properties":{"title":{"type":"string"},"description":{"type":"string"},"geojson":{"type":"object"},"categoryIds":{"type":"array","items":{"type":"integer"}},"map3dType":{"type":"string","nullable":true},"map3dUrl":{"type":"string","nullable":true},"map3dConfig":{"type":"object","nullable":true}}}}}},"responses":{"201":{"description":"Zone created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Zone"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/InternalServerError"}}}}},"components":{"schemas":{"Zone":{"type":"object","properties":{"id":{"type":"integer"},"title":{"type":"string"},"description":{"type":"string","nullable":true},"geojson":{"type":"object"},"userId":{"type":"string","nullable":true},"isGlobal":{"type":"boolean"},"map3dType":{"type":"string","nullable":true},"map3dUrl":{"type":"string","nullable":true},"map3dConfig":{"type":"object","nullable":true},"previewImageUrl":{"type":"string","nullable":true},"area":{"type":"number","nullable":true},"blockchainSignature":{"type":"string","nullable":true},"blockchainTxUrl":{"type":"string","nullable":true},"blockchainSyncedAt":{"type":"string","format":"date-time","nullable":true},"blockchainSyncStatus":{"type":"string","enum":["not_synced","syncing","synced","failed"]},"categories":{"type":"array","items":{"type":"object","properties":{"category":{"$ref":"#/components/schemas/Category"}}}},"pins":{"type":"array","items":{"$ref":"#/components/schemas/Pin"}},"_count":{"type":"object","properties":{"pins":{"type":"integer"}}},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"Category":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"description":{"type":"string","nullable":true},"color":{"type":"string"},"createdAt":{"type":"string","format":"date-time"}}},"Pin":{"type":"object","properties":{"id":{"type":"integer"},"title":{"type":"string"},"latitude":{"type":"number","nullable":true},"longitude":{"type":"number","nullable":true},"geoPose":{"type":"object","description":"JSON object containing geographic position and orientation data"},"contentType":{"type":"string","enum":["APK","GLB","IMAGE","VIDEO","URL","LENS","GCP","AUDIO","USDZ","BIN"]},"contentUrl":{"type":"string"},"localizationData":{"type":"object","nullable":true,"description":"ARWorld localization data for iOS AR experiences","properties":{"trackingMode":{"type":"string","enum":["WORLD","GEO"],"description":"AR tracking mode for the pin"},"worldmapURL":{"type":"string","format":"uri","nullable":true,"description":"S3 URL of the ARWorld binary data"}}},"zoneId":{"type":"integer","nullable":true},"gcpId":{"type":"integer","nullable":true},"userId":{"type":"string","nullable":true},"visibility":{"type":"string","enum":["public","private","friends"],"description":"Pin visibility setting"},"relocalizationPhotoUrl":{"type":"string","nullable":true,"description":"URL of the relocalization reference photo"},"scanInstructions":{"type":"string","nullable":true,"description":"Instructions for scanning/relocalizing"},"cameraPoseHint":{"type":"object","nullable":true,"description":"Camera pose hint for AR relocalization"},"blockchainSignature":{"type":"string","nullable":true},"blockchainTxUrl":{"type":"string","nullable":true},"blockchainSyncedAt":{"type":"string","format":"date-time","nullable":true},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"Error":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"object"}}}},"responses":{"BadRequest":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unauthorized":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InternalServerError":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}
```

## Get zones owned by authenticated user

> Fetches all zones owned by the currently authenticated user, ordered by creation date (newest first).

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"tags":[{"name":"Zones","description":"Zone management"}],"servers":[{"url":"/api","description":"API base path"}],"security":[{"BearerAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","description":"Bearer token authentication. Pass the session token obtained from /auth/sync."}},"schemas":{"Zone":{"type":"object","properties":{"id":{"type":"integer"},"title":{"type":"string"},"description":{"type":"string","nullable":true},"geojson":{"type":"object"},"userId":{"type":"string","nullable":true},"isGlobal":{"type":"boolean"},"map3dType":{"type":"string","nullable":true},"map3dUrl":{"type":"string","nullable":true},"map3dConfig":{"type":"object","nullable":true},"previewImageUrl":{"type":"string","nullable":true},"area":{"type":"number","nullable":true},"blockchainSignature":{"type":"string","nullable":true},"blockchainTxUrl":{"type":"string","nullable":true},"blockchainSyncedAt":{"type":"string","format":"date-time","nullable":true},"blockchainSyncStatus":{"type":"string","enum":["not_synced","syncing","synced","failed"]},"categories":{"type":"array","items":{"type":"object","properties":{"category":{"$ref":"#/components/schemas/Category"}}}},"pins":{"type":"array","items":{"$ref":"#/components/schemas/Pin"}},"_count":{"type":"object","properties":{"pins":{"type":"integer"}}},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"Category":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"description":{"type":"string","nullable":true},"color":{"type":"string"},"createdAt":{"type":"string","format":"date-time"}}},"Pin":{"type":"object","properties":{"id":{"type":"integer"},"title":{"type":"string"},"latitude":{"type":"number","nullable":true},"longitude":{"type":"number","nullable":true},"geoPose":{"type":"object","description":"JSON object containing geographic position and orientation data"},"contentType":{"type":"string","enum":["APK","GLB","IMAGE","VIDEO","URL","LENS","GCP","AUDIO","USDZ","BIN"]},"contentUrl":{"type":"string"},"localizationData":{"type":"object","nullable":true,"description":"ARWorld localization data for iOS AR experiences","properties":{"trackingMode":{"type":"string","enum":["WORLD","GEO"],"description":"AR tracking mode for the pin"},"worldmapURL":{"type":"string","format":"uri","nullable":true,"description":"S3 URL of the ARWorld binary data"}}},"zoneId":{"type":"integer","nullable":true},"gcpId":{"type":"integer","nullable":true},"userId":{"type":"string","nullable":true},"visibility":{"type":"string","enum":["public","private","friends"],"description":"Pin visibility setting"},"relocalizationPhotoUrl":{"type":"string","nullable":true,"description":"URL of the relocalization reference photo"},"scanInstructions":{"type":"string","nullable":true,"description":"Instructions for scanning/relocalizing"},"cameraPoseHint":{"type":"object","nullable":true,"description":"Camera pose hint for AR relocalization"},"blockchainSignature":{"type":"string","nullable":true},"blockchainTxUrl":{"type":"string","nullable":true},"blockchainSyncedAt":{"type":"string","format":"date-time","nullable":true},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"Error":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"object"}}}},"responses":{"Unauthorized":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InternalServerError":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/zones/mine":{"get":{"summary":"Get zones owned by authenticated user","description":"Fetches all zones owned by the currently authenticated user, ordered by creation date (newest first).","tags":["Zones"],"responses":{"200":{"description":"List of user's zones","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Zone"}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/InternalServerError"}}}}}}
```

## GET /zones/{id}

> Get a zone by ID

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"tags":[{"name":"Zones","description":"Zone management"}],"servers":[{"url":"/api","description":"API base path"}],"paths":{"/zones/{id}":{"get":{"summary":"Get a zone by ID","tags":["Zones"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"},"description":"Zone ID"}],"responses":{"200":{"description":"Zone details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Zone"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"Validation failed"},"500":{"$ref":"#/components/responses/InternalServerError"}}}}},"components":{"schemas":{"Zone":{"type":"object","properties":{"id":{"type":"integer"},"title":{"type":"string"},"description":{"type":"string","nullable":true},"geojson":{"type":"object"},"userId":{"type":"string","nullable":true},"isGlobal":{"type":"boolean"},"map3dType":{"type":"string","nullable":true},"map3dUrl":{"type":"string","nullable":true},"map3dConfig":{"type":"object","nullable":true},"previewImageUrl":{"type":"string","nullable":true},"area":{"type":"number","nullable":true},"blockchainSignature":{"type":"string","nullable":true},"blockchainTxUrl":{"type":"string","nullable":true},"blockchainSyncedAt":{"type":"string","format":"date-time","nullable":true},"blockchainSyncStatus":{"type":"string","enum":["not_synced","syncing","synced","failed"]},"categories":{"type":"array","items":{"type":"object","properties":{"category":{"$ref":"#/components/schemas/Category"}}}},"pins":{"type":"array","items":{"$ref":"#/components/schemas/Pin"}},"_count":{"type":"object","properties":{"pins":{"type":"integer"}}},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"Category":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"description":{"type":"string","nullable":true},"color":{"type":"string"},"createdAt":{"type":"string","format":"date-time"}}},"Pin":{"type":"object","properties":{"id":{"type":"integer"},"title":{"type":"string"},"latitude":{"type":"number","nullable":true},"longitude":{"type":"number","nullable":true},"geoPose":{"type":"object","description":"JSON object containing geographic position and orientation data"},"contentType":{"type":"string","enum":["APK","GLB","IMAGE","VIDEO","URL","LENS","GCP","AUDIO","USDZ","BIN"]},"contentUrl":{"type":"string"},"localizationData":{"type":"object","nullable":true,"description":"ARWorld localization data for iOS AR experiences","properties":{"trackingMode":{"type":"string","enum":["WORLD","GEO"],"description":"AR tracking mode for the pin"},"worldmapURL":{"type":"string","format":"uri","nullable":true,"description":"S3 URL of the ARWorld binary data"}}},"zoneId":{"type":"integer","nullable":true},"gcpId":{"type":"integer","nullable":true},"userId":{"type":"string","nullable":true},"visibility":{"type":"string","enum":["public","private","friends"],"description":"Pin visibility setting"},"relocalizationPhotoUrl":{"type":"string","nullable":true,"description":"URL of the relocalization reference photo"},"scanInstructions":{"type":"string","nullable":true,"description":"Instructions for scanning/relocalizing"},"cameraPoseHint":{"type":"object","nullable":true,"description":"Camera pose hint for AR relocalization"},"blockchainSignature":{"type":"string","nullable":true},"blockchainTxUrl":{"type":"string","nullable":true},"blockchainSyncedAt":{"type":"string","format":"date-time","nullable":true},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"Error":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"object"}}}},"responses":{"BadRequest":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InternalServerError":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}
```

## PUT /zones/{id}

> Update a zone

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"tags":[{"name":"Zones","description":"Zone management"}],"servers":[{"url":"/api","description":"API base path"}],"paths":{"/zones/{id}":{"put":{"summary":"Update a zone","tags":["Zones"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["title"],"properties":{"title":{"type":"string"},"description":{"type":"string"},"geojson":{"type":"object"},"categoryIds":{"type":"array","items":{"type":"integer"}},"map3dType":{"type":"string","nullable":true},"map3dUrl":{"type":"string","nullable":true},"map3dConfig":{"type":"object","nullable":true}}}}}},"responses":{"200":{"description":"Zone updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Zone"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}}},"components":{"schemas":{"Zone":{"type":"object","properties":{"id":{"type":"integer"},"title":{"type":"string"},"description":{"type":"string","nullable":true},"geojson":{"type":"object"},"userId":{"type":"string","nullable":true},"isGlobal":{"type":"boolean"},"map3dType":{"type":"string","nullable":true},"map3dUrl":{"type":"string","nullable":true},"map3dConfig":{"type":"object","nullable":true},"previewImageUrl":{"type":"string","nullable":true},"area":{"type":"number","nullable":true},"blockchainSignature":{"type":"string","nullable":true},"blockchainTxUrl":{"type":"string","nullable":true},"blockchainSyncedAt":{"type":"string","format":"date-time","nullable":true},"blockchainSyncStatus":{"type":"string","enum":["not_synced","syncing","synced","failed"]},"categories":{"type":"array","items":{"type":"object","properties":{"category":{"$ref":"#/components/schemas/Category"}}}},"pins":{"type":"array","items":{"$ref":"#/components/schemas/Pin"}},"_count":{"type":"object","properties":{"pins":{"type":"integer"}}},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"Category":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"description":{"type":"string","nullable":true},"color":{"type":"string"},"createdAt":{"type":"string","format":"date-time"}}},"Pin":{"type":"object","properties":{"id":{"type":"integer"},"title":{"type":"string"},"latitude":{"type":"number","nullable":true},"longitude":{"type":"number","nullable":true},"geoPose":{"type":"object","description":"JSON object containing geographic position and orientation data"},"contentType":{"type":"string","enum":["APK","GLB","IMAGE","VIDEO","URL","LENS","GCP","AUDIO","USDZ","BIN"]},"contentUrl":{"type":"string"},"localizationData":{"type":"object","nullable":true,"description":"ARWorld localization data for iOS AR experiences","properties":{"trackingMode":{"type":"string","enum":["WORLD","GEO"],"description":"AR tracking mode for the pin"},"worldmapURL":{"type":"string","format":"uri","nullable":true,"description":"S3 URL of the ARWorld binary data"}}},"zoneId":{"type":"integer","nullable":true},"gcpId":{"type":"integer","nullable":true},"userId":{"type":"string","nullable":true},"visibility":{"type":"string","enum":["public","private","friends"],"description":"Pin visibility setting"},"relocalizationPhotoUrl":{"type":"string","nullable":true,"description":"URL of the relocalization reference photo"},"scanInstructions":{"type":"string","nullable":true,"description":"Instructions for scanning/relocalizing"},"cameraPoseHint":{"type":"object","nullable":true,"description":"Camera pose hint for AR relocalization"},"blockchainSignature":{"type":"string","nullable":true},"blockchainTxUrl":{"type":"string","nullable":true},"blockchainSyncedAt":{"type":"string","format":"date-time","nullable":true},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"Error":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"object"}}}},"responses":{"BadRequest":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InternalServerError":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}
```

## DELETE /zones/{id}

> Delete a zone

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"tags":[{"name":"Zones","description":"Zone management"}],"servers":[{"url":"/api","description":"API base path"}],"paths":{"/zones/{id}":{"delete":{"summary":"Delete a zone","tags":["Zones"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"204":{"description":"Zone deleted"},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}}},"components":{"responses":{"BadRequest":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InternalServerError":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"object"}}}}}}
```

## Get blockchain status for a zone

> Retrieves the blockchain sync status, transaction signature, explorer URL, and transaction history for a specific zone. Authentication required.

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"tags":[{"name":"Zones","description":"Zone management"},{"name":"Blockchain","description":"Blockchain synchronization operations for syncing entities to Solana blockchain"}],"servers":[{"url":"/api","description":"API base path"}],"security":[{"DynamicAuth":[]}],"components":{"securitySchemes":{"DynamicAuth":{"type":"apiKey","in":"header","name":"X-Dynamic-User-Id","description":"Dynamic authentication header. Required for blockchain endpoints and other authenticated operations."}},"schemas":{"ZoneBlockchainStatus":{"type":"object","properties":{"id":{"type":"integer","description":"Zone ID"},"blockchainSignature":{"type":"string","nullable":true,"description":"Blockchain transaction signature"},"blockchainTxUrl":{"type":"string","nullable":true,"format":"uri","description":"URL to view transaction on Solana Explorer"},"blockchainSyncedAt":{"type":"string","nullable":true,"format":"date-time","description":"When the zone was synced to blockchain"},"blockchainSyncStatus":{"type":"string","enum":["not_synced","syncing","synced","failed"],"description":"Current blockchain sync status"}}},"BlockchainTransaction":{"type":"object","properties":{"id":{"type":"integer","description":"Transaction record ID"},"entityType":{"type":"string","enum":["Zone","Pin","Gcp","File"],"description":"Type of entity"},"entityId":{"type":"integer","description":"ID of the entity"},"signature":{"type":"string","description":"Transaction signature"},"network":{"type":"string","enum":["devnet","mainnet","testnet"],"description":"Solana network"},"status":{"type":"string","enum":["pending","confirmed","failed"],"description":"Transaction status"},"blockHeight":{"type":"integer","nullable":true,"description":"Block height at which transaction was confirmed"},"createdAt":{"type":"string","format":"date-time","description":"When the transaction was created"}}},"Error":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"object"}}}},"responses":{"Unauthorized":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/zones/{id}/blockchain":{"get":{"summary":"Get blockchain status for a zone","description":"Retrieves the blockchain sync status, transaction signature, explorer URL, and transaction history for a specific zone. Authentication required.","tags":["Zones","Blockchain"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"},"description":"Zone ID"}],"responses":{"200":{"description":"Blockchain status retrieved successfully","content":{"application/json":{"schema":{"type":"object","required":["zone","transactions"],"properties":{"zone":{"$ref":"#/components/schemas/ZoneBlockchainStatus"},"transactions":{"type":"array","description":"Transaction history (last 10 transactions)","items":{"$ref":"#/components/schemas/BlockchainTransaction"}}}}}}},"400":{"description":"Invalid zone ID","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Zone not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Failed to fetch blockchain status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```

## GET /zones/{id}/pins

> Get pins for a zone

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"tags":[{"name":"Zones","description":"Zone management"}],"servers":[{"url":"/api","description":"API base path"}],"paths":{"/zones/{id}/pins":{"get":{"summary":"Get pins for a zone","tags":["Zones"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"List of pins in zone","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Pin"}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"500":{"$ref":"#/components/responses/InternalServerError"}}}}},"components":{"schemas":{"Pin":{"type":"object","properties":{"id":{"type":"integer"},"title":{"type":"string"},"latitude":{"type":"number","nullable":true},"longitude":{"type":"number","nullable":true},"geoPose":{"type":"object","description":"JSON object containing geographic position and orientation data"},"contentType":{"type":"string","enum":["APK","GLB","IMAGE","VIDEO","URL","LENS","GCP","AUDIO","USDZ","BIN"]},"contentUrl":{"type":"string"},"localizationData":{"type":"object","nullable":true,"description":"ARWorld localization data for iOS AR experiences","properties":{"trackingMode":{"type":"string","enum":["WORLD","GEO"],"description":"AR tracking mode for the pin"},"worldmapURL":{"type":"string","format":"uri","nullable":true,"description":"S3 URL of the ARWorld binary data"}}},"zoneId":{"type":"integer","nullable":true},"gcpId":{"type":"integer","nullable":true},"userId":{"type":"string","nullable":true},"visibility":{"type":"string","enum":["public","private","friends"],"description":"Pin visibility setting"},"relocalizationPhotoUrl":{"type":"string","nullable":true,"description":"URL of the relocalization reference photo"},"scanInstructions":{"type":"string","nullable":true,"description":"Instructions for scanning/relocalizing"},"cameraPoseHint":{"type":"object","nullable":true,"description":"Camera pose hint for AR relocalization"},"blockchainSignature":{"type":"string","nullable":true},"blockchainTxUrl":{"type":"string","nullable":true},"blockchainSyncedAt":{"type":"string","format":"date-time","nullable":true},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"Error":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"object"}}}},"responses":{"BadRequest":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InternalServerError":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}
```

## POST /zones/{id}/pins

> Create a pin in a zone

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"tags":[{"name":"Zones","description":"Zone management"}],"servers":[{"url":"/api","description":"API base path"}],"paths":{"/zones/{id}/pins":{"post":{"summary":"Create a pin in a zone","tags":["Zones"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["title","contentUrl","contentType","geoPose"],"properties":{"title":{"type":"string"},"contentUrl":{"type":"string","format":"uri"},"contentType":{"type":"string","enum":["IMAGE","VIDEO","GLB","USDZ","BIN","URL","APK","LENS","GCP","AUDIO","AR_WORLD"]},"geoPose":{"$ref":"#/components/schemas/GeoPose"},"gcpData":{"type":"string"},"gcpType":{"type":"string","enum":["SINGLE","MULTIPLE"]},"altitude":{"type":"number"},"aprilTagId":{"type":"integer","minimum":0,"maximum":50},"photoUrls":{"type":"array","items":{"type":"string","format":"uri"},"maxItems":3},"localizationData":{"$ref":"#/components/schemas/LocalizationData","description":"ARWorld localization data for iOS AR experiences"}}}}}},"responses":{"201":{"description":"Pin created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Pin"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"500":{"$ref":"#/components/responses/InternalServerError"}}}}},"components":{"schemas":{"GeoPose":{"type":"object","required":["position","angles"],"properties":{"position":{"$ref":"#/components/schemas/Position"},"angles":{"$ref":"#/components/schemas/YprAngles"}}},"Position":{"type":"object","required":["lat","lon"],"properties":{"lat":{"type":"number","description":"Latitude in decimal degrees"},"lon":{"type":"number","description":"Longitude in decimal degrees"},"h":{"type":"number","description":"Height/altitude in meters","default":0}}},"YprAngles":{"type":"object","required":["yaw","pitch","roll"],"properties":{"yaw":{"type":"number","description":"Yaw angle in degrees","default":0},"pitch":{"type":"number","description":"Pitch angle in degrees","default":0},"roll":{"type":"number","description":"Roll angle in degrees","default":0}}},"LocalizationData":{"type":"object","description":"ARWorld localization data for iOS AR experiences","properties":{"trackingMode":{"type":"string","enum":["WORLD","GEO"],"description":"AR tracking mode for the pin"},"worldmapURL":{"type":"string","format":"uri","nullable":true,"description":"S3 URL of the ARWorld binary data"},"localizationPhotoURL":{"type":"string","format":"uri","nullable":true,"description":"URL of the photo used for re-localization"}}},"Pin":{"type":"object","properties":{"id":{"type":"integer"},"title":{"type":"string"},"latitude":{"type":"number","nullable":true},"longitude":{"type":"number","nullable":true},"geoPose":{"type":"object","description":"JSON object containing geographic position and orientation data"},"contentType":{"type":"string","enum":["APK","GLB","IMAGE","VIDEO","URL","LENS","GCP","AUDIO","USDZ","BIN"]},"contentUrl":{"type":"string"},"localizationData":{"type":"object","nullable":true,"description":"ARWorld localization data for iOS AR experiences","properties":{"trackingMode":{"type":"string","enum":["WORLD","GEO"],"description":"AR tracking mode for the pin"},"worldmapURL":{"type":"string","format":"uri","nullable":true,"description":"S3 URL of the ARWorld binary data"}}},"zoneId":{"type":"integer","nullable":true},"gcpId":{"type":"integer","nullable":true},"userId":{"type":"string","nullable":true},"visibility":{"type":"string","enum":["public","private","friends"],"description":"Pin visibility setting"},"relocalizationPhotoUrl":{"type":"string","nullable":true,"description":"URL of the relocalization reference photo"},"scanInstructions":{"type":"string","nullable":true,"description":"Instructions for scanning/relocalizing"},"cameraPoseHint":{"type":"object","nullable":true,"description":"Camera pose hint for AR relocalization"},"blockchainSignature":{"type":"string","nullable":true},"blockchainTxUrl":{"type":"string","nullable":true},"blockchainSyncedAt":{"type":"string","format":"date-time","nullable":true},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"Error":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"object"}}}},"responses":{"BadRequest":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InternalServerError":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}
```

## POST /zones/{id}/gcp

> Create a GCP in a zone

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"tags":[{"name":"Zones","description":"Zone management"}],"servers":[{"url":"/api","description":"API base path"}],"paths":{"/zones/{id}/gcp":{"post":{"summary":"Create a GCP in a zone","tags":["Zones"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GCPCreateRequest"}}}},"responses":{"201":{"description":"GCP created in zone","content":{"application/json":{"schema":{"type":"object","properties":{"gcp":{"$ref":"#/components/schemas/GCP"},"pin":{"$ref":"#/components/schemas/Pin"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"500":{"$ref":"#/components/responses/InternalServerError"}}}}},"components":{"schemas":{"GCPCreateRequest":{"type":"object","required":["title","gcpType","geoPose"],"properties":{"title":{"type":"string"},"description":{"type":"string"},"gcpType":{"type":"string","enum":["SINGLE","MULTIPLE"]},"geoPose":{"$ref":"#/components/schemas/GeoPose"},"altitude":{"type":"number"},"aprilTagId":{"type":"integer","minimum":0,"maximum":50},"gcpData":{"type":"string"},"photoUrls":{"type":"array","items":{"type":"string","format":"uri"},"maxItems":3},"uploadedFileIds":{"type":"array","items":{"type":"integer"}},"zoneId":{"type":"integer"}}},"GeoPose":{"type":"object","required":["position","angles"],"properties":{"position":{"$ref":"#/components/schemas/Position"},"angles":{"$ref":"#/components/schemas/YprAngles"}}},"Position":{"type":"object","required":["lat","lon"],"properties":{"lat":{"type":"number","description":"Latitude in decimal degrees"},"lon":{"type":"number","description":"Longitude in decimal degrees"},"h":{"type":"number","description":"Height/altitude in meters","default":0}}},"YprAngles":{"type":"object","required":["yaw","pitch","roll"],"properties":{"yaw":{"type":"number","description":"Yaw angle in degrees","default":0},"pitch":{"type":"number","description":"Pitch angle in degrees","default":0},"roll":{"type":"number","description":"Roll angle in degrees","default":0}}},"GCP":{"type":"object","properties":{"id":{"type":"integer"},"title":{"type":"string"},"description":{"type":"string","nullable":true},"gcpType":{"type":"string","enum":["SINGLE","MULTIPLE"]},"altitude":{"type":"number","nullable":true},"aprilTagId":{"type":"integer","nullable":true,"minimum":0,"maximum":50},"gcpData":{"type":"string","nullable":true},"photoUrls":{"type":"array","items":{"type":"string"}},"zoneId":{"type":"integer","nullable":true},"pin":{"$ref":"#/components/schemas/Pin","nullable":true},"zone":{"type":"object","nullable":true,"properties":{"id":{"type":"integer"},"title":{"type":"string"}}},"uploadedFiles":{"type":"array","items":{"$ref":"#/components/schemas/UploadedFile"}},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"Pin":{"type":"object","properties":{"id":{"type":"integer"},"title":{"type":"string"},"latitude":{"type":"number","nullable":true},"longitude":{"type":"number","nullable":true},"geoPose":{"type":"object","description":"JSON object containing geographic position and orientation data"},"contentType":{"type":"string","enum":["APK","GLB","IMAGE","VIDEO","URL","LENS","GCP","AUDIO","USDZ","BIN"]},"contentUrl":{"type":"string"},"localizationData":{"type":"object","nullable":true,"description":"ARWorld localization data for iOS AR experiences","properties":{"trackingMode":{"type":"string","enum":["WORLD","GEO"],"description":"AR tracking mode for the pin"},"worldmapURL":{"type":"string","format":"uri","nullable":true,"description":"S3 URL of the ARWorld binary data"}}},"zoneId":{"type":"integer","nullable":true},"gcpId":{"type":"integer","nullable":true},"userId":{"type":"string","nullable":true},"visibility":{"type":"string","enum":["public","private","friends"],"description":"Pin visibility setting"},"relocalizationPhotoUrl":{"type":"string","nullable":true,"description":"URL of the relocalization reference photo"},"scanInstructions":{"type":"string","nullable":true,"description":"Instructions for scanning/relocalizing"},"cameraPoseHint":{"type":"object","nullable":true,"description":"Camera pose hint for AR relocalization"},"blockchainSignature":{"type":"string","nullable":true},"blockchainTxUrl":{"type":"string","nullable":true},"blockchainSyncedAt":{"type":"string","format":"date-time","nullable":true},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"UploadedFile":{"type":"object","properties":{"id":{"type":"integer"},"fileName":{"type":"string"},"fileUrl":{"type":"string"},"fileSize":{"type":"integer","nullable":true},"mimeType":{"type":"string","nullable":true},"bucketPath":{"type":"string","nullable":true},"gcpId":{"type":"integer","nullable":true},"userId":{"type":"string","nullable":true},"uploadedAt":{"type":"string","format":"date-time"}}},"Error":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"object"}}}},"responses":{"BadRequest":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InternalServerError":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}
```

## GET /zones/{id}/gcp/check-april-tag

> Check April tag availability in a zone

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"tags":[{"name":"Zones","description":"Zone management"}],"servers":[{"url":"/api","description":"API base path"}],"paths":{"/zones/{id}/gcp/check-april-tag":{"get":{"summary":"Check April tag availability in a zone","tags":["Zones"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}},{"name":"aprilTagId","in":"query","required":true,"schema":{"type":"integer","minimum":0,"maximum":50}},{"name":"latitude","in":"query","schema":{"type":"number"}},{"name":"longitude","in":"query","schema":{"type":"number"}},{"name":"excludeGcpId","in":"query","schema":{"type":"integer"}}],"responses":{"200":{"description":"April tag validation result","content":{"application/json":{"schema":{"type":"object","properties":{"available":{"type":"boolean"},"conflicts":{"type":"array","items":{"type":"object"}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"500":{"$ref":"#/components/responses/InternalServerError"}}}}},"components":{"responses":{"BadRequest":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InternalServerError":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"object"}}}}}}
```

## GET /zones/global-zone

> Get the global zone

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"tags":[{"name":"Zones","description":"Zone management"}],"servers":[{"url":"/api","description":"API base path"}],"paths":{"/zones/global-zone":{"get":{"summary":"Get the global zone","tags":["Zones"],"responses":{"200":{"description":"Global zone","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Zone"}}}},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"Validation failed"},"500":{"$ref":"#/components/responses/InternalServerError"}}}}},"components":{"schemas":{"Zone":{"type":"object","properties":{"id":{"type":"integer"},"title":{"type":"string"},"description":{"type":"string","nullable":true},"geojson":{"type":"object"},"userId":{"type":"string","nullable":true},"isGlobal":{"type":"boolean"},"map3dType":{"type":"string","nullable":true},"map3dUrl":{"type":"string","nullable":true},"map3dConfig":{"type":"object","nullable":true},"previewImageUrl":{"type":"string","nullable":true},"area":{"type":"number","nullable":true},"blockchainSignature":{"type":"string","nullable":true},"blockchainTxUrl":{"type":"string","nullable":true},"blockchainSyncedAt":{"type":"string","format":"date-time","nullable":true},"blockchainSyncStatus":{"type":"string","enum":["not_synced","syncing","synced","failed"]},"categories":{"type":"array","items":{"type":"object","properties":{"category":{"$ref":"#/components/schemas/Category"}}}},"pins":{"type":"array","items":{"$ref":"#/components/schemas/Pin"}},"_count":{"type":"object","properties":{"pins":{"type":"integer"}}},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"Category":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"description":{"type":"string","nullable":true},"color":{"type":"string"},"createdAt":{"type":"string","format":"date-time"}}},"Pin":{"type":"object","properties":{"id":{"type":"integer"},"title":{"type":"string"},"latitude":{"type":"number","nullable":true},"longitude":{"type":"number","nullable":true},"geoPose":{"type":"object","description":"JSON object containing geographic position and orientation data"},"contentType":{"type":"string","enum":["APK","GLB","IMAGE","VIDEO","URL","LENS","GCP","AUDIO","USDZ","BIN"]},"contentUrl":{"type":"string"},"localizationData":{"type":"object","nullable":true,"description":"ARWorld localization data for iOS AR experiences","properties":{"trackingMode":{"type":"string","enum":["WORLD","GEO"],"description":"AR tracking mode for the pin"},"worldmapURL":{"type":"string","format":"uri","nullable":true,"description":"S3 URL of the ARWorld binary data"}}},"zoneId":{"type":"integer","nullable":true},"gcpId":{"type":"integer","nullable":true},"userId":{"type":"string","nullable":true},"visibility":{"type":"string","enum":["public","private","friends"],"description":"Pin visibility setting"},"relocalizationPhotoUrl":{"type":"string","nullable":true,"description":"URL of the relocalization reference photo"},"scanInstructions":{"type":"string","nullable":true,"description":"Instructions for scanning/relocalizing"},"cameraPoseHint":{"type":"object","nullable":true,"description":"Camera pose hint for AR relocalization"},"blockchainSignature":{"type":"string","nullable":true},"blockchainTxUrl":{"type":"string","nullable":true},"blockchainSyncedAt":{"type":"string","format":"date-time","nullable":true},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"Error":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"object"}}}},"responses":{"NotFound":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InternalServerError":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}
```


# Location

Location-related operations

## Find nearby pins using spatial query

> Returns pins within a specified radius of a geographic location using PostGIS spatial queries. Radius is in meters.

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"tags":[{"name":"Location","description":"Location-related operations"}],"servers":[{"url":"/api","description":"API base path"}],"paths":{"/location/pins":{"get":{"summary":"Find nearby pins using spatial query","description":"Returns pins within a specified radius of a geographic location using PostGIS spatial queries. Radius is in meters.","tags":["Location"],"parameters":[{"name":"lat","in":"query","required":true,"schema":{"type":"string"},"description":"Latitude in decimal degrees (will be parsed as a number)"},{"name":"lon","in":"query","required":true,"schema":{"type":"string"},"description":"Longitude in decimal degrees (will be parsed as a number)"},{"name":"radius","in":"query","required":false,"schema":{"type":"string","default":"1000","minimum":0,"maximum":100000},"description":"Search radius in meters (default: 1000m / 1km, min: 0m, max: 100,000m, will be parsed as a number)"}],"responses":{"200":{"description":"List of nearby pins with distances","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NearbyPinList"}}}},"500":{"$ref":"#/components/responses/InternalServerError"}}}}},"components":{"schemas":{"NearbyPinList":{"type":"object","required":["pins"],"properties":{"pins":{"type":"array","items":{"$ref":"#/components/schemas/NearbyPin"},"description":"Array of pins sorted by distance from query point"}}},"NearbyPin":{"allOf":[{"$ref":"#/components/schemas/Pin"},{"type":"object","required":["distance"],"properties":{"distance":{"type":"number","description":"Distance from query point in meters"}}}]},"Pin":{"type":"object","properties":{"id":{"type":"integer"},"title":{"type":"string"},"latitude":{"type":"number","nullable":true},"longitude":{"type":"number","nullable":true},"geoPose":{"type":"object","description":"JSON object containing geographic position and orientation data"},"contentType":{"type":"string","enum":["APK","GLB","IMAGE","VIDEO","URL","LENS","GCP","AUDIO","USDZ","BIN"]},"contentUrl":{"type":"string"},"localizationData":{"type":"object","nullable":true,"description":"ARWorld localization data for iOS AR experiences","properties":{"trackingMode":{"type":"string","enum":["WORLD","GEO"],"description":"AR tracking mode for the pin"},"worldmapURL":{"type":"string","format":"uri","nullable":true,"description":"S3 URL of the ARWorld binary data"}}},"zoneId":{"type":"integer","nullable":true},"gcpId":{"type":"integer","nullable":true},"userId":{"type":"string","nullable":true},"visibility":{"type":"string","enum":["public","private","friends"],"description":"Pin visibility setting"},"relocalizationPhotoUrl":{"type":"string","nullable":true,"description":"URL of the relocalization reference photo"},"scanInstructions":{"type":"string","nullable":true,"description":"Instructions for scanning/relocalizing"},"cameraPoseHint":{"type":"object","nullable":true,"description":"Camera pose hint for AR relocalization"},"blockchainSignature":{"type":"string","nullable":true},"blockchainTxUrl":{"type":"string","nullable":true},"blockchainSyncedAt":{"type":"string","format":"date-time","nullable":true},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"Error":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"object"}}}},"responses":{"InternalServerError":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}
```


# Maps

Map management, pin ordering, sharing, and visit tracking

## List all maps

> Fetches all maps accessible to the authenticated user, including their pins, visit counts, and GCP details.

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"tags":[{"name":"Maps","description":"Map management, pin ordering, sharing, and visit tracking"}],"servers":[{"url":"/api","description":"API base path"}],"security":[{"BearerAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","description":"Bearer token authentication. Pass the session token obtained from /auth/sync."}},"schemas":{"Map":{"type":"object","properties":{"id":{"type":"integer"},"title":{"type":"string"},"description":{"type":"string","nullable":true},"isPublic":{"type":"boolean"},"coverImage":{"type":"string","nullable":true},"latitude":{"type":"number","nullable":true},"longitude":{"type":"number","nullable":true},"userId":{"type":"string"},"pins":{"type":"array","items":{"type":"object","properties":{"pin":{"$ref":"#/components/schemas/Pin"},"order":{"type":"integer"}}}},"_count":{"type":"object","properties":{"visits":{"type":"integer"}}},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"deletedAt":{"type":"string","format":"date-time","nullable":true}}},"Pin":{"type":"object","properties":{"id":{"type":"integer"},"title":{"type":"string"},"latitude":{"type":"number","nullable":true},"longitude":{"type":"number","nullable":true},"geoPose":{"type":"object","description":"JSON object containing geographic position and orientation data"},"contentType":{"type":"string","enum":["APK","GLB","IMAGE","VIDEO","URL","LENS","GCP","AUDIO","USDZ","BIN"]},"contentUrl":{"type":"string"},"localizationData":{"type":"object","nullable":true,"description":"ARWorld localization data for iOS AR experiences","properties":{"trackingMode":{"type":"string","enum":["WORLD","GEO"],"description":"AR tracking mode for the pin"},"worldmapURL":{"type":"string","format":"uri","nullable":true,"description":"S3 URL of the ARWorld binary data"}}},"zoneId":{"type":"integer","nullable":true},"gcpId":{"type":"integer","nullable":true},"userId":{"type":"string","nullable":true},"visibility":{"type":"string","enum":["public","private","friends"],"description":"Pin visibility setting"},"relocalizationPhotoUrl":{"type":"string","nullable":true,"description":"URL of the relocalization reference photo"},"scanInstructions":{"type":"string","nullable":true,"description":"Instructions for scanning/relocalizing"},"cameraPoseHint":{"type":"object","nullable":true,"description":"Camera pose hint for AR relocalization"},"blockchainSignature":{"type":"string","nullable":true},"blockchainTxUrl":{"type":"string","nullable":true},"blockchainSyncedAt":{"type":"string","format":"date-time","nullable":true},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"Error":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"object"}}}},"responses":{"Unauthorized":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InternalServerError":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/maps":{"get":{"summary":"List all maps","description":"Fetches all maps accessible to the authenticated user, including their pins, visit counts, and GCP details.","tags":["Maps"],"parameters":[{"name":"isPublic","in":"query","schema":{"type":"string","enum":["true"]},"description":"Filter for public maps only"},{"name":"friends","in":"query","schema":{"type":"string","enum":["true"]},"description":"Filter for friends' public maps"}],"responses":{"200":{"description":"List of maps","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Map"}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/InternalServerError"}}}}}}
```

## POST /maps

> Create a new map

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"tags":[{"name":"Maps","description":"Map management, pin ordering, sharing, and visit tracking"}],"servers":[{"url":"/api","description":"API base path"}],"security":[{"BearerAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","description":"Bearer token authentication. Pass the session token obtained from /auth/sync."}},"schemas":{"Map":{"type":"object","properties":{"id":{"type":"integer"},"title":{"type":"string"},"description":{"type":"string","nullable":true},"isPublic":{"type":"boolean"},"coverImage":{"type":"string","nullable":true},"latitude":{"type":"number","nullable":true},"longitude":{"type":"number","nullable":true},"userId":{"type":"string"},"pins":{"type":"array","items":{"type":"object","properties":{"pin":{"$ref":"#/components/schemas/Pin"},"order":{"type":"integer"}}}},"_count":{"type":"object","properties":{"visits":{"type":"integer"}}},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"deletedAt":{"type":"string","format":"date-time","nullable":true}}},"Pin":{"type":"object","properties":{"id":{"type":"integer"},"title":{"type":"string"},"latitude":{"type":"number","nullable":true},"longitude":{"type":"number","nullable":true},"geoPose":{"type":"object","description":"JSON object containing geographic position and orientation data"},"contentType":{"type":"string","enum":["APK","GLB","IMAGE","VIDEO","URL","LENS","GCP","AUDIO","USDZ","BIN"]},"contentUrl":{"type":"string"},"localizationData":{"type":"object","nullable":true,"description":"ARWorld localization data for iOS AR experiences","properties":{"trackingMode":{"type":"string","enum":["WORLD","GEO"],"description":"AR tracking mode for the pin"},"worldmapURL":{"type":"string","format":"uri","nullable":true,"description":"S3 URL of the ARWorld binary data"}}},"zoneId":{"type":"integer","nullable":true},"gcpId":{"type":"integer","nullable":true},"userId":{"type":"string","nullable":true},"visibility":{"type":"string","enum":["public","private","friends"],"description":"Pin visibility setting"},"relocalizationPhotoUrl":{"type":"string","nullable":true,"description":"URL of the relocalization reference photo"},"scanInstructions":{"type":"string","nullable":true,"description":"Instructions for scanning/relocalizing"},"cameraPoseHint":{"type":"object","nullable":true,"description":"Camera pose hint for AR relocalization"},"blockchainSignature":{"type":"string","nullable":true},"blockchainTxUrl":{"type":"string","nullable":true},"blockchainSyncedAt":{"type":"string","format":"date-time","nullable":true},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"Error":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"object"}}}},"responses":{"BadRequest":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unauthorized":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InternalServerError":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/maps":{"post":{"summary":"Create a new map","tags":["Maps"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["title"],"properties":{"title":{"type":"string","minLength":1},"description":{"type":"string"},"isPublic":{"type":"boolean","default":false},"coverImage":{"type":"string","format":"uri"},"latitude":{"type":"number","minimum":-90,"maximum":90},"longitude":{"type":"number","minimum":-180,"maximum":180}}}}}},"responses":{"201":{"description":"Map created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Map"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/InternalServerError"}}}}}}
```

## GET /maps/{id}

> Get a map by ID

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"tags":[{"name":"Maps","description":"Map management, pin ordering, sharing, and visit tracking"}],"servers":[{"url":"/api","description":"API base path"}],"security":[{"BearerAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","description":"Bearer token authentication. Pass the session token obtained from /auth/sync."}},"schemas":{"Map":{"type":"object","properties":{"id":{"type":"integer"},"title":{"type":"string"},"description":{"type":"string","nullable":true},"isPublic":{"type":"boolean"},"coverImage":{"type":"string","nullable":true},"latitude":{"type":"number","nullable":true},"longitude":{"type":"number","nullable":true},"userId":{"type":"string"},"pins":{"type":"array","items":{"type":"object","properties":{"pin":{"$ref":"#/components/schemas/Pin"},"order":{"type":"integer"}}}},"_count":{"type":"object","properties":{"visits":{"type":"integer"}}},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"deletedAt":{"type":"string","format":"date-time","nullable":true}}},"Pin":{"type":"object","properties":{"id":{"type":"integer"},"title":{"type":"string"},"latitude":{"type":"number","nullable":true},"longitude":{"type":"number","nullable":true},"geoPose":{"type":"object","description":"JSON object containing geographic position and orientation data"},"contentType":{"type":"string","enum":["APK","GLB","IMAGE","VIDEO","URL","LENS","GCP","AUDIO","USDZ","BIN"]},"contentUrl":{"type":"string"},"localizationData":{"type":"object","nullable":true,"description":"ARWorld localization data for iOS AR experiences","properties":{"trackingMode":{"type":"string","enum":["WORLD","GEO"],"description":"AR tracking mode for the pin"},"worldmapURL":{"type":"string","format":"uri","nullable":true,"description":"S3 URL of the ARWorld binary data"}}},"zoneId":{"type":"integer","nullable":true},"gcpId":{"type":"integer","nullable":true},"userId":{"type":"string","nullable":true},"visibility":{"type":"string","enum":["public","private","friends"],"description":"Pin visibility setting"},"relocalizationPhotoUrl":{"type":"string","nullable":true,"description":"URL of the relocalization reference photo"},"scanInstructions":{"type":"string","nullable":true,"description":"Instructions for scanning/relocalizing"},"cameraPoseHint":{"type":"object","nullable":true,"description":"Camera pose hint for AR relocalization"},"blockchainSignature":{"type":"string","nullable":true},"blockchainTxUrl":{"type":"string","nullable":true},"blockchainSyncedAt":{"type":"string","format":"date-time","nullable":true},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"Error":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"object"}}}},"responses":{"BadRequest":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unauthorized":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InternalServerError":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/maps/{id}":{"get":{"summary":"Get a map by ID","tags":["Maps"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"},"description":"Map ID"}],"responses":{"200":{"description":"Map details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Map"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}}}}
```

## DELETE /maps/{id}

> Delete a map

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"tags":[{"name":"Maps","description":"Map management, pin ordering, sharing, and visit tracking"}],"servers":[{"url":"/api","description":"API base path"}],"security":[{"BearerAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","description":"Bearer token authentication. Pass the session token obtained from /auth/sync."}},"responses":{"BadRequest":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unauthorized":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InternalServerError":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"object"}}}}},"paths":{"/maps/{id}":{"delete":{"summary":"Delete a map","tags":["Maps"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Map deleted","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"},"deletedPins":{"type":"integer"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}}}}
```

## PATCH /maps/{id}

> Update a map

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"tags":[{"name":"Maps","description":"Map management, pin ordering, sharing, and visit tracking"}],"servers":[{"url":"/api","description":"API base path"}],"security":[{"BearerAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","description":"Bearer token authentication. Pass the session token obtained from /auth/sync."}},"schemas":{"Map":{"type":"object","properties":{"id":{"type":"integer"},"title":{"type":"string"},"description":{"type":"string","nullable":true},"isPublic":{"type":"boolean"},"coverImage":{"type":"string","nullable":true},"latitude":{"type":"number","nullable":true},"longitude":{"type":"number","nullable":true},"userId":{"type":"string"},"pins":{"type":"array","items":{"type":"object","properties":{"pin":{"$ref":"#/components/schemas/Pin"},"order":{"type":"integer"}}}},"_count":{"type":"object","properties":{"visits":{"type":"integer"}}},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"deletedAt":{"type":"string","format":"date-time","nullable":true}}},"Pin":{"type":"object","properties":{"id":{"type":"integer"},"title":{"type":"string"},"latitude":{"type":"number","nullable":true},"longitude":{"type":"number","nullable":true},"geoPose":{"type":"object","description":"JSON object containing geographic position and orientation data"},"contentType":{"type":"string","enum":["APK","GLB","IMAGE","VIDEO","URL","LENS","GCP","AUDIO","USDZ","BIN"]},"contentUrl":{"type":"string"},"localizationData":{"type":"object","nullable":true,"description":"ARWorld localization data for iOS AR experiences","properties":{"trackingMode":{"type":"string","enum":["WORLD","GEO"],"description":"AR tracking mode for the pin"},"worldmapURL":{"type":"string","format":"uri","nullable":true,"description":"S3 URL of the ARWorld binary data"}}},"zoneId":{"type":"integer","nullable":true},"gcpId":{"type":"integer","nullable":true},"userId":{"type":"string","nullable":true},"visibility":{"type":"string","enum":["public","private","friends"],"description":"Pin visibility setting"},"relocalizationPhotoUrl":{"type":"string","nullable":true,"description":"URL of the relocalization reference photo"},"scanInstructions":{"type":"string","nullable":true,"description":"Instructions for scanning/relocalizing"},"cameraPoseHint":{"type":"object","nullable":true,"description":"Camera pose hint for AR relocalization"},"blockchainSignature":{"type":"string","nullable":true},"blockchainTxUrl":{"type":"string","nullable":true},"blockchainSyncedAt":{"type":"string","format":"date-time","nullable":true},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"Error":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"object"}}}},"responses":{"BadRequest":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unauthorized":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InternalServerError":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/maps/{id}":{"patch":{"summary":"Update a map","tags":["Maps"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string","minLength":1},"description":{"type":"string","nullable":true},"isPublic":{"type":"boolean"},"coverImage":{"type":"string","format":"uri","nullable":true},"latitude":{"type":"number","minimum":-90,"maximum":90,"nullable":true},"longitude":{"type":"number","minimum":-180,"maximum":180,"nullable":true}}}}}},"responses":{"200":{"description":"Map updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Map"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}}}}
```

## Get map completion progress

> Gets the authenticated user's completion progress for a map, including which pins have been visited.

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"tags":[{"name":"Maps","description":"Map management, pin ordering, sharing, and visit tracking"}],"servers":[{"url":"/api","description":"API base path"}],"security":[{"BearerAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","description":"Bearer token authentication. Pass the session token obtained from /auth/sync."}},"schemas":{"MapProgress":{"type":"object","properties":{"mapId":{"type":"integer"},"mapTitle":{"type":"string"},"totalPins":{"type":"integer"},"visitedPins":{"type":"integer"},"completionPercentage":{"type":"number"},"visitedPinIds":{"type":"array","items":{"type":"integer"}},"pins":{"type":"array","items":{"type":"object","properties":{"pinId":{"type":"integer"},"order":{"type":"integer"},"title":{"type":"string"},"visited":{"type":"boolean"},"visitedAt":{"type":"string","format":"date-time","nullable":true}}}}}},"Error":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"object"}}}},"responses":{"BadRequest":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unauthorized":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InternalServerError":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/maps/{id}/progress":{"get":{"summary":"Get map completion progress","description":"Gets the authenticated user's completion progress for a map, including which pins have been visited.","tags":["Maps"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"},"description":"Map ID"}],"responses":{"200":{"description":"Map progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MapProgress"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}}}}
```

## Reset map visit progress

> Resets all visit records for a map. WARNING: This affects all users' progress for this map.

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"tags":[{"name":"Maps","description":"Map management, pin ordering, sharing, and visit tracking"}],"servers":[{"url":"/api","description":"API base path"}],"security":[{"BearerAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","description":"Bearer token authentication. Pass the session token obtained from /auth/sync."}},"responses":{"BadRequest":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unauthorized":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InternalServerError":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"object"}}}}},"paths":{"/maps/{id}/reset":{"post":{"summary":"Reset map visit progress","description":"Resets all visit records for a map. WARNING: This affects all users' progress for this map.","tags":["Maps"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Progress reset successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"deletedVisits":{"type":"integer"},"message":{"type":"string"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}}}}
```

## POST /maps/{id}/pins

> Add pin to map

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"tags":[{"name":"Maps","description":"Map management, pin ordering, sharing, and visit tracking"}],"servers":[{"url":"/api","description":"API base path"}],"security":[{"BearerAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","description":"Bearer token authentication. Pass the session token obtained from /auth/sync."}},"schemas":{"MapPin":{"type":"object","properties":{"mapId":{"type":"integer"},"pinId":{"type":"integer"},"order":{"type":"integer"}}},"Error":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"object"}}}},"responses":{"BadRequest":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unauthorized":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InternalServerError":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/maps/{id}/pins":{"post":{"summary":"Add pin to map","tags":["Maps"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["pinId"],"properties":{"pinId":{"type":"integer","minimum":1},"order":{"type":"integer","minimum":0,"default":0}}}}}},"responses":{"201":{"description":"Pin added to map","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MapPin"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}}}}
```

## DELETE /maps/{id}/pins

> Remove pin from map

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"tags":[{"name":"Maps","description":"Map management, pin ordering, sharing, and visit tracking"}],"servers":[{"url":"/api","description":"API base path"}],"security":[{"BearerAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","description":"Bearer token authentication. Pass the session token obtained from /auth/sync."}},"responses":{"BadRequest":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unauthorized":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InternalServerError":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"object"}}}}},"paths":{"/maps/{id}/pins":{"delete":{"summary":"Remove pin from map","tags":["Maps"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}},{"name":"pinId","in":"query","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Pin removed from map","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}}}}
```

## PATCH /maps/{id}/pins

> Update pin order in map

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"tags":[{"name":"Maps","description":"Map management, pin ordering, sharing, and visit tracking"}],"servers":[{"url":"/api","description":"API base path"}],"security":[{"BearerAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","description":"Bearer token authentication. Pass the session token obtained from /auth/sync."}},"schemas":{"MapPin":{"type":"object","properties":{"mapId":{"type":"integer"},"pinId":{"type":"integer"},"order":{"type":"integer"}}},"Error":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"object"}}}},"responses":{"BadRequest":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unauthorized":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InternalServerError":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/maps/{id}/pins":{"patch":{"summary":"Update pin order in map","tags":["Maps"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["pinId","order"],"properties":{"pinId":{"type":"integer","minimum":1},"order":{"type":"integer","minimum":0}}}}}},"responses":{"200":{"description":"Pin order updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MapPin"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}}}}
```

## GET /maps/{id}/share

> Get users a map is shared with

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"tags":[{"name":"Maps","description":"Map management, pin ordering, sharing, and visit tracking"}],"servers":[{"url":"/api","description":"API base path"}],"security":[{"BearerAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","description":"Bearer token authentication. Pass the session token obtained from /auth/sync."}},"schemas":{"MapShare":{"type":"object","properties":{"id":{"type":"integer"},"mapId":{"type":"integer"},"userId":{"type":"string"},"canEdit":{"type":"boolean"},"sharedAt":{"type":"string","format":"date-time"}}},"Error":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"object"}}}},"responses":{"BadRequest":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unauthorized":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InternalServerError":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/maps/{id}/share":{"get":{"summary":"Get users a map is shared with","tags":["Maps"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"List of share records","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/MapShare"}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}}}}
```

## POST /maps/{id}/share

> Share map with a user

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"tags":[{"name":"Maps","description":"Map management, pin ordering, sharing, and visit tracking"}],"servers":[{"url":"/api","description":"API base path"}],"security":[{"BearerAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","description":"Bearer token authentication. Pass the session token obtained from /auth/sync."}},"schemas":{"MapShare":{"type":"object","properties":{"id":{"type":"integer"},"mapId":{"type":"integer"},"userId":{"type":"string"},"canEdit":{"type":"boolean"},"sharedAt":{"type":"string","format":"date-time"}}},"Error":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"object"}}}},"responses":{"BadRequest":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unauthorized":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InternalServerError":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/maps/{id}/share":{"post":{"summary":"Share map with a user","tags":["Maps"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["userId"],"properties":{"userId":{"type":"string","minLength":1},"canEdit":{"type":"boolean","default":false}}}}}},"responses":{"201":{"description":"Map shared successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MapShare"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}}}}
```

## DELETE /maps/{id}/share

> Unshare map with a user

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"tags":[{"name":"Maps","description":"Map management, pin ordering, sharing, and visit tracking"}],"servers":[{"url":"/api","description":"API base path"}],"security":[{"BearerAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","description":"Bearer token authentication. Pass the session token obtained from /auth/sync."}},"responses":{"BadRequest":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unauthorized":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InternalServerError":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"object"}}}}},"paths":{"/maps/{id}/share":{"delete":{"summary":"Unshare map with a user","tags":["Maps"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}},{"name":"userId","in":"query","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Map unshared","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}}}}
```


# Blockchain

Blockchain synchronization operations for syncing entities to Solana blockchain

## Prepare blockchain sync transaction

> Prepares a blockchain transaction for syncing an entity (Zone, Pin, GCP, or File) to the Solana blockchain. Returns transaction metadata and data that the client will sign and send. Authentication required.

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"tags":[{"name":"Blockchain","description":"Blockchain synchronization operations for syncing entities to Solana blockchain"}],"servers":[{"url":"/api","description":"API base path"}],"security":[{"DynamicAuth":[]}],"components":{"securitySchemes":{"DynamicAuth":{"type":"apiKey","in":"header","name":"X-Dynamic-User-Id","description":"Dynamic authentication header. Required for blockchain endpoints and other authenticated operations."}},"schemas":{"BlockchainSyncRequest":{"type":"object","required":["entityType","entityId"],"properties":{"entityType":{"type":"string","enum":["Zone","Pin","Gcp","File"],"description":"Type of entity to sync to blockchain"},"entityId":{"type":"integer","minimum":1,"description":"ID of the entity to sync"},"network":{"type":"string","enum":["devnet","mainnet","testnet"],"description":"Solana network to use (defaults to devnet if not specified)"}}},"BlockchainSyncResponse":{"oneOf":[{"type":"object","title":"Success Response","required":["success","metadata","network"],"properties":{"success":{"type":"boolean","const":true,"description":"Transaction was prepared successfully"},"metadata":{"type":"object","description":"Spatial metadata object containing entity information for blockchain","properties":{"entityType":{"type":"string","enum":["Zone","Pin","Gcp","File"]},"entityId":{"type":"integer"},"title":{"type":"string"},"geojson":{"type":"object","description":"GeoJSON object containing geographic data"},"geoPose":{"type":"object","description":"GeoPose object containing position and orientation data"}},"additionalProperties":true},"transactionData":{"type":"object","description":"Transaction data for client to sign and send","additionalProperties":true},"network":{"type":"string","enum":["devnet","mainnet","testnet"],"description":"Solana network used for the transaction"}}},{"type":"object","title":"Error Response","required":["error"],"properties":{"error":{"type":"string","description":"Error message"},"details":{"type":"array","description":"Validation error details (only present on validation errors)","items":{"type":"object"}}}}]},"Error":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"object"}}}}},"paths":{"/blockchain/sync":{"post":{"summary":"Prepare blockchain sync transaction","description":"Prepares a blockchain transaction for syncing an entity (Zone, Pin, GCP, or File) to the Solana blockchain. Returns transaction metadata and data that the client will sign and send. Authentication required.","tags":["Blockchain"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BlockchainSyncRequest"}}}},"responses":{"200":{"description":"Transaction prepared successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BlockchainSyncResponse"}}}},"400":{"description":"Bad request - invalid input or wallet address not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized - authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal server error - failed to prepare sync","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```

## Confirm blockchain transaction

> Records a confirmed blockchain transaction in the database after it has been signed and sent by the client. Verifies the transaction status and updates the entity's blockchain sync status. Authentication required.

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"tags":[{"name":"Blockchain","description":"Blockchain synchronization operations for syncing entities to Solana blockchain"}],"servers":[{"url":"/api","description":"API base path"}],"security":[{"DynamicAuth":[]}],"components":{"securitySchemes":{"DynamicAuth":{"type":"apiKey","in":"header","name":"X-Dynamic-User-Id","description":"Dynamic authentication header. Required for blockchain endpoints and other authenticated operations."}},"schemas":{"BlockchainConfirmRequest":{"type":"object","required":["entityType","entityId","signature"],"properties":{"entityType":{"type":"string","enum":["Zone","Pin","Gcp","File"],"description":"Type of entity that was synced"},"entityId":{"type":"integer","minimum":1,"description":"ID of the entity that was synced"},"signature":{"type":"string","minLength":1,"description":"Transaction signature from Solana blockchain"},"network":{"type":"string","enum":["devnet","mainnet","testnet"],"description":"Solana network used for the transaction"},"blockHeight":{"type":"integer","minimum":1,"description":"Block height at which the transaction was confirmed (optional)"}}},"BlockchainConfirmResponse":{"oneOf":[{"type":"object","title":"Success Response","required":["success","signature","explorerUrl"],"properties":{"success":{"type":"boolean","const":true,"description":"Transaction was confirmed successfully"},"signature":{"type":"string","description":"Transaction signature"},"explorerUrl":{"type":"string","format":"uri","description":"URL to view the transaction on Solana Explorer"}}},{"type":"object","title":"Error Response","required":["error"],"properties":{"error":{"type":"string","description":"Error message"},"details":{"type":"array","description":"Validation error details (only present on validation errors)","items":{"type":"object"}}}}]},"Error":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"object"}}}}},"paths":{"/blockchain/confirm":{"post":{"summary":"Confirm blockchain transaction","description":"Records a confirmed blockchain transaction in the database after it has been signed and sent by the client. Verifies the transaction status and updates the entity's blockchain sync status. Authentication required.","tags":["Blockchain"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BlockchainConfirmRequest"}}}},"responses":{"200":{"description":"Transaction confirmed successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BlockchainConfirmResponse"}}}},"400":{"description":"Bad request - invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized - authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal server error - failed to confirm transaction","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```

## Mark blockchain sync as failed

> Marks an entity's blockchain sync as failed (e.g., when transaction is cancelled or fails). Authentication required.

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"tags":[{"name":"Blockchain","description":"Blockchain synchronization operations for syncing entities to Solana blockchain"}],"servers":[{"url":"/api","description":"API base path"}],"security":[{"DynamicAuth":[]}],"components":{"securitySchemes":{"DynamicAuth":{"type":"apiKey","in":"header","name":"X-Dynamic-User-Id","description":"Dynamic authentication header. Required for blockchain endpoints and other authenticated operations."}},"schemas":{"BlockchainSyncRequest":{"type":"object","required":["entityType","entityId"],"properties":{"entityType":{"type":"string","enum":["Zone","Pin","Gcp","File"],"description":"Type of entity to sync to blockchain"},"entityId":{"type":"integer","minimum":1,"description":"ID of the entity to sync"},"network":{"type":"string","enum":["devnet","mainnet","testnet"],"description":"Solana network to use (defaults to devnet if not specified)"}}},"Error":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"object"}}}}},"paths":{"/blockchain/fail":{"post":{"summary":"Mark blockchain sync as failed","description":"Marks an entity's blockchain sync as failed (e.g., when transaction is cancelled or fails). Authentication required.","tags":["Blockchain"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BlockchainSyncRequest"}}}},"responses":{"200":{"description":"Sync marked as failed successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}}}}}},"400":{"description":"Bad request - invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized - authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal server error - failed to mark sync as failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```


# Models

## The User object

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"components":{"schemas":{"User":{"type":"object","properties":{"id":{"type":"string"},"walletAddress":{"type":"string","nullable":true},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}}}}}
```

## The UserResponse object

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"components":{"schemas":{"UserResponse":{"allOf":[{"$ref":"#/components/schemas/User"},{"type":"object","properties":{"isNewUser":{"type":"boolean"}}}]},"User":{"type":"object","properties":{"id":{"type":"string"},"walletAddress":{"type":"string","nullable":true},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}}}}}
```

## The Category object

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"components":{"schemas":{"Category":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"description":{"type":"string","nullable":true},"color":{"type":"string"},"createdAt":{"type":"string","format":"date-time"}}}}}}
```

## The Zone object

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"components":{"schemas":{"Zone":{"type":"object","properties":{"id":{"type":"integer"},"title":{"type":"string"},"description":{"type":"string","nullable":true},"geojson":{"type":"object"},"userId":{"type":"string","nullable":true},"isGlobal":{"type":"boolean"},"map3dType":{"type":"string","nullable":true},"map3dUrl":{"type":"string","nullable":true},"map3dConfig":{"type":"object","nullable":true},"previewImageUrl":{"type":"string","nullable":true},"area":{"type":"number","nullable":true},"blockchainSignature":{"type":"string","nullable":true},"blockchainTxUrl":{"type":"string","nullable":true},"blockchainSyncedAt":{"type":"string","format":"date-time","nullable":true},"blockchainSyncStatus":{"type":"string","enum":["not_synced","syncing","synced","failed"]},"categories":{"type":"array","items":{"type":"object","properties":{"category":{"$ref":"#/components/schemas/Category"}}}},"pins":{"type":"array","items":{"$ref":"#/components/schemas/Pin"}},"_count":{"type":"object","properties":{"pins":{"type":"integer"}}},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"Category":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"description":{"type":"string","nullable":true},"color":{"type":"string"},"createdAt":{"type":"string","format":"date-time"}}},"Pin":{"type":"object","properties":{"id":{"type":"integer"},"title":{"type":"string"},"latitude":{"type":"number","nullable":true},"longitude":{"type":"number","nullable":true},"geoPose":{"type":"object","description":"JSON object containing geographic position and orientation data"},"contentType":{"type":"string","enum":["APK","GLB","IMAGE","VIDEO","URL","LENS","GCP","AUDIO","USDZ","BIN"]},"contentUrl":{"type":"string"},"localizationData":{"type":"object","nullable":true,"description":"ARWorld localization data for iOS AR experiences","properties":{"trackingMode":{"type":"string","enum":["WORLD","GEO"],"description":"AR tracking mode for the pin"},"worldmapURL":{"type":"string","format":"uri","nullable":true,"description":"S3 URL of the ARWorld binary data"}}},"zoneId":{"type":"integer","nullable":true},"gcpId":{"type":"integer","nullable":true},"userId":{"type":"string","nullable":true},"visibility":{"type":"string","enum":["public","private","friends"],"description":"Pin visibility setting"},"relocalizationPhotoUrl":{"type":"string","nullable":true,"description":"URL of the relocalization reference photo"},"scanInstructions":{"type":"string","nullable":true,"description":"Instructions for scanning/relocalizing"},"cameraPoseHint":{"type":"object","nullable":true,"description":"Camera pose hint for AR relocalization"},"blockchainSignature":{"type":"string","nullable":true},"blockchainTxUrl":{"type":"string","nullable":true},"blockchainSyncedAt":{"type":"string","format":"date-time","nullable":true},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}}}}}
```

## The GeoPose object

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"components":{"schemas":{"GeoPose":{"type":"object","required":["position","angles"],"properties":{"position":{"$ref":"#/components/schemas/Position"},"angles":{"$ref":"#/components/schemas/YprAngles"}}},"Position":{"type":"object","required":["lat","lon"],"properties":{"lat":{"type":"number","description":"Latitude in decimal degrees"},"lon":{"type":"number","description":"Longitude in decimal degrees"},"h":{"type":"number","description":"Height/altitude in meters","default":0}}},"YprAngles":{"type":"object","required":["yaw","pitch","roll"],"properties":{"yaw":{"type":"number","description":"Yaw angle in degrees","default":0},"pitch":{"type":"number","description":"Pitch angle in degrees","default":0},"roll":{"type":"number","description":"Roll angle in degrees","default":0}}}}}}
```

## The Position object

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"components":{"schemas":{"Position":{"type":"object","required":["lat","lon"],"properties":{"lat":{"type":"number","description":"Latitude in decimal degrees"},"lon":{"type":"number","description":"Longitude in decimal degrees"},"h":{"type":"number","description":"Height/altitude in meters","default":0}}}}}}
```

## The YprAngles object

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"components":{"schemas":{"YprAngles":{"type":"object","required":["yaw","pitch","roll"],"properties":{"yaw":{"type":"number","description":"Yaw angle in degrees","default":0},"pitch":{"type":"number","description":"Pitch angle in degrees","default":0},"roll":{"type":"number","description":"Roll angle in degrees","default":0}}}}}}
```

## The Pin object

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"components":{"schemas":{"Pin":{"type":"object","properties":{"id":{"type":"integer"},"title":{"type":"string"},"latitude":{"type":"number","nullable":true},"longitude":{"type":"number","nullable":true},"geoPose":{"type":"object","description":"JSON object containing geographic position and orientation data"},"contentType":{"type":"string","enum":["APK","GLB","IMAGE","VIDEO","URL","LENS","GCP","AUDIO","USDZ","BIN"]},"contentUrl":{"type":"string"},"localizationData":{"type":"object","nullable":true,"description":"ARWorld localization data for iOS AR experiences","properties":{"trackingMode":{"type":"string","enum":["WORLD","GEO"],"description":"AR tracking mode for the pin"},"worldmapURL":{"type":"string","format":"uri","nullable":true,"description":"S3 URL of the ARWorld binary data"}}},"zoneId":{"type":"integer","nullable":true},"gcpId":{"type":"integer","nullable":true},"userId":{"type":"string","nullable":true},"visibility":{"type":"string","enum":["public","private","friends"],"description":"Pin visibility setting"},"relocalizationPhotoUrl":{"type":"string","nullable":true,"description":"URL of the relocalization reference photo"},"scanInstructions":{"type":"string","nullable":true,"description":"Instructions for scanning/relocalizing"},"cameraPoseHint":{"type":"object","nullable":true,"description":"Camera pose hint for AR relocalization"},"blockchainSignature":{"type":"string","nullable":true},"blockchainTxUrl":{"type":"string","nullable":true},"blockchainSyncedAt":{"type":"string","format":"date-time","nullable":true},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}}}}}
```

## The GCP object

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"components":{"schemas":{"GCP":{"type":"object","properties":{"id":{"type":"integer"},"title":{"type":"string"},"description":{"type":"string","nullable":true},"gcpType":{"type":"string","enum":["SINGLE","MULTIPLE"]},"altitude":{"type":"number","nullable":true},"aprilTagId":{"type":"integer","nullable":true,"minimum":0,"maximum":50},"gcpData":{"type":"string","nullable":true},"photoUrls":{"type":"array","items":{"type":"string"}},"zoneId":{"type":"integer","nullable":true},"pin":{"$ref":"#/components/schemas/Pin","nullable":true},"zone":{"type":"object","nullable":true,"properties":{"id":{"type":"integer"},"title":{"type":"string"}}},"uploadedFiles":{"type":"array","items":{"$ref":"#/components/schemas/UploadedFile"}},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"Pin":{"type":"object","properties":{"id":{"type":"integer"},"title":{"type":"string"},"latitude":{"type":"number","nullable":true},"longitude":{"type":"number","nullable":true},"geoPose":{"type":"object","description":"JSON object containing geographic position and orientation data"},"contentType":{"type":"string","enum":["APK","GLB","IMAGE","VIDEO","URL","LENS","GCP","AUDIO","USDZ","BIN"]},"contentUrl":{"type":"string"},"localizationData":{"type":"object","nullable":true,"description":"ARWorld localization data for iOS AR experiences","properties":{"trackingMode":{"type":"string","enum":["WORLD","GEO"],"description":"AR tracking mode for the pin"},"worldmapURL":{"type":"string","format":"uri","nullable":true,"description":"S3 URL of the ARWorld binary data"}}},"zoneId":{"type":"integer","nullable":true},"gcpId":{"type":"integer","nullable":true},"userId":{"type":"string","nullable":true},"visibility":{"type":"string","enum":["public","private","friends"],"description":"Pin visibility setting"},"relocalizationPhotoUrl":{"type":"string","nullable":true,"description":"URL of the relocalization reference photo"},"scanInstructions":{"type":"string","nullable":true,"description":"Instructions for scanning/relocalizing"},"cameraPoseHint":{"type":"object","nullable":true,"description":"Camera pose hint for AR relocalization"},"blockchainSignature":{"type":"string","nullable":true},"blockchainTxUrl":{"type":"string","nullable":true},"blockchainSyncedAt":{"type":"string","format":"date-time","nullable":true},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"UploadedFile":{"type":"object","properties":{"id":{"type":"integer"},"fileName":{"type":"string"},"fileUrl":{"type":"string"},"fileSize":{"type":"integer","nullable":true},"mimeType":{"type":"string","nullable":true},"bucketPath":{"type":"string","nullable":true},"gcpId":{"type":"integer","nullable":true},"userId":{"type":"string","nullable":true},"uploadedAt":{"type":"string","format":"date-time"}}}}}}
```

## The GCPCreateRequest object

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"components":{"schemas":{"GCPCreateRequest":{"type":"object","required":["title","gcpType","geoPose"],"properties":{"title":{"type":"string"},"description":{"type":"string"},"gcpType":{"type":"string","enum":["SINGLE","MULTIPLE"]},"geoPose":{"$ref":"#/components/schemas/GeoPose"},"altitude":{"type":"number"},"aprilTagId":{"type":"integer","minimum":0,"maximum":50},"gcpData":{"type":"string"},"photoUrls":{"type":"array","items":{"type":"string","format":"uri"},"maxItems":3},"uploadedFileIds":{"type":"array","items":{"type":"integer"}},"zoneId":{"type":"integer"}}},"GeoPose":{"type":"object","required":["position","angles"],"properties":{"position":{"$ref":"#/components/schemas/Position"},"angles":{"$ref":"#/components/schemas/YprAngles"}}},"Position":{"type":"object","required":["lat","lon"],"properties":{"lat":{"type":"number","description":"Latitude in decimal degrees"},"lon":{"type":"number","description":"Longitude in decimal degrees"},"h":{"type":"number","description":"Height/altitude in meters","default":0}}},"YprAngles":{"type":"object","required":["yaw","pitch","roll"],"properties":{"yaw":{"type":"number","description":"Yaw angle in degrees","default":0},"pitch":{"type":"number","description":"Pitch angle in degrees","default":0},"roll":{"type":"number","description":"Roll angle in degrees","default":0}}}}}}
```

## The GCPUpdateRequest object

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"components":{"schemas":{"GCPUpdateRequest":{"type":"object","required":["title","geoPose"],"properties":{"title":{"type":"string"},"description":{"type":"string","nullable":true},"geoPose":{"$ref":"#/components/schemas/GeoPose"},"altitude":{"type":"number","nullable":true},"aprilTagId":{"type":"integer","nullable":true,"minimum":0,"maximum":50},"photoUrls":{"type":"array","items":{"type":"string","format":"uri"}}}},"GeoPose":{"type":"object","required":["position","angles"],"properties":{"position":{"$ref":"#/components/schemas/Position"},"angles":{"$ref":"#/components/schemas/YprAngles"}}},"Position":{"type":"object","required":["lat","lon"],"properties":{"lat":{"type":"number","description":"Latitude in decimal degrees"},"lon":{"type":"number","description":"Longitude in decimal degrees"},"h":{"type":"number","description":"Height/altitude in meters","default":0}}},"YprAngles":{"type":"object","required":["yaw","pitch","roll"],"properties":{"yaw":{"type":"number","description":"Yaw angle in degrees","default":0},"pitch":{"type":"number","description":"Pitch angle in degrees","default":0},"roll":{"type":"number","description":"Roll angle in degrees","default":0}}}}}}
```

## The UploadedFile object

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"components":{"schemas":{"UploadedFile":{"type":"object","properties":{"id":{"type":"integer"},"fileName":{"type":"string"},"fileUrl":{"type":"string"},"fileSize":{"type":"integer","nullable":true},"mimeType":{"type":"string","nullable":true},"bucketPath":{"type":"string","nullable":true},"gcpId":{"type":"integer","nullable":true},"userId":{"type":"string","nullable":true},"uploadedAt":{"type":"string","format":"date-time"}}}}}}
```

## The CSVUpload object

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"components":{"schemas":{"CSVUpload":{"type":"object","properties":{"id":{"type":"string"},"fileName":{"type":"string"},"fileUrl":{"type":"string"},"status":{"type":"string","enum":["PENDING","COMPLETED","FAILED"]},"errorMessage":{"type":"string","nullable":true},"rowCount":{"type":"integer","nullable":true},"processedRows":{"type":"integer","nullable":true},"userId":{"type":"string"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}}}}}
```

## The NearbyPin object

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"components":{"schemas":{"NearbyPin":{"allOf":[{"$ref":"#/components/schemas/Pin"},{"type":"object","required":["distance"],"properties":{"distance":{"type":"number","description":"Distance from query point in meters"}}}]},"Pin":{"type":"object","properties":{"id":{"type":"integer"},"title":{"type":"string"},"latitude":{"type":"number","nullable":true},"longitude":{"type":"number","nullable":true},"geoPose":{"type":"object","description":"JSON object containing geographic position and orientation data"},"contentType":{"type":"string","enum":["APK","GLB","IMAGE","VIDEO","URL","LENS","GCP","AUDIO","USDZ","BIN"]},"contentUrl":{"type":"string"},"localizationData":{"type":"object","nullable":true,"description":"ARWorld localization data for iOS AR experiences","properties":{"trackingMode":{"type":"string","enum":["WORLD","GEO"],"description":"AR tracking mode for the pin"},"worldmapURL":{"type":"string","format":"uri","nullable":true,"description":"S3 URL of the ARWorld binary data"}}},"zoneId":{"type":"integer","nullable":true},"gcpId":{"type":"integer","nullable":true},"userId":{"type":"string","nullable":true},"visibility":{"type":"string","enum":["public","private","friends"],"description":"Pin visibility setting"},"relocalizationPhotoUrl":{"type":"string","nullable":true,"description":"URL of the relocalization reference photo"},"scanInstructions":{"type":"string","nullable":true,"description":"Instructions for scanning/relocalizing"},"cameraPoseHint":{"type":"object","nullable":true,"description":"Camera pose hint for AR relocalization"},"blockchainSignature":{"type":"string","nullable":true},"blockchainTxUrl":{"type":"string","nullable":true},"blockchainSyncedAt":{"type":"string","format":"date-time","nullable":true},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}}}}}
```

## The NearbyPinList object

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"components":{"schemas":{"NearbyPinList":{"type":"object","required":["pins"],"properties":{"pins":{"type":"array","items":{"$ref":"#/components/schemas/NearbyPin"},"description":"Array of pins sorted by distance from query point"}}},"NearbyPin":{"allOf":[{"$ref":"#/components/schemas/Pin"},{"type":"object","required":["distance"],"properties":{"distance":{"type":"number","description":"Distance from query point in meters"}}}]},"Pin":{"type":"object","properties":{"id":{"type":"integer"},"title":{"type":"string"},"latitude":{"type":"number","nullable":true},"longitude":{"type":"number","nullable":true},"geoPose":{"type":"object","description":"JSON object containing geographic position and orientation data"},"contentType":{"type":"string","enum":["APK","GLB","IMAGE","VIDEO","URL","LENS","GCP","AUDIO","USDZ","BIN"]},"contentUrl":{"type":"string"},"localizationData":{"type":"object","nullable":true,"description":"ARWorld localization data for iOS AR experiences","properties":{"trackingMode":{"type":"string","enum":["WORLD","GEO"],"description":"AR tracking mode for the pin"},"worldmapURL":{"type":"string","format":"uri","nullable":true,"description":"S3 URL of the ARWorld binary data"}}},"zoneId":{"type":"integer","nullable":true},"gcpId":{"type":"integer","nullable":true},"userId":{"type":"string","nullable":true},"visibility":{"type":"string","enum":["public","private","friends"],"description":"Pin visibility setting"},"relocalizationPhotoUrl":{"type":"string","nullable":true,"description":"URL of the relocalization reference photo"},"scanInstructions":{"type":"string","nullable":true,"description":"Instructions for scanning/relocalizing"},"cameraPoseHint":{"type":"object","nullable":true,"description":"Camera pose hint for AR relocalization"},"blockchainSignature":{"type":"string","nullable":true},"blockchainTxUrl":{"type":"string","nullable":true},"blockchainSyncedAt":{"type":"string","format":"date-time","nullable":true},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}}}}}
```

## The Map object

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"components":{"schemas":{"Map":{"type":"object","properties":{"id":{"type":"integer"},"title":{"type":"string"},"description":{"type":"string","nullable":true},"isPublic":{"type":"boolean"},"coverImage":{"type":"string","nullable":true},"latitude":{"type":"number","nullable":true},"longitude":{"type":"number","nullable":true},"userId":{"type":"string"},"pins":{"type":"array","items":{"type":"object","properties":{"pin":{"$ref":"#/components/schemas/Pin"},"order":{"type":"integer"}}}},"_count":{"type":"object","properties":{"visits":{"type":"integer"}}},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"deletedAt":{"type":"string","format":"date-time","nullable":true}}},"Pin":{"type":"object","properties":{"id":{"type":"integer"},"title":{"type":"string"},"latitude":{"type":"number","nullable":true},"longitude":{"type":"number","nullable":true},"geoPose":{"type":"object","description":"JSON object containing geographic position and orientation data"},"contentType":{"type":"string","enum":["APK","GLB","IMAGE","VIDEO","URL","LENS","GCP","AUDIO","USDZ","BIN"]},"contentUrl":{"type":"string"},"localizationData":{"type":"object","nullable":true,"description":"ARWorld localization data for iOS AR experiences","properties":{"trackingMode":{"type":"string","enum":["WORLD","GEO"],"description":"AR tracking mode for the pin"},"worldmapURL":{"type":"string","format":"uri","nullable":true,"description":"S3 URL of the ARWorld binary data"}}},"zoneId":{"type":"integer","nullable":true},"gcpId":{"type":"integer","nullable":true},"userId":{"type":"string","nullable":true},"visibility":{"type":"string","enum":["public","private","friends"],"description":"Pin visibility setting"},"relocalizationPhotoUrl":{"type":"string","nullable":true,"description":"URL of the relocalization reference photo"},"scanInstructions":{"type":"string","nullable":true,"description":"Instructions for scanning/relocalizing"},"cameraPoseHint":{"type":"object","nullable":true,"description":"Camera pose hint for AR relocalization"},"blockchainSignature":{"type":"string","nullable":true},"blockchainTxUrl":{"type":"string","nullable":true},"blockchainSyncedAt":{"type":"string","format":"date-time","nullable":true},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}}}}}
```

## The MapPin object

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"components":{"schemas":{"MapPin":{"type":"object","properties":{"mapId":{"type":"integer"},"pinId":{"type":"integer"},"order":{"type":"integer"}}}}}}
```

## The MapShare object

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"components":{"schemas":{"MapShare":{"type":"object","properties":{"id":{"type":"integer"},"mapId":{"type":"integer"},"userId":{"type":"string"},"canEdit":{"type":"boolean"},"sharedAt":{"type":"string","format":"date-time"}}}}}}
```

## The MapProgress object

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"components":{"schemas":{"MapProgress":{"type":"object","properties":{"mapId":{"type":"integer"},"mapTitle":{"type":"string"},"totalPins":{"type":"integer"},"visitedPins":{"type":"integer"},"completionPercentage":{"type":"number"},"visitedPinIds":{"type":"array","items":{"type":"integer"}},"pins":{"type":"array","items":{"type":"object","properties":{"pinId":{"type":"integer"},"order":{"type":"integer"},"title":{"type":"string"},"visited":{"type":"boolean"},"visitedAt":{"type":"string","format":"date-time","nullable":true}}}}}}}}}
```

## The Error object

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"components":{"schemas":{"Error":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"object"}}}}}}
```

## The FileUploadResult object

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"components":{"schemas":{"FileUploadResult":{"type":"object","description":"Result of a successful file upload","properties":{"status":{"type":"string","enum":["success"],"description":"Upload status"},"message":{"type":"string","description":"Success message"},"url":{"type":"string","format":"uri","description":"S3 URL of the uploaded file"},"fileId":{"type":"integer","description":"Database ID of the uploaded file record"},"fileName":{"type":"string","description":"Original filename"},"fileSize":{"type":"integer","nullable":true,"description":"File size in bytes"},"mimeType":{"type":"string","nullable":true,"description":"MIME type of the file"},"arWorldUrl":{"type":"string","format":"uri","nullable":true,"description":"S3 URL for AR World files (.bin), null for other file types"}}}}}}
```

## The LocalizationData object

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"components":{"schemas":{"LocalizationData":{"type":"object","description":"ARWorld localization data for iOS AR experiences","properties":{"trackingMode":{"type":"string","enum":["WORLD","GEO"],"description":"AR tracking mode for the pin"},"worldmapURL":{"type":"string","format":"uri","nullable":true,"description":"S3 URL of the ARWorld binary data"},"localizationPhotoURL":{"type":"string","format":"uri","nullable":true,"description":"URL of the photo used for re-localization"}}}}}}
```

## The BlockchainSyncRequest object

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"components":{"schemas":{"BlockchainSyncRequest":{"type":"object","required":["entityType","entityId"],"properties":{"entityType":{"type":"string","enum":["Zone","Pin","Gcp","File"],"description":"Type of entity to sync to blockchain"},"entityId":{"type":"integer","minimum":1,"description":"ID of the entity to sync"},"network":{"type":"string","enum":["devnet","mainnet","testnet"],"description":"Solana network to use (defaults to devnet if not specified)"}}}}}}
```

## The BlockchainSyncResponse object

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"components":{"schemas":{"BlockchainSyncResponse":{"oneOf":[{"type":"object","title":"Success Response","required":["success","metadata","network"],"properties":{"success":{"type":"boolean","const":true,"description":"Transaction was prepared successfully"},"metadata":{"type":"object","description":"Spatial metadata object containing entity information for blockchain","properties":{"entityType":{"type":"string","enum":["Zone","Pin","Gcp","File"]},"entityId":{"type":"integer"},"title":{"type":"string"},"geojson":{"type":"object","description":"GeoJSON object containing geographic data"},"geoPose":{"type":"object","description":"GeoPose object containing position and orientation data"}},"additionalProperties":true},"transactionData":{"type":"object","description":"Transaction data for client to sign and send","additionalProperties":true},"network":{"type":"string","enum":["devnet","mainnet","testnet"],"description":"Solana network used for the transaction"}}},{"type":"object","title":"Error Response","required":["error"],"properties":{"error":{"type":"string","description":"Error message"},"details":{"type":"array","description":"Validation error details (only present on validation errors)","items":{"type":"object"}}}}]}}}}
```

## The BlockchainConfirmRequest object

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"components":{"schemas":{"BlockchainConfirmRequest":{"type":"object","required":["entityType","entityId","signature"],"properties":{"entityType":{"type":"string","enum":["Zone","Pin","Gcp","File"],"description":"Type of entity that was synced"},"entityId":{"type":"integer","minimum":1,"description":"ID of the entity that was synced"},"signature":{"type":"string","minLength":1,"description":"Transaction signature from Solana blockchain"},"network":{"type":"string","enum":["devnet","mainnet","testnet"],"description":"Solana network used for the transaction"},"blockHeight":{"type":"integer","minimum":1,"description":"Block height at which the transaction was confirmed (optional)"}}}}}}
```

## The BlockchainConfirmResponse object

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"components":{"schemas":{"BlockchainConfirmResponse":{"oneOf":[{"type":"object","title":"Success Response","required":["success","signature","explorerUrl"],"properties":{"success":{"type":"boolean","const":true,"description":"Transaction was confirmed successfully"},"signature":{"type":"string","description":"Transaction signature"},"explorerUrl":{"type":"string","format":"uri","description":"URL to view the transaction on Solana Explorer"}}},{"type":"object","title":"Error Response","required":["error"],"properties":{"error":{"type":"string","description":"Error message"},"details":{"type":"array","description":"Validation error details (only present on validation errors)","items":{"type":"object"}}}}]}}}}
```

## The BlockchainTransaction object

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"components":{"schemas":{"BlockchainTransaction":{"type":"object","properties":{"id":{"type":"integer","description":"Transaction record ID"},"entityType":{"type":"string","enum":["Zone","Pin","Gcp","File"],"description":"Type of entity"},"entityId":{"type":"integer","description":"ID of the entity"},"signature":{"type":"string","description":"Transaction signature"},"network":{"type":"string","enum":["devnet","mainnet","testnet"],"description":"Solana network"},"status":{"type":"string","enum":["pending","confirmed","failed"],"description":"Transaction status"},"blockHeight":{"type":"integer","nullable":true,"description":"Block height at which transaction was confirmed"},"createdAt":{"type":"string","format":"date-time","description":"When the transaction was created"}}}}}}
```

## The ZoneBlockchainStatus object

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"components":{"schemas":{"ZoneBlockchainStatus":{"type":"object","properties":{"id":{"type":"integer","description":"Zone ID"},"blockchainSignature":{"type":"string","nullable":true,"description":"Blockchain transaction signature"},"blockchainTxUrl":{"type":"string","nullable":true,"format":"uri","description":"URL to view transaction on Solana Explorer"},"blockchainSyncedAt":{"type":"string","nullable":true,"format":"date-time","description":"When the zone was synced to blockchain"},"blockchainSyncStatus":{"type":"string","enum":["not_synced","syncing","synced","failed"],"description":"Current blockchain sync status"}}}}}}
```

## The PinBlockchainStatus object

```json
{"openapi":"3.0.0","info":{"title":"Zones API","version":"1.0.0"},"components":{"schemas":{"PinBlockchainStatus":{"type":"object","properties":{"id":{"type":"integer","description":"Pin ID"},"blockchainSignature":{"type":"string","nullable":true,"description":"Blockchain transaction signature"},"blockchainTxUrl":{"type":"string","nullable":true,"format":"uri","description":"URL to view transaction on Solana Explorer"},"blockchainSyncedAt":{"type":"string","nullable":true,"format":"date-time","description":"When the pin was synced to blockchain"}}}}}}
```


# Downloads

Download and install the latest builds of our various XR apps and demos.

## City Champ

Download the CityChamp v1.5.2 .apk build for Meta Quest 2/3/Pro and upload it to your headset using [SideQuest](https://sidequestvr.com/setup-howto). We recommend playing on Quest 3 for the best experience. *Last updated July 2024.*

## City Champ AR Meshing Demo

Download the CityChamp v0.4.2 .apk build for Magic Leap 2 and upload it to your headset via [Magic Leap Hub](https://www.magicleap.care/hc/en-us/articles/5951496255885-Installing-and-Uninstalling-Apps). We recommend playing in pedestrian-safe, walkable areas for the best experience. *Last updated September 2024.*

## *HANDMADE*

Download the LeslieThorntonHandmade v0.1.2 .apk build for Meta Quest 2/3/Pro and upload it to your headset using [SideQuest](https://sidequestvr.com/setup-howto). *Last updated November 2024.*


