API Reference
Detailed Experience and Panorama resources, retry behavior, and Partner API routes.
11. Experience API
Auth: Authorization: Bearer API key. Tenant is taken from the credential. No org id in the path.
Optimistic concurrency: mutating routes take expected_draft_revision (except archive/restore/delete).
POST /api/v1/360/experiences
| Scope | experiences:write |
| Purpose | Create or resolve by external identity |
| Body | name (string), external_resource_id (string), external_customer_id (optional) |
| Success | 201 created or 200 existing PartnerExperienceResource |
GET /api/v1/360/experiences
| Scope | experiences:read |
| Query | lifecycle_status (active | archived), publication_status (unpublished | published), external_resource_id, external_customer_id, cursor, limit (1–100, default 50) |
| Success | { "items": [...], "next_cursor": string | null } |
Deletion-requested Experiences are omitted from list/get.
GET /api/v1/360/experiences/{experience_id}
Scope experiences:read. 404 if missing or pending deletion.
PATCH /api/v1/360/experiences/{experience_id}
Scope experiences:write. Body: expected_draft_revision, name.
POST /api/v1/360/experiences/{experience_id}/archive
Scope experiences:write. Sets lifecycle archived. Cannot publish while archived.
POST /api/v1/360/experiences/{experience_id}/restore
Scope experiences:write. Returns lifecycle active.
DELETE /api/v1/360/experiences/{experience_id}
Scope experiences:write. Status 202. Requests deletion; the resource becomes 404 for normal reads while the external identity stays reserved.
Experience resource fields
experience_id, public_id, name, external_customer_id, external_resource_id, lifecycle_status, publication_status, draft_revision, created_at, updated_at, archived_at, deletion_requested_at
public_id is the stable Viewer identity. It does not change when you republish.
12. Panorama API
Draft Panoramas only. Published Viewer content is a snapshot, not these rows.
There is no Partner reorder-all endpoint. Set sort_order on create/patch. Creator has its own order API for the iframe editor.
GET /api/v1/360/experiences/{experience_id}/panoramas
Scope panoramas:read. { "items": [...], "draft_revision": n }
POST /api/v1/360/experiences/{experience_id}/panoramas
Scope panoramas:write. Status 201.
Body:
| Field | Notes |
|---|---|
expected_draft_revision | Required |
label | Required |
sort_order | Optional int |
initial_yaw | Radians, default 0, range ±2π |
initial_pitch | Radians, default 0, range ±π/2 |
initial_fov | Degrees, default 75, must satisfy 0 < fov < 180 |
is_starting | Default false. Publish requires exactly one starting Panorama. |
media | Discriminated: { "type": "managed", "upload_id" } or { "type": "external", "validation_id" } |
GET /api/v1/360/experiences/{experience_id}/panoramas/{panorama_id}
Scope panoramas:read.
PATCH /api/v1/360/experiences/{experience_id}/panoramas/{panorama_id}
Scope panoramas:write. Always send expected_draft_revision. Other fields optional. Omitted fields are left unchanged.
DELETE /api/v1/360/experiences/{experience_id}/panoramas/{panorama_id}?expected_draft_revision={n}
Scope panoramas:write. Query param expected_draft_revision is required. Response: { "panorama_id", "deleted": true, "draft_revision" }.
Panorama resource fields
panorama_id, experience_id, label, sort_order, source_type (managed | external), managed_upload_id, external_validation_id, external_url, camera fields, is_starting, health (status, checked_at, error_code, next_check_at), timestamps, draft_revision.
Navigation links
Navigation links are directed connections between two Panoramas in the same Experience. Each link has its own opaque navigation_link_id; multiple links may share the same source and target. Reverse links are not created automatically.
Read routes require panoramas:read. Create, update, and delete routes require panoramas:write and the current expected_draft_revision.
Create body: expected_draft_revision, source_panorama_id, target_panorama_id, yaw, pitch.
Patch body: expected_draft_revision plus one or more of target_panorama_id, yaw, or pitch. Delete sends expected_draft_revision as a query parameter.
Coordinates are spherical: yaw and pitch identify the point in the source Panorama. Publishing snapshots the current navigation graph for the Viewer.
Optional floor plan
An Experience may have one optional floor-plan image. Existing Panoramas may optionally have normalized x/y positions on that image (0..1, origin top-left of the displayed image). Optional heading_degrees is clockwise from plan-up, normalized to [0, 360). Partial placement is valid. Floor plan placement is not required to publish.
This is not another content hierarchy and does not create navigation links. Marker identity is the existing panorama_id.
Upload lifecycle (same write-once PUT as panoramas, JPEG/PNG/WebP, no 2:1 panorama check; max 8192×8192 and 32 megapixels):
POST /api/v1/360/experiences/{experience_id}/floor-plan-uploads(panoramas:upload)- Client
PUTtoupload_url POST .../floor-plan-uploads/{upload_id}/completePUT /api/v1/360/experiences/{experience_id}/floor-planwith{ "expected_draft_revision", "upload_id" }(panoramas:write)
GET .../floor-plan returns { "floor_plan": null | { media_url, placements }, "draft_revision" }.
PUT .../panoramas/{panorama_id}/floor-plan-position sets { x, y, heading_degrees }. Delete that path (query expected_draft_revision) clears that Panorama's placement.
Replacing the floor-plan image keeps existing placements. Removing the floor plan clears the image and all draft placements in one revision bump. Guests see the previous published plan until the next successful publish. The public payload includes nullable floor_plan (etag version aw360-public-v3).
22. Idempotency and retries
| Action | Retry behavior |
|---|---|
| POST Experience create/resolve | Safe. Same external identity → 200 existing row. Name is not overwritten. |
| POST publish | Safe exact retry of the same current draft revision → 200, no new snapshot. Stale expected_draft_revision → 409. |
| POST unpublish | Safe to repeat. |
| Credential revoke (settings) | Safe to repeat. |
Managed upload with client_request_id | Safe if payload matches. |
| Complete ready upload | Safe. |
| PATCH / POST panoramas | Not idempotent; uses draft revision. Retry after re-read. |
| DELETE Experience | Requests deletion (202). Do not assume a second call is a no-op for every state. |
| POST Creator session | Not idempotent — each call mints a new session. |
27. Partner API route index
Path parameters use the FastAPI names (experience_api_id, and so on). Those values are the opaque ids returned in JSON as experience_id, panorama_id, upload_id, and validation_id.
| Method | Path | Scope |
|---|---|---|
| POST | /api/v1/360/experiences | experiences:write |
| GET | /api/v1/360/experiences | experiences:read |
| GET | /api/v1/360/experiences/{experience_api_id} | experiences:read |
| PATCH | /api/v1/360/experiences/{experience_api_id} | experiences:write |
| POST | /api/v1/360/experiences/{experience_api_id}/archive | experiences:write |
| POST | /api/v1/360/experiences/{experience_api_id}/restore | experiences:write |
| DELETE | /api/v1/360/experiences/{experience_api_id} | experiences:write |
| GET | /api/v1/360/experiences/{experience_api_id}/panoramas | panoramas:read |
| POST | /api/v1/360/experiences/{experience_api_id}/panoramas | panoramas:write |
| GET | /api/v1/360/experiences/{experience_api_id}/panoramas/{panorama_api_id} | panoramas:read |
| PATCH | /api/v1/360/experiences/{experience_api_id}/panoramas/{panorama_api_id} | panoramas:write |
| DELETE | /api/v1/360/experiences/{experience_api_id}/panoramas/{panorama_api_id} | panoramas:write |
| GET | /api/v1/360/experiences/{experience_api_id}/navigation-links | panoramas:read |
| POST | /api/v1/360/experiences/{experience_api_id}/navigation-links | panoramas:write |
| GET | /api/v1/360/experiences/{experience_api_id}/navigation-links/{navigation_link_id} | panoramas:read |
| PATCH | /api/v1/360/experiences/{experience_api_id}/navigation-links/{navigation_link_id} | panoramas:write |
| DELETE | /api/v1/360/experiences/{experience_api_id}/navigation-links/{navigation_link_id} | panoramas:write |
| POST | /api/v1/360/experiences/{experience_api_id}/publish | experiences:publish |
| POST | /api/v1/360/experiences/{experience_api_id}/unpublish | experiences:publish |
| POST | /api/v1/360/experiences/{experience_api_id}/panorama-uploads | panoramas:upload |
| POST | /api/v1/360/experiences/{experience_api_id}/panorama-uploads/{upload_api_id}/complete | panoramas:upload |
| GET | /api/v1/360/experiences/{experience_api_id}/panorama-uploads/{upload_api_id} | panoramas:upload |
| DELETE | /api/v1/360/experiences/{experience_api_id}/panorama-uploads/{upload_api_id} | panoramas:upload |
| POST | /api/v1/360/experiences/{experience_api_id}/floor-plan-uploads | panoramas:upload |
| POST | /api/v1/360/experiences/{experience_api_id}/floor-plan-uploads/{upload_api_id}/complete | panoramas:upload |
| GET | /api/v1/360/experiences/{experience_api_id}/floor-plan-uploads/{upload_api_id} | panoramas:upload |
| DELETE | /api/v1/360/experiences/{experience_api_id}/floor-plan-uploads/{upload_api_id} | panoramas:upload |
| GET | /api/v1/360/experiences/{experience_api_id}/floor-plan | panoramas:read |
| PUT | /api/v1/360/experiences/{experience_api_id}/floor-plan | panoramas:write |
| DELETE | /api/v1/360/experiences/{experience_api_id}/floor-plan | panoramas:write |
| PUT | /api/v1/360/experiences/{experience_api_id}/panoramas/{panorama_api_id}/floor-plan-position | panoramas:write |
| DELETE | /api/v1/360/experiences/{experience_api_id}/panoramas/{panorama_api_id}/floor-plan-position | panoramas:write |
| POST | /api/v1/360/experiences/{experience_api_id}/external-media-validations | panoramas:write |
| GET | /api/v1/360/experiences/{experience_api_id}/external-media-validations/{validation_api_id} | panoramas:write |
| GET | /api/v1/360/external-media-validations/{validation_api_id}/browser-check | challenge token |
| POST | /api/v1/360/external-media-validations/{validation_api_id}/browser-check | challenge token |
| POST | /api/v1/360/creator-sessions | creator_sessions:issue |
Public (no Partner API key):
| Method | Path |
|---|---|
| GET | /aw360/public/{public_id} |
| GET | /aw360/public/{public_id}/embed-policy |
| POST | /aw360/public/{public_id}/usage/open |
Frontend documents:
| Surface | Path |
|---|---|
| Viewer | /aw360/v/{public_id} |
| Creator | /aw360/c/{session_api_id} |
| External media check | /aw360/media-check |
Hosted Organization settings (/archwalk-360/integrations, Clerk) and Manager (/archwalk-360/experiences) are not the Partner API.
OpenAPI at {ARCHWALK_API_BASE}/openapi.json includes the whole ArchWalk backend. Filter paths that start with /api/v1/360 (and /aw360/public if needed). Do not treat architect/admin routes as partner contract.
28. Reference integration
A Bookme-style partner host lives in the ArchWalk frontend repository at examples/aw360-bookme-reference/. It is executable documentation, not a production ArchWalk surface.
It demonstrates:
- Partner marketplace authentication (mocked) with no ArchWalk / Clerk login for the seller
- Server-side
AW360_REFERENCE_API_KEY(aw360_sk_…) that never reaches the browser POST /api/v1/360/experiencescreate-or-resolve byexternal_resource_idPOST /api/v1/360/creator-sessionsmint, iframe/aw360/c/{api_id}, exact-origincreator:ready-for-init/creator:init- Public listing iframe
/aw360/v/{public_id}after publication - Hybrid plugin import at
/seller/plugin(200 mock physical rooms → 8 visual variants → 8 Experiences) and guest reuse at/listing/plugin?room=101
See that folder's README.md for environment variables, bootstrap (scripts/aw360_reference_bootstrap.py in the backend repository), and how to run the seller, listing, and plugin pages.