Skip to content
← Back to Guides

API reference

11 min read

The Prewalk API is part of the Enterprise plan. With an API key your own software can create tours, upload scenes, publish, read projects and statistics, and get a signed webhook at every processing stage. This page is the reference; the studio and the public player keep working as before.

Base URL and conventions

  • Base URL: https://prewalk.co. Do not use app.prewalk.co: it redirects (308) and most HTTP clients drop the Authorization header on a redirect to another host.
  • Format: JSON in and out (Content-Type: application/json), except scene uploads, which send the image as the raw request body.
  • IDs are 12 lower-case letters and digits, for example teeg8crtm0u0. Dates are ISO 8601 in UTC.
  • Errors always look the same: an HTTP status and {"error": {"code": "…", "message": "…"}}; validation errors add field. The codes are listed at the end of this page.
  • Rate limit: 600 requests a minute per key. Above it you get 429 with a Retry-After header (seconds).

Authentication

Create a key in Settings → API (account owner or admin, Enterprise). A key belongs to the account, can be limited to one brand profile, and has up to three scopes: read, write and stats. The full key is shown once; the list shows only its prefix. Up to 20 keys per account; a revoked key stops at once.

Authorization: Bearer pw_…

A key acts for one account. GET /api/me describes what the key can see: the account and its plan limits, the profiles of that account, the profile the key acts as (actor) and the key itself with its scopes. It never returns the key owner’s e-mail or their other accounts.

Endpoints

Method and pathScopeWhat it does
GET /api/mereadThe key’s account, profiles and scopes
GET /api/toursreadList the account’s tours (limit ≤ 50, cursor, profileId)
GET /api/tours/{id}readOne tour with its scenes, hotspots and settings
GET /api/tours/{id}/eventsreadProcessing progress as a server-sent event stream
POST /api/tourswriteCreate a tour ({"title": "…"})
PATCH /api/tours/{id}writeChange title, description, category, location, tags, slug (the readable address), scene order, start scene, scene names
POST /api/tours/{id}/sceneswriteUpload a 360° image as the raw body
POST /api/content/{id}/visibilitywritePublish, make link-only, or password-protect a tour or project
GET /api/projectsreadList the account’s projects (limit, cursor, profileId)
GET /api/projects/{id}readOne project with its tours, images, videos and floor plans
GET /api/stats/{contentId}statsViews of a tour or project (range in days)
GET /api/stats/{contentId}/export.csvstatsThe same as CSV (table = daily, scenes or referrers)

Every other endpoint answers 403 api_key_forbidden with a key. Account, team, billing, API keys and webhooks are managed in the browser.

Tours

List. GET /api/tours?limit=50 returns {"items": […], "next": "…", "usage": {…}}. Pass next back as cursor for the following page; usage holds the account’s scene count and limits. Each item has id, title, visibility, publishedAt, createdAt, updatedAt, the profile and scene counts.

Read. GET /api/tours/{id} returns the full tour: title, description, category, location, tags, slug, visibility (public, link or password), hasPassword, publishedAt, profile, projectId, startScene, coverScene, revision, scenes in order, hotspots, settings, audio and links (player is the shareable address, post the public page when published).

A scene has id, name, status (queued, processing, ready or error), createdAt, optionally view (the opening direction: yaw, pitch and fov in radians), north (radians; 0 is the centre of the image, increasing to the right), and while it is being processed stage and pct. A ready scene carries meta with its tile levels and the media root.

Create. POST /api/tours with {"title": "Harbor View — Unit 4"} returns the new tour. Without a title it is called “Untitled tour”.

Update. PATCH /api/tours/{id} takes any of these fields and returns the tour:

  • title, description, category, location, tags (array of strings), slug (the public address, lower-case letters, digits and dashes), commentsEnabled;
  • order: an array of scene ids in the order you want; scenes you leave out keep their place after the ones you listed;
  • startScene: a scene id; it is moved to the front (the first scene is the start scene);
  • coverScene: a scene id or null for the start scene;
  • scenes: an object keyed by scene id with name, view ({"yaw", "pitch", "fov"}) and north (degrees).

Send If-Match: "<revision>" with the revision you last read to refuse a write over someone else’s change: the answer is then 409 revision_conflict with the current tour in error.tour. Without If-Match the last writer wins.

Uploading scenes

POST /api/tours/{id}/scenes with the image file as the raw body. Headers:

  • Content-Type: image/jpeg, image/png, image/tiff or image/webp;
  • x-file-name: the file name, URL-encoded (encodeURIComponent); the scene is named after it, so 01-living-room.jpg becomes “01 living room”;
  • x-pw-upload-batch and x-pw-upload-index (optional): when you upload several files of one selection in parallel, give them the same batch id (letters, digits, - and _, up to 32 characters) and their index in the selection; the server then keeps that order no matter which file finishes first. Without them scenes are appended in arrival order.

Accepted images: 2:1 equirectangular panoramas up to the plan’s width (8K on Free, 16K on Pro and above), or 1:1 top-bottom stereo panoramas on Pro and above (left eye on top), up to 1 GiB. The answer is the scene (status: "queued"). Processing runs in stages: the scene becomes visible first (preview tiles), then the full-resolution tiles arrive (“sharpening”) and the VR textures on Pro and above; the two run side by side and either may finish first. The scene’s meta.pending, the event stream and the webhooks tell you which stages are still running.

Errors: 400 unsupported_format (not a JPG, PNG, TIFF or WebP), 400 unsupported_ratio (not 2:1, or 1:1 top-bottom), 400 unreadable_image (a damaged file), 402 plan_required (16K or stereo on a plan without it), 402 quota_exceeded (the account’s scene limit), 413 file_too_large.

Processing progress

GET /api/tours/{id}/events is a server-sent event stream (text/event-stream). Events: hello on connect, queue (the queue position of waiting scenes and a summary of the tour, every few seconds while something runs), scene (status, stage, pct, or error) and tour (the tour changed; read it again). For server-to-server integration prefer the webhooks below.

Publishing and visibility

POST /api/content/{id}/visibility works for tours and projects and returns the content:

  • {"visibility": "link"} — reachable by its address only (the default of every new tour);
  • {"visibility": "public", "rightsAccepted": true} — on the profile, in Explore and indexable; a category is required (set it with PATCH first) and you confirm that you have the right to publish the images. For a project add "tourIds": [...] to publish tours together with it;
  • {"visibility": "password", "password": "…"} — Pro and above; leave password out to keep the current one.

A tour inside a password-protected project cannot go public or get its own password (409 in_password_project); the project’s password is the gate.

Projects

Projects are read-only through the API today. GET /api/projects lists them with id, title, slug, visibility, category, coverUrl, tourCount, imageCount, videoCount and dates; GET /api/projects/{id} adds description, location, tags, urls (public and the private link), the tours with their scenes, and the gallery images, videos and floorplans with pins. Create projects and add media in the studio.

Statistics

GET /api/stats/{contentId}?range=30 returns views for a tour or project. What you get depends on the plan: the all-time total on Free; a zero-filled daily series (from, to, up to 90 days) on Pro; up to 365 days plus sources (page, embed, direct), devices (phone, desktop, VR headset), scenes and the embedding referrers on Office and Enterprise. …/export.csv?range=30&table=daily gives the daily table, table=scenes the views per scene and table=referrers the embedding sites. Views are counted per visitor address and content, once every 30 minutes; nothing personal is stored.

Webhooks

Add up to five webhooks in Settings → API. We send a signed POST with a JSON body:

{"eventId": "evt_…", "eventType": "scene.ready", "occurredAt": "2026-10-22T09:12:03.000Z",
 "data": {"tourId": "…", "sceneId": "…", "status": "ready", "pending": ["refine", "vr"]}}
EventWhenData
scene.readyThe scene is visible (preview tiles)pending: stages still running — refine (full-resolution tiles) and vr (headset textures); [] when the preview is final
scene.sharpenedThe full-resolution tiles are in placestage: "refine", ok (false when the stage was dropped and the scene stays at preview quality), pending
scene.vr_readyThe VR textures are in placestage: "vr", ok, pending
scene.failedProcessing failed for gooderror with the reason
pingThe “Send test” buttonempty

Headers: Prewalk-Signature: ts=<unix seconds>;h1=<hex> where h1 is HMAC-SHA256 of ts + ":" + body with your webhook secret (computed over the exact bytes you received), plus Prewalk-Event-Id and Prewalk-Event-Type. While a secret rotation is in progress the header carries two h1 values (ts=…;h1=<new>;h1=<old>) until the previous secret expires: accept the delivery when any of them verifies. Verify the signature, reject a timestamp older than a few minutes, and deduplicate by eventId: a delivery is retried until your endpoint answers 2xx (ten attempts, 30 s then doubling, about four hours in all) and the retried body is byte-identical. After 20 failed deliveries in a row the webhook is turned off and the account owner gets an e-mail. Endpoints must be https on a public address; redirects are not followed. The secret can be rotated; the previous one stays valid for 24 hours.

A walk through

# 1. create a tour
curl -s https://prewalk.co/api/tours -H "Authorization: Bearer $PW_KEY" \
  -H "Content-Type: application/json" -d '{"title":"Harbor View — Unit 4"}'

# 2. upload two scenes in order
curl -s https://prewalk.co/api/tours/$TOUR/scenes -H "Authorization: Bearer $PW_KEY" \
  -H "Content-Type: image/jpeg" -H "x-file-name: 01-living-room.jpg" \
  -H "x-pw-upload-batch: sel-1" -H "x-pw-upload-index: 0" --data-binary @01-living-room.jpg
curl -s https://prewalk.co/api/tours/$TOUR/scenes -H "Authorization: Bearer $PW_KEY" \
  -H "Content-Type: image/jpeg" -H "x-file-name: 02-kitchen.jpg" \
  -H "x-pw-upload-batch: sel-1" -H "x-pw-upload-index: 1" --data-binary @02-kitchen.jpg

# 3. set a category and publish
curl -s -X PATCH https://prewalk.co/api/tours/$TOUR -H "Authorization: Bearer $PW_KEY" \
  -H "Content-Type: application/json" -d '{"category":"real-estate","location":"Istanbul"}'
curl -s https://prewalk.co/api/content/$TOUR/visibility -H "Authorization: Bearer $PW_KEY" \
  -H "Content-Type: application/json" -d '{"visibility":"public","rightsAccepted":true}'

# 4. views of the last 30 days
curl -s "https://prewalk.co/api/stats/$TOUR?range=30" -H "Authorization: Bearer $PW_KEY"

Embedding

Embedding needs no key: every tour has https://prewalk.co/embed/{id}, and embed.js sizes the frame and opens it full screen on your page. Which websites may show your embeds is set under Settings → Branding. The embedding guide has the details.

Error codes

StatusCodeMeaning
400invalid_requestA field is missing or invalid; field names it
400unsupported_format, unsupported_ratio, unreadable_image, rights_required, category_required, project_needs_contentUpload or publish preconditions (a project needs a public tour, an image or a video)
401unauthorized, invalid_api_keyNo key at all, or a malformed or revoked key
402plan_required, quota_exceededThe plan lacks the feature, or a limit is reached (feature or limit says which)
403api_key_forbidden, api_scope_missingThe endpoint is not open to keys, or the key lacks the scope
404not_foundNot yours, deleted, or no such id
409revision_conflict, slug_taken, in_password_projectConcurrent edit (the current tour is included), address in use, project password rule
413file_too_largeAbove the size limit
429rate_limitedToo many requests; wait Retry-After seconds
503maintenancePrewalk is updating; retry after a minute

Changes

  • 10 Nov 2026: a project can be published or password-protected with only images or videos. A project with nothing to show answers 400 project_needs_content, for public and for password alike; it replaces project_needs_public_tour and project_needs_tour, which are no longer sent. GET /api/projects adds videoCount.
  • 22 Oct 2026: scene.ready carries pending; new events scene.sharpened and scene.vr_ready; upload order headers x-pw-upload-batch and x-pw-upload-index; GET /api/me with a key describes the key’s account only.

Questions or a missing endpoint? Write to hello@prewalk.co.

Share: 𝕏 LinkedIn WhatsApp

  • How to make a 360° virtual tour

    How to make a 360° virtual tour, step by step: shoot or render a 360° image in every room, upload the 2:1 files, link the rooms, and share one link.

  • Your first tour in 10 minutes

    Create a project, drop in your 360° images, link the rooms, publish and share one link. Every step with the exact buttons, in about ten minutes.

  • How to shoot a home with a 360° camera

    Camera height, HDR, the doorway shot, file naming and the right export. A shot list for a two-bedroom flat you can finish in twenty minutes.