curl --request POST \
--url https://apis.viggle.ai/v1/scenes/pano \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"sessionId": "living-room-01",
"prompt": "<string>",
"referenceImages": [
"<string>"
],
"quality": "low",
"panoramaUri": "<string>",
"spzOutputUrl": "<string>",
"outputUris": {
"spz": "<string>",
"preview": "<string>",
"panorama": "<string>"
}
}
'import requests
url = "https://apis.viggle.ai/v1/scenes/pano"
payload = {
"sessionId": "living-room-01",
"prompt": "<string>",
"referenceImages": ["<string>"],
"quality": "low",
"panoramaUri": "<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',
prompt: '<string>',
referenceImages: ['<string>'],
quality: 'low',
panoramaUri: '<string>',
spzOutputUrl: '<string>',
outputUris: {spz: '<string>', preview: '<string>', panorama: '<string>'}
})
};
fetch('https://apis.viggle.ai/v1/scenes/pano', 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/pano",
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',
'prompt' => '<string>',
'referenceImages' => [
'<string>'
],
'quality' => 'low',
'panoramaUri' => '<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/pano"
payload := strings.NewReader("{\n \"sessionId\": \"living-room-01\",\n \"prompt\": \"<string>\",\n \"referenceImages\": [\n \"<string>\"\n ],\n \"quality\": \"low\",\n \"panoramaUri\": \"<string>\",\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/pano")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"sessionId\": \"living-room-01\",\n \"prompt\": \"<string>\",\n \"referenceImages\": [\n \"<string>\"\n ],\n \"quality\": \"low\",\n \"panoramaUri\": \"<string>\",\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/pano")
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 \"prompt\": \"<string>\",\n \"referenceImages\": [\n \"<string>\"\n ],\n \"quality\": \"low\",\n \"panoramaUri\": \"<string>\",\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
}
}
}创建场景
用文字描述或 360° 全景图构建 3D 高斯泼溅场景。
curl --request POST \
--url https://apis.viggle.ai/v1/scenes/pano \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"sessionId": "living-room-01",
"prompt": "<string>",
"referenceImages": [
"<string>"
],
"quality": "low",
"panoramaUri": "<string>",
"spzOutputUrl": "<string>",
"outputUris": {
"spz": "<string>",
"preview": "<string>",
"panorama": "<string>"
}
}
'import requests
url = "https://apis.viggle.ai/v1/scenes/pano"
payload = {
"sessionId": "living-room-01",
"prompt": "<string>",
"referenceImages": ["<string>"],
"quality": "low",
"panoramaUri": "<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',
prompt: '<string>',
referenceImages: ['<string>'],
quality: 'low',
panoramaUri: '<string>',
spzOutputUrl: '<string>',
outputUris: {spz: '<string>', preview: '<string>', panorama: '<string>'}
})
};
fetch('https://apis.viggle.ai/v1/scenes/pano', 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/pano",
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',
'prompt' => '<string>',
'referenceImages' => [
'<string>'
],
'quality' => 'low',
'panoramaUri' => '<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/pano"
payload := strings.NewReader("{\n \"sessionId\": \"living-room-01\",\n \"prompt\": \"<string>\",\n \"referenceImages\": [\n \"<string>\"\n ],\n \"quality\": \"low\",\n \"panoramaUri\": \"<string>\",\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/pano")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"sessionId\": \"living-room-01\",\n \"prompt\": \"<string>\",\n \"referenceImages\": [\n \"<string>\"\n ],\n \"quality\": \"low\",\n \"panoramaUri\": \"<string>\",\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/pano")
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 \"prompt\": \"<string>\",\n \"referenceImages\": [\n \"<string>\"\n ],\n \"quality\": \"low\",\n \"panoramaUri\": \"<string>\",\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
}
}
}sessionId,以及 prompt 或 panoramaUri 二者之一。传 prompt 时,场景模型先生成一张 360° 全景图(可用参考图引导),再据此构建场景;传 panoramaUri 时,直接用你的全景图构建场景。接口是异步的:立即返回 processing 状态、ID 以 scn_ 开头的场景任务,之后用查询场景任务轮询,直到任务结束。
pano 任务就绪后,你会得到 .spz 格式的高斯泼溅场景、全景图,以及一个由带位姿视图组成的 initial 图像批次。把该批次的 batchId 传给生成场景视图和重建场景,即可扩展场景。
会话
每个场景请求都要指定一个由你自定的sessionId。一个会话包含一个场景,以及为它生成的所有图像批次:
sessionId为 1–128 个 ASCII 字母、数字、下划线或连字符。它的作用域是你的账号,不会与其他客户冲突。- 一个会话同一时间只运行一个任务。若仍有任务处于
processing,再提交会返回409,该任务的 ID 在error.details.running_task_id中,请先轮询它结束后再提交。 generate和reconstruct的referenceBatchIds只接受同一会话中生成的批次;传入其他会话的批次会使任务以INVALID_REQUEST失败。
请求参数
使用application/json,最大 1 MiB。不接受未知字段。
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
sessionId | string | 是 | — | 构建场景所在的会话,见上方“会话”一节。 |
prompt | string | prompt / panoramaUri 二选一 | — | 场景描述,1–1000 个字符,不能全是空白。全景图据此生成。 |
referenceImages | string[] | 否 | [] | 最多 8 个图片 URL,用于引导全景图生成。仅与 prompt 一起使用。 |
quality | string | 否 | low | 全景图生成质量:fast、low、medium 或 high。仅与 prompt 一起使用。 |
panoramaUri | string | prompt / panoramaUri 二选一 | — | 用于构建场景的现有 360° 全景图 URL。不能与 prompt、referenceImages 或 quality 同时使用。 |
spzOutputUrl | string | 否 | — | 你自己的 .spz 场景上传地址,见下方“上传到自己的存储”一节。 |
outputUris | object | 否 | — | 按文件指定你自己的上传地址:spz、preview、panorama。不能与 spzOutputUrl 同时使用。 |
URL 规则
panoramaUri、referenceImages、spzOutputUrl 和 outputUris 由 Viggle 服务器读取或写入,因此每个地址都必须:
- 使用
http或https,不含用户名或密码,如指定端口则端口必须有效; - 主机只解析到公网地址。私有地址、回环地址、链路本地地址和运营商级 NAT 地址会被拒绝,返回
400。
上传到自己的存储
spzOutputUrl,或 outputUris.spz / outputUris.preview / outputUris.panorama,是 Viggle 以 HTTP PUT 上传成品文件的地址,通常是 S3、R2 或 GCS 的预签名上传链接。它们是可选的;不传时,请在任务可读期间从任务结果中下载文件。
spzOutputUrl等同于outputUris.spz,二者只传其一。- 上传到你地址的
.spz不会由 Viggle 保留:结果中的spz.url就是你的地址。 - 上传到你地址的预览图或全景图 Viggle 也会保留一份,结果中仍链接到 Viggle 的签名副本。
outputUris中值为null的项会被忽略。所有场景操作都接受这些字段。
响应参数
返回200 OK 和 processing 状态的场景任务,结构与查询场景任务返回的相同:
{
"id": "scn_0199f0c2-6a1e-7c3d-9b2a-4f5e6d7c8b9a.living-room-01",
"sessionId": "living-room-01",
"operation": "pano",
"status": "processing",
"createdAt": 1791856740000,
"completedAt": null,
"result": null,
"error": null
}
常见错误
| 错误 | 原因 |
|---|---|
400 INVALID_REQUEST | 字段缺失、不合法或未知,或某个 URL 不符合上方的 URL 规则。message 会指出具体字段。不会创建任务。 |
401 UNAUTHENTICATED | 缺少或无效的 API 密钥。 |
409 INVALID_REQUEST | 该会话已有任务处于 processing。error.details.running_task_id 即该任务,请先轮询它再重新提交。 |
503 SERVICE_BUSY | 场景模型暂时不可用,可重试。 |
示例
curl -X POST https://apis.viggle.ai/v1/scenes/pano \
-H "Authorization: Bearer $VIGGLE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"sessionId": "living-room-01",
"prompt": "A sunlit Scandinavian living room with a grey sofa and large windows",
"quality": "low"
}'
import os, requests
response = requests.post(
"https://apis.viggle.ai/v1/scenes/pano",
headers={"Authorization": f"Bearer {os.environ['VIGGLE_API_KEY']}"},
json={
"sessionId": "living-room-01",
"prompt": "A sunlit Scandinavian living room with a grey sofa and large windows",
"quality": "low",
},
)
response.raise_for_status()
task = response.json()
const response = await fetch("https://apis.viggle.ai/v1/scenes/pano", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.VIGGLE_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
sessionId: "living-room-01",
prompt: "A sunlit Scandinavian living room with a grey sofa and large windows",
quality: "low",
}),
});
const task = await response.json();
使用现有全景图
curl -X POST https://apis.viggle.ai/v1/scenes/pano \
-H "Authorization: Bearer $VIGGLE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"sessionId": "courtyard-01",
"panoramaUri": "https://example.com/courtyard-360.jpg"
}'
带参考图并上传到自己的存储
curl -X POST https://apis.viggle.ai/v1/scenes/pano \
-H "Authorization: Bearer $VIGGLE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"sessionId": "loft-01",
"prompt": "An industrial loft with exposed brick and warm evening light",
"referenceImages": ["https://example.com/loft-a.jpg", "https://example.com/loft-b.jpg"],
"quality": "high",
"outputUris": {
"spz": "https://my-bucket.s3.amazonaws.com/scenes/loft-01.spz?X-Amz-Signature=...",
"panorama": "https://my-bucket.s3.amazonaws.com/scenes/loft-01.png?X-Amz-Signature=..."
}
}'
下一步
查询场景任务
快速入门:场景模型
授权
服务端 SDK 使用项目 API 密钥,Remote MCP 使用 OAuth 访问令牌。不要在浏览器代码中暴露项目密钥。
请求头
可选的调用者自定义关联 ID,最长 128 字符。服务会在响应头返回实际使用的值,便于追踪和支持排查。
1 - 128可选来源标签,最长 128 字符,用于识别 SDK、集成、产品入口或内部工作流。
1 - 128请求体
POST /v1/scenes/pano 的请求体。prompt(可带 referenceImages 与 quality)和 panoramaUri 二选一;spzOutputUrl 与 outputUris 不能同时使用。输入 URL 必须是 http(s)、不含账号密码、且主机只解析到公网地址。
调用方自定的会话 ID,按账户隔离。一个会话对应一个场景及其全部图像批次,同一时间只运行一个任务;已有任务 processing 时再提交会返回 409,error.details.running_task_id 为正在运行的任务。
1 - 128^[A-Za-z0-9_-]+$"living-room-01"
场景描述,先据此生成 360° 全景图。不能为空白。
1 - 1000引导全景图生成的图片 URL,仅与 prompt 一起使用。
8全景图生成质量,仅与 prompt 一起使用。
fast, low, medium, high 已有 360° 全景图的 URL,直接据此构建场景;不能与 prompt、referenceImages、quality 同时使用。
等同于 outputUris.spz。
调用方自己的上传地址,Viggle 完成后用 PUT 把对应文件上传过去;值为 null 的项会被忽略。上传到这里的 .spz Viggle 不再保留,结果中的 spz.url 即该地址;预览图和全景图上传后 Viggle 仍保留一份,结果中链接指向 Viggle 的副本。每个地址必须是 http(s)、不含账号密码、且主机只解析到公网地址。
Show child attributes
Show child attributes
响应
场景任务已启动。
场景任务(以 scn_ 开头),所有 /v1/scenes 提交接口和 GET /v1/scenes/{task_id} 都返回它。ready、failed、cancelled 为终态。任务结束后约 72 小时内可查询。
任务 ID,格式为 scn_{runId}.{sessionId}。
custom-pano 任务也返回 pano。
pano, generate, reconstruct processing, ready, failed, cancelled 开始时间,Unix 毫秒。
结束时间,Unix 毫秒;processing 时为 null。
只在 ready 时有值。
Show child attributes
Show child attributes
只在 failed 时有值。code 为 INVALID_REQUEST(不可重试,模型拒绝了输入,例如引用了其他会话的批次)或 PROCESSING_FAILED(可重试)。
Show child attributes
Show child attributes

