Quickstart
Capture a real site with WaveForm Field, publish a nav-pack, and pull it from a test robot — in about 30 minutes.
- An iPhone with LiDAR (12 Pro or newer).
- A WaveForm account at
app.waveform.vision. Sign-up takes one email round-trip. - Python 3.10+ on your laptop for the admin SDK + nav-pack publish.
Step 1 — Sign up + create a customer
Visit app.waveform.vision/signup and complete the magic-link flow. You’ll land on the project picker with a single empty customer scope. Note your customer_id — every API call below carries it.
Step 2 — Capture a site (WaveForm Field)
- Install WaveForm Field from TestFlight (or the App Store once GA).
- Sign in with the same email — the iOS app uses the same magic-link flow as the web app.
- Tap New site, drop a pin on a map, and tap Start capture.
- Walk a slow loop (about 0.5 m/s) around the area of interest. The coverage heatmap turns green as you go.
- Tap Finish. The app queues the bundle and uploads opportunistically — you can finish in the field with no signal and let it sync at home.
Step 3 — Wait for reconstruction
Once the bundle finishes uploading, the platform queues a reconstruction job. For an indoor scene of ~200 frames this takes 5–10 minutes. You can watch it run in the web app under Sites → your site → Snapshots.
The reconstruction produces a georeferenced 3D snapshot, which is the input to nav-pack publishing.
Step 4 — Publish a nav-pack
Install the admin SDK and authenticate with a platform-admin API token (see Settings → API keys in the web app):
pip install waveform-nav-pack-sdk
export WAVEFORM_API_TOKEN=sk_...
export WAVEFORM_BASE_URL=https://api.waveform.visionThen in Python:
import asyncio
import httpx
from rough_rider.clients.nav_pack_client import NavPackClient
async def main():
async with httpx.AsyncClient(
base_url="https://api.waveform.vision",
headers={"Authorization": f"Bearer {os.environ['WAVEFORM_API_TOKEN']}"},
) as http:
client = NavPackClient(http=http)
# Resolve the area you captured — the site_id is shown in the
# web app's URL bar when you open the snapshot.
area = await client.resolve_area(site_ids=["<site_id>"])
# Publish a viewer-profile pack pointing at the latest snapshot.
pack = await client.publish({
"area_id": area.area_id,
"profile_key": "viewer",
"produced_at": "2026-05-28T00:00:00Z",
"layers": [
{"layer_kind": "manifest", "role": "required"},
],
})
print("published:", pack.pack_version_id)
asyncio.run(main())Step 5 — Pull the pack from a robot
Register a robot, issue a device token, then fetch the manifest as that robot:
# 1. Register the robot (admin)
curl -X POST https://api.waveform.vision/v1/robots \
-H "Authorization: Bearer $WAVEFORM_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"fleet_id":"demo","display_name":"r1"}'
# → {"data": {"robot_id": "..."}}
# 2. Issue a device token (admin)
curl -X POST https://api.waveform.vision/v1/robots/<robot_id>/tokens \
-H "Authorization: Bearer $WAVEFORM_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"scopes":["nav_pack:read"]}'
# → {"data": {"token": "eyJhbGc...", ...}}
# 3. Use the device token to fetch the manifest (as the robot)
curl https://api.waveform.vision/v1/areas/<area_id>/nav-pack \
-H "Authorization: Bearer <robot-token>"The third call returns the signed manifest the robot would consume offline.
What to do next
- Browse the concepts page if any of the vocabulary felt unfamiliar.
- Read publishing to understand the signed-manifest contract in depth.
- For an automated pipeline, set up webhooks so your systems get pinged when a pack is published.