Skip to main content

Panorama Editing

Editing a world works the same way in the API as it does in the app: you edit the panorama behind a world, iterate until you like it, then create a new world from the final panorama.

Endpoints

EndpointScopePurpose
POST /v1/panoramas/editworlds:createEdit a panorama and return the updated panorama.
GET /v1/panoramasworlds:readList panoramas created by the current API key.
GET /v1/panoramas/{panorama_id}worlds:readGet a panorama.
GET /v1/panoramas/{panorama_id}/downloadworlds:read302-redirect to a short-lived signed image URL.

The edit loop

POST /v1/panoramas/edit { source: { type: "request_id", request_id }, prompt }
-> { panorama_id: "pano_1", ... }
POST /v1/panoramas/edit { source: { type: "panorama_id", panorama_id: "pano_1" }, prompt }
-> { panorama_id: "pano_2", parent_panorama_id: "pano_1", ... }
POST /v1/worlds { input: { type: "panorama_id", panorama_id: "pano_2" } }
-> { request_id: "req_...", status: "PENDING", ... } # normal world flow

Edit a panorama

The source is the panorama you want to edit. It can be:

  • request_id — an API-created world request owned by the same user (uses that world's panorama).
  • world_id — the world.id from a create result (API-created worlds only, owned by the same user even if created by a different API key).
  • panorama_id — a panorama from a previous edit (this is how you iterate).
API_KEY="spt_live_..."

curl -sX POST "https://api.spaitial.ai/v1/panoramas/edit" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"source": { "type": "request_id", "request_id": "req_abc123" },
"prompt": "make it night time with warm street lights"
}'

The edit runs synchronously and returns 200 OK with the new panorama. Lineage fields use public IDs (req_... / pano_...), not internal UUIDs:

{
"panorama_id": "pano_def456",
"status": "READY",
"panorama_url": "https://api.spaitial.ai/v1/panoramas/pano_def456/download",
"prompt": "make it night time with warm street lights",
"source_request_id": "req_abc123",
"source_world_id": "…",
"parent_panorama_id": null,
"consumed": false,
"created_at": "2026-05-20T10:42:00.000Z",
"expires_at": "2026-05-21T10:42:00.000Z"
}

Optional reference images

Provide up to 3 reference images to guide the edit. Each image is a file_id (from POST /v1/files), an https url, or inline base64:

{
"source": { "type": "panorama_id", "panorama_id": "pano_def456" },
"prompt": "match the materials in the reference",
"images": [{ "type": "url", "image_url": "https://example.com/reference.jpg" }]
}

The prompt and all reference images are moderated before the edit runs. The output always preserves the source panorama aspect ratio so it can be used safely for world generation.

Iterate

Pass the returned panorama_id back as the source to keep refining. Each iteration records parent_panorama_id so you can trace the chain.

curl -sX POST "https://api.spaitial.ai/v1/panoramas/edit" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"source": { "type": "panorama_id", "panorama_id": "pano_def456" },
"prompt": "add neon signs on the storefronts"
}'

Create a world from a panorama

When you are happy with a panorama, create a world from it. Generation starts from the panorama stage (it skips image-to-panorama), and the new world is linked back to its source world.

curl -sX POST "https://api.spaitial.ai/v1/worlds" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"input": { "type": "panorama_id", "panorama_id": "pano_def456" },
"title": "Edited world"
}'

This returns the usual 202 Accepted world job — poll its status_url or use a webhook as with any other world. Creating a world from a panorama does not consume the panorama permanently; you can still iterate or create again from it until it expires.

Idempotency

POST /v1/panoramas/edit accepts an Idempotency-Key header. Replaying the same key returns the previously created panorama instead of running a new edit; reusing a key with a different body returns 409 IDEMPOTENCY_KEY_REUSED.

Errors

HTTPerror.codeWhen it happens
400INVALID_INPUTMalformed request (e.g. more than 3 images, bad source).
401UNAUTHORIZEDMissing, invalid, revoked, or expired API key.
402INSUFFICIENT_CREDITSNot enough credits to run the edit.
403FORBIDDENAPI key lacks the required scope.
403MODERATION_REJECTEDThe prompt or a reference image failed moderation.
404REQUEST_NOT_FOUNDrequest_id / world_id does not exist or belongs to another caller.
404PANORAMA_NOT_FOUNDpanorama_id does not exist or belongs to another caller.
409RESOURCE_NOT_READYThe source world is not finished generating yet.
410PANORAMA_EXPIREDThe panorama is past its 24-hour TTL.
502EDIT_FAILEDThe edit could not be completed; retry.

See Moderation for content screening and Credits & Billing for edit pricing.