Media
Choose whether ArchWalk or your own CDN hosts Panorama files.
ArchWalk stores panorama bytes.
Your organization keeps media on its CDN.
Need to create the source images first? See the Capture guide.
9. Quickstart B — external media
Same as Quickstart A, replacing the upload with validation.
Responsibility: ArchWalk does not host or control this file. You own HTTPS, CORS, durability, and a stable URL. Short-lived signed URLs (AWS SigV4, GCS, Azure SAS, CloudFront signed query params) are rejected.
VALIDATION_JSON=$(curl -sS -X POST "$ARCHWALK_API_BASE/api/v1/360/experiences/$EXPERIENCE_ID/external-media-validations" \
-H "Authorization: Bearer $AW360_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url":"https://cdn.acmestay.example/panoramas/room_12345.jpg"}')
export VALIDATION_ID=$(echo "$VALIDATION_JSON" | jq -r .validation_id)
export BROWSER_CHECK_URL=$(echo "$VALIDATION_JSON" | jq -r .browser_check_url)
The create response includes challenge_token and browser_check_url once (BROWSER_CHECK_URL). Open that URL (ArchWalk Viewer attestation). When GET validation shows status: "ready" and expired: false, attach using $VALIDATION_ID and the current $DRAFT_REVISION:
PANORAMA_JSON=$(curl -sS -X POST "$ARCHWALK_API_BASE/api/v1/360/experiences/$EXPERIENCE_ID/panoramas" \
-H "Authorization: Bearer $AW360_API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"expected_draft_revision\": $DRAFT_REVISION,
\"label\": \"Living room\",
\"is_starting\": true,
\"media\": { \"type\": \"external\", \"validation_id\": \"$VALIDATION_ID\" }
}")
export DRAFT_REVISION=$(echo "$PANORAMA_JSON" | jq -r .draft_revision)
Then publish as in Quickstart A (step 6), using the updated $DRAFT_REVISION.
13. Managed upload flow
Routes (all scope panoramas:upload):
POST /api/v1/360/experiences/{experience_id}/panorama-uploads- Client
PUTtoupload_url POST .../panorama-uploads/{upload_id}/completeGET .../panorama-uploads/{upload_id}DELETE .../panorama-uploads/{upload_id}(discard)
Initiate body: content_type, declared_size_bytes (> 0), optional client_request_id (1–64 chars).
Initiate response extra fields: upload_url, method (PUT), headers (include Content-Type and If-None-Match: *).
Treat upload_url as a secret: short-lived, do not log it.
Accepted types and ceilings (operational defaults, not an SLA)
| Constraint | Default |
|---|---|
| MIME | image/jpeg, image/png, image/webp |
| Declared / verified size | 150 MiB |
| Minimum width | 2048 px |
| Maximum width | 16384 px |
| Maximum pixels | 16384 × 8192 |
| Aspect | ~2:1 equirectangular (about 2% tolerance) |
| Presigned PUT TTL | 15 minutes |
| Ready but unattached TTL | 24 hours |
Do not alter returned object keys. ArchWalk chooses storage keys.
client_request_id retries: same Experience + same id + same content_type/size returns the existing upload. A different payload with the same id is rejected.
Completing an already-ready upload is safe (returns the ready resource).
14. External media flow
Warning: ArchWalk does not own partner-hosted files. If the object disappears, CORS breaks, or the URL becomes a signed/temporary link, the Viewer can fail. External media counts as zero managed-storage bytes.
Partner routes (scope panoramas:write)
POST /api/v1/360/experiences/{experience_id}/external-media-validationsGET /api/v1/360/experiences/{experience_id}/external-media-validations/{validation_id}
Body: { "url": "https://..." } (max 2048 characters).
Create returns the validation resource plus once: challenge_token, browser_check_url.
GET never returns the challenge again.
Browser attestation (not Partner-key auth)
GET /api/v1/360/external-media-validations/{validation_id}/browser-checkPOST /api/v1/360/external-media-validations/{validation_id}/browser-check
These use Authorization: Bearer with the challenge token, not the API key. The hosted browser_check_url already encodes that flow (/aw360/media-check#<challenge>). POST body: { "outcome": "rendered" } or { "outcome": "failed" }.
Statuses: server probe first (server_validated), then browser ready or failed. expired is derived from ready_expires_at (default ready lifetime 24 hours). Attach only a non-expired ready validation.
URL rules partners must satisfy
httpsonly (no http upgrade/downgrade)- Default allowed port 443
- Public global addresses only (no localhost, private, link-local, metadata, CGNAT, etc.)
- Redirects revalidated; https→http rejected; redirect loop/count limited
- No userinfo in the URL
- Known cloud signed-URL query patterns rejected
- Uncompressed responses (
Content-Encodingidentity) - Trustworthy
Content-Length/Content-Rangesize - JPEG/PNG/WebP; ~2:1; same dimension ceilings as managed uploads
- CORS:
Access-Control-Allow-Originmust be*or ArchWalk's validation origin
Anonymous probe: ArchWalk does not forward your cookies or Authorization to the media host.
15. External media troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
archwalk_360_external_url_invalid | Not a clean https URL | Use https, no userinfo/path tricks, port 443 |
archwalk_360_external_url_appears_temporary | Signed/SAS/CloudFront query params | Publish a stable public CDN URL |
archwalk_360_external_url_target_not_allowed | Private/special IP after DNS | Host on public CDN |
archwalk_360_external_unsafe_redirect | Bad/loop/http redirect | Redirect only to allowed https URLs |
archwalk_360_external_cors_not_allowed | Missing/wrong ACAO | Allow ArchWalk origin or * |
archwalk_360_external_content_type_mismatch | Wrong Content-Type | Serve image/jpeg, image/png, or image/webp |
archwalk_360_external_size_unknown / too_large | Missing size or over cap | Send valid length/range; stay under size ceiling |
archwalk_360_external_host_timeout | Slow or Slowloris origin | Serve promptly; avoid header/body drip |
| Browser check fails | CORS, mixed content, decode failure | Match server CORS; use a real equirectangular image |
| Health later unhealthy | File moved or CORS changed | Keep the final_url durable; re-validate and re-attach |
Validation expired: true | Ready TTL elapsed | Run a new validation before attach/publish |