curl --request POST \
--url https://apis.viggle.ai/v1/scenes/reconstruct \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"sessionId": "living-room-01",
"referenceBatchIds": [
"<string>"
],
"spzOutputUrl": "<string>",
"outputUris": {
"spz": "<string>",
"preview": "<string>",
"panorama": "<string>"
}
}
'import requests
url = "https://apis.viggle.ai/v1/scenes/reconstruct"
payload = {
"sessionId": "living-room-01",
"referenceBatchIds": ["<string>"],
"spzOutputUrl": "<string>",
"outputUris": {
"spz": "<string>",
"preview": "<string>",
"panorama": "<string>"
}
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
sessionId: 'living-room-01',
referenceBatchIds: ['<string>'],
spzOutputUrl: '<string>',
outputUris: {spz: '<string>', preview: '<string>', panorama: '<string>'}
})
};
fetch('https://apis.viggle.ai/v1/scenes/reconstruct', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://apis.viggle.ai/v1/scenes/reconstruct",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'sessionId' => 'living-room-01',
'referenceBatchIds' => [
'<string>'
],
'spzOutputUrl' => '<string>',
'outputUris' => [
'spz' => '<string>',
'preview' => '<string>',
'panorama' => '<string>'
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://apis.viggle.ai/v1/scenes/reconstruct"
payload := strings.NewReader("{\n \"sessionId\": \"living-room-01\",\n \"referenceBatchIds\": [\n \"<string>\"\n ],\n \"spzOutputUrl\": \"<string>\",\n \"outputUris\": {\n \"spz\": \"<string>\",\n \"preview\": \"<string>\",\n \"panorama\": \"<string>\"\n }\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://apis.viggle.ai/v1/scenes/reconstruct")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"sessionId\": \"living-room-01\",\n \"referenceBatchIds\": [\n \"<string>\"\n ],\n \"spzOutputUrl\": \"<string>\",\n \"outputUris\": {\n \"spz\": \"<string>\",\n \"preview\": \"<string>\",\n \"panorama\": \"<string>\"\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://apis.viggle.ai/v1/scenes/reconstruct")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"sessionId\": \"living-room-01\",\n \"referenceBatchIds\": [\n \"<string>\"\n ],\n \"spzOutputUrl\": \"<string>\",\n \"outputUris\": {\n \"spz\": \"<string>\",\n \"preview\": \"<string>\",\n \"panorama\": \"<string>\"\n }\n}"
response = http.request(request)
puts response.read_body{
"id": "<string>",
"sessionId": "<string>",
"operation": "pano",
"status": "processing",
"createdAt": 123,
"completedAt": 123,
"result": {
"operation": "pano",
"sessionId": "<string>",
"spz": {
"url": "<string>"
},
"preview": {
"url": "<string>"
},
"panorama": {
"url": "<string>"
},
"outputBatch": {
"batchId": "<string>",
"createdAt": 123,
"manifest": {
"url": "<string>"
},
"kind": "initial",
"views": [
{
"index": 1,
"url": "<string>",
"c2w": [
[
1,
0,
0,
0
],
[
0,
1,
0,
0
],
[
0,
0,
1,
0
],
[
0,
0,
0,
1
]
],
"intrinsic": [
123
]
}
]
},
"referenceViewCount": 123,
"newViewCount": 123,
"seconds": {}
},
"error": {
"code": "UNAUTHENTICATED",
"message": "<string>",
"retryable": true,
"request_id": "<string>",
"details": {},
"remediation": {
"action": "<string>",
"retry_after_ms": 1
}
}
}{
"error": {
"code": "UNAUTHENTICATED",
"message": "<string>",
"retryable": true,
"request_id": "<string>",
"details": {},
"remediation": {
"action": "<string>",
"retry_after_ms": 1
}
}
}{
"error": {
"code": "UNAUTHENTICATED",
"message": "<string>",
"retryable": true,
"request_id": "<string>",
"details": {},
"remediation": {
"action": "<string>",
"retry_after_ms": 1
}
}
}{
"error": {
"code": "UNAUTHENTICATED",
"message": "<string>",
"retryable": true,
"request_id": "<string>",
"details": {},
"remediation": {
"action": "<string>",
"retry_after_ms": 1
}
}
}{
"error": {
"code": "UNAUTHENTICATED",
"message": "<string>",
"retryable": true,
"request_id": "<string>",
"details": {},
"remediation": {
"action": "<string>",
"retry_after_ms": 1
}
}
}Reconstruct Scene
Rebuild a session’s 3D scene from its image batches, as a new .spz.
curl --request POST \
--url https://apis.viggle.ai/v1/scenes/reconstruct \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"sessionId": "living-room-01",
"referenceBatchIds": [
"<string>"
],
"spzOutputUrl": "<string>",
"outputUris": {
"spz": "<string>",
"preview": "<string>",
"panorama": "<string>"
}
}
'import requests
url = "https://apis.viggle.ai/v1/scenes/reconstruct"
payload = {
"sessionId": "living-room-01",
"referenceBatchIds": ["<string>"],
"spzOutputUrl": "<string>",
"outputUris": {
"spz": "<string>",
"preview": "<string>",
"panorama": "<string>"
}
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
sessionId: 'living-room-01',
referenceBatchIds: ['<string>'],
spzOutputUrl: '<string>',
outputUris: {spz: '<string>', preview: '<string>', panorama: '<string>'}
})
};
fetch('https://apis.viggle.ai/v1/scenes/reconstruct', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://apis.viggle.ai/v1/scenes/reconstruct",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'sessionId' => 'living-room-01',
'referenceBatchIds' => [
'<string>'
],
'spzOutputUrl' => '<string>',
'outputUris' => [
'spz' => '<string>',
'preview' => '<string>',
'panorama' => '<string>'
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://apis.viggle.ai/v1/scenes/reconstruct"
payload := strings.NewReader("{\n \"sessionId\": \"living-room-01\",\n \"referenceBatchIds\": [\n \"<string>\"\n ],\n \"spzOutputUrl\": \"<string>\",\n \"outputUris\": {\n \"spz\": \"<string>\",\n \"preview\": \"<string>\",\n \"panorama\": \"<string>\"\n }\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://apis.viggle.ai/v1/scenes/reconstruct")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"sessionId\": \"living-room-01\",\n \"referenceBatchIds\": [\n \"<string>\"\n ],\n \"spzOutputUrl\": \"<string>\",\n \"outputUris\": {\n \"spz\": \"<string>\",\n \"preview\": \"<string>\",\n \"panorama\": \"<string>\"\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://apis.viggle.ai/v1/scenes/reconstruct")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"sessionId\": \"living-room-01\",\n \"referenceBatchIds\": [\n \"<string>\"\n ],\n \"spzOutputUrl\": \"<string>\",\n \"outputUris\": {\n \"spz\": \"<string>\",\n \"preview\": \"<string>\",\n \"panorama\": \"<string>\"\n }\n}"
response = http.request(request)
puts response.read_body{
"id": "<string>",
"sessionId": "<string>",
"operation": "pano",
"status": "processing",
"createdAt": 123,
"completedAt": 123,
"result": {
"operation": "pano",
"sessionId": "<string>",
"spz": {
"url": "<string>"
},
"preview": {
"url": "<string>"
},
"panorama": {
"url": "<string>"
},
"outputBatch": {
"batchId": "<string>",
"createdAt": 123,
"manifest": {
"url": "<string>"
},
"kind": "initial",
"views": [
{
"index": 1,
"url": "<string>",
"c2w": [
[
1,
0,
0,
0
],
[
0,
1,
0,
0
],
[
0,
0,
1,
0
],
[
0,
0,
0,
1
]
],
"intrinsic": [
123
]
}
]
},
"referenceViewCount": 123,
"newViewCount": 123,
"seconds": {}
},
"error": {
"code": "UNAUTHENTICATED",
"message": "<string>",
"retryable": true,
"request_id": "<string>",
"details": {},
"remediation": {
"action": "<string>",
"retry_after_ms": 1
}
}
}{
"error": {
"code": "UNAUTHENTICATED",
"message": "<string>",
"retryable": true,
"request_id": "<string>",
"details": {},
"remediation": {
"action": "<string>",
"retry_after_ms": 1
}
}
}{
"error": {
"code": "UNAUTHENTICATED",
"message": "<string>",
"retryable": true,
"request_id": "<string>",
"details": {},
"remediation": {
"action": "<string>",
"retry_after_ms": 1
}
}
}{
"error": {
"code": "UNAUTHENTICATED",
"message": "<string>",
"retryable": true,
"request_id": "<string>",
"details": {},
"remediation": {
"action": "<string>",
"retry_after_ms": 1
}
}
}{
"error": {
"code": "UNAUTHENTICATED",
"message": "<string>",
"retryable": true,
"request_id": "<string>",
"details": {},
"remediation": {
"action": "<string>",
"retry_after_ms": 1
}
}
}.spz Gaussian splat from the image batches you list — typically the initial batch from Create Scene plus the generated batches from Generate Scene Views. The more of the scene the views cover, the more complete the result.
The request is asynchronous: it answers with a processing Scene Task; poll it until it finishes.
Request parameters
Sendapplication/json, up to 1 MiB. Unknown fields are rejected.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
sessionId | string | Yes | — | The session whose scene to rebuild — see Sessions. |
referenceBatchIds | string[] | Yes | — | 1–35 batch IDs from this session, as returned in result.outputBatch.batchId. Lowercase UUIDs, no repeats. |
spzOutputUrl | string | No | — | Your own upload URL for the .spz scene — see Upload to your own storage. |
outputUris | object | No | — | Your own upload URLs per file: spz, preview, panorama. Can’t be combined with spzOutputUrl. |
targetC2w, projection, fov, and seed belong to Generate Scene Views and are rejected here.
Response parameters
Returns200 OK with a Scene Task in processing state:
{
"id": "scn_0199f0c6-9a8b-7c6d-8e5f-4a3b2c1d0e9f.living-room-01",
"sessionId": "living-room-01",
"operation": "reconstruct",
"status": "processing",
"createdAt": 1791857000000,
"completedAt": null,
"result": null,
"error": null
}
ready, result.spz.url is the rebuilt scene and result.referenceViewCount is how many views it used. result.outputBatch is null: reconstruct adds no images to the session.
Common errors
| Error | Cause |
|---|---|
400 INVALID_REQUEST | A field is missing, invalid, or unknown — for example referenceBatchIds is empty, has more than 35 IDs or a repeat, or an ID isn’t a lowercase UUID. |
401 UNAUTHENTICATED | Missing or invalid API key. |
409 INVALID_REQUEST | The session already has a task processing; see error.details.running_task_id. |
503 SERVICE_BUSY | The scene model is temporarily unavailable. Retryable. |
INVALID_REQUEST.
Examples
curl -X POST https://apis.viggle.ai/v1/scenes/reconstruct \
-H "Authorization: Bearer $VIGGLE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"sessionId": "living-room-01",
"referenceBatchIds": [
"3f0a6c1e-8d2b-4b7a-9e5c-1a2b3c4d5e6f",
"9b8a7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d"
]
}'
import os, requests
response = requests.post(
"https://apis.viggle.ai/v1/scenes/reconstruct",
headers={"Authorization": f"Bearer {os.environ['VIGGLE_API_KEY']}"},
json={
"sessionId": "living-room-01",
"referenceBatchIds": [
"3f0a6c1e-8d2b-4b7a-9e5c-1a2b3c4d5e6f",
"9b8a7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
],
},
)
response.raise_for_status()
task = response.json()
const response = await fetch("https://apis.viggle.ai/v1/scenes/reconstruct", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.VIGGLE_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
sessionId: "living-room-01",
referenceBatchIds: [
"3f0a6c1e-8d2b-4b7a-9e5c-1a2b3c4d5e6f",
"9b8a7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
],
}),
});
const task = await response.json();
Quick Start: Scene Model
Authorizations
Server-side SDK clients use a project API key. Remote MCP clients use an OAuth access token. Never expose a project API key in browser code.
Headers
Optional caller-supplied correlation ID, up to 128 characters. The service returns the effective value in the response header for tracing and support.
1 - 128Optional source-channel label, up to 128 characters, used to attribute traffic to an SDK, integration, product surface, or internal workflow.
1 - 128Body
Body of POST /v1/scenes/reconstruct. spzOutputUrl and outputUris are mutually exclusive.
Session chosen by the caller, scoped to the caller's account. A
session holds one scene and its image batches, and runs one task at
a time: a submission while a task is still processing answers
409 with that task in error.details.running_task_id.
1 - 128^[A-Za-z0-9_-]+$"living-room-01"
1–35 distinct batch IDs from the same session, as returned in result.outputBatch.batchId. A well-formed ID from another session fails the task with INVALID_REQUEST.
1 - 35 elements^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$Same as outputUris.spz.
The caller's own upload URLs; Viggle PUTs each finished file to
its URL. Null entries are ignored. An .spz sent here is not kept
by Viggle, and the result's spz.url is this URL; a preview or
panorama sent here is also kept, and the result links to Viggle's
copy. Each URL must be http(s), without credentials, on a host
that resolves only to public addresses.
Show child attributes
Show child attributes
Response
Scene task started.
A scene task (scn_ prefix), returned by every /v1/scenes
submission and by GET /v1/scenes/{task_id}. ready, failed, and
cancelled are terminal. A task stays readable for about 72 hours
after it finishes.
Task ID, scn_{runId}.{sessionId}.
A custom-pano task reports pano.
pano, generate, reconstruct processing, ready, failed, cancelled Start time, Unix milliseconds.
End time, Unix milliseconds; null while processing.
Set only when ready.
Show child attributes
Show child attributes
Set only when failed. code is INVALID_REQUEST (not
retryable — the model rejected the input, such as a batch from
another session) or PROCESSING_FAILED (retryable).
Show child attributes
Show child attributes

