# 快速开始
> 复制本页命令,几分钟内跑通:查询可用模型、生成一张图片、提交并轮询视频或 3D 任务、下载结果,并按报错对照表自行排查。
## 准备工作
| 需要什么 | 说明 |
| --- | --- |
| 网关地址 | `https://platform.sgt.site`,下文写作 `$GATEWAY_ORIGIN`。所有接口路径都直接拼在它后面 |
| 虚拟密钥 | 平台管理员为你的账号或应用签发的调用凭证(控制台中显示为“API 密钥”),放在请求头 `Authorization: Bearer <密钥>`。它不是厂商密钥,也不是网页登录密码;其他写法和限制见[地址与鉴权](access.md) |
| 模型授权与预算 | 管理员需要为该密钥授权要用的模型并分配额度。查询模型免费,生成会产生费用 |
| 命令行工具 | `curl`;轮询示例另需 `jq`。也可以直接看下文的 Python 示例 |
先在终端设置两个变量,后面的命令都可以直接复制执行:
```bash
export GATEWAY_ORIGIN="https://platform.sgt.site"
export KEY="替换为你的完整虚拟密钥"
```
设置好后直接做下文第 1 步:它是一条只读请求,能查到模型就说明地址和密钥都正确。
## 从模型到生成结果
图片、视频和 3D 共用同一套流程,区别只在提交路径和参数:
1. **查模型**:`GET /media/v1/models` 返回这把密钥能用的模型 ID 和每个参数的取值范围。
2. **提交生成**:`POST /media/v1/images/generations`、`/videos/generations` 或 `/3d/generations`,响应是一个任务对象,记下 `id`。
3. **查任务**:`GET /media/v1/tasks/{id}`,分别检查生成状态、产物可读状态和账务状态。`succeeded` 但产物为空或不可读时仍需查询原任务。
4. **下载产物**:按 `kind` 和 `state` 遍历全部 `artifacts[]`,完整保存后识别格式。下载链接不带密钥;文件可读不等于客户端可预览。
### 第 1 步:查询可用模型
这一步不花钱,同时用来确认地址和密钥是否正确:
```bash
curl -s "$GATEWAY_ORIGIN/media/v1/models?category=image" \
-H "Authorization: Bearer $KEY" | jq -r '.data[].id'
```
`category` 可选 `image`、`video`、`3d`,省略时返回全部。输出类似下面这样(示意,实际以你的密钥为准):
```text
doubao-seedream-5-0-lite-260128
gemini-3.1-flash-image
Nano Banana 2 Lite
gpt-image-2
```
| 你看到的 | 说明 |
| --- | --- |
| 一行或多行模型 ID | 地址和密钥都正确。把 ID **原样**填进生成请求的 `model` |
| 没有任何输出 | 请求成功,但这把密钥还没有被授权该类模型,请联系管理员 |
| jq 报错,例如 Cannot iterate over null | 请求失败。去掉 `\| jq -r '.data[].id'`,把 `-s` 换成 `-i` 重新执行,按 HTTP 状态和 `error.code` 对照[调试与排错](overview.md#overview-section-3) |
完整的返回 JSON、每个字段的含义见[模型列表:查询媒体模型列表](models.md#models-media-list);展示名称与模型 ID 的对应见[媒体模型名称映射](models.md#models-media-names)。
### 第 2 步:生成一张图片
图片通常在几秒到几十秒内完成。请求里带上 `"wait": 50`,网关会在这次请求中最多等待 50 秒,多数情况下直接拿到结果:
```bash
curl --fail-with-body -sS "$GATEWAY_ORIGIN/media/v1/images/generations" \
-H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: quickstart-image-001" \
-d '{
"model": "doubao-seedream-5-0-lite-260128",
"prompt": "白色背景上的蓝色陶瓷杯,柔和自然光,产品摄影",
"resolution": "2K",
"aspect_ratio": "1:1",
"output_format": "png",
"wait": 50
}' -o image-task.json || { echo "提交未确认,请检查 image-task.json;不要换新键重提"; exit 1; }
TASK_ID=$(jq -er '.id | select(type == "string" and length > 0)' image-task.json) || exit 1
```
在 50 秒内完成时返回 HTTP 200,响应如下(ID、链接与金额均为示意):
```json
{
"id": "task_example",
"object": "media.task",
"model": "doubao-seedream-5-0-lite-260128",
"type": "image",
"status": "succeeded",
"progress": 100,
"created_at": 1791763200,
"started_at": 1791763205,
"finished_at": 1791763209,
"artifacts": [
{
"index": 0,
"kind": "image",
"mime_type": "image/png",
"state": "available",
"url": "https://example.com/image.png",
"url_type": "upstream",
"expires_at": null
}
],
"error": null,
"billing": {"status": "settled", "currency": "CNY", "amount": "0.300000"},
"metadata": {},
"request_id": "req_example"
}
```
`status: "succeeded"` 只表示生成成功。本例显式请求 PNG,但下载时仍检查实际产物;多图不能只读首项。把下文完整 Python 示例保存为 `media_client.py` 后,用响应中的 `id` 恢复查询和下载:
```bash
export TASK_ID
python media_client.py
```
该恢复命令只查询与下载原任务。脚本按文件头及明确 MIME 保存 JPEG/PNG/视频等格式,未知格式保留为 `.bin`。
如果 50 秒内没有完成,会返回 HTTP 202,`status` 为 `queued` 或 `running`,这不是错误。记下 `id`,按第 3 步的方式查询。Nano Banana 四款与 GPT Image 三款也使用相同任务流程,`model` 原样填写目录 ID;其参数和参考图编辑示例见[图片生成](media-images.md)。设置 `wait=0` 可直接取得任务,等待超时后查询同一 ID,不重新生成。
### 第 3 步:提交视频或 3D 并轮询
视频和 3D 通常需要几分钟。提交时用 `"wait": 0` 立即拿到任务 ID,再轮询:
```bash
curl --fail-with-body -sS "$GATEWAY_ORIGIN/media/v1/videos/generations" \
-H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: quickstart-video-001" \
-d '{
"model": "doubao-seedance-2-0-mini-260615",
"prompt": "镜头缓慢推近白色桌面上的蓝色陶瓷杯,柔和自然光",
"duration": 4,
"resolution": "480p",
"aspect_ratio": "16:9",
"generate_audio": false,
"wait": 0
}' -o video-task.json || { echo "提交未确认,请检查 video-task.json;不要换新键重提"; exit 1; }
TASK_ID=$(jq -er '.id | select(type == "string" and length > 0)' video-task.json) || exit 1
echo "$TASK_ID"
```
提交成功时返回 HTTP 202,任务处于排队状态:
```json
{
"id": "task_example",
"object": "media.task",
"model": "doubao-seedance-2-0-mini-260615",
"type": "video",
"status": "queued",
"progress": 0,
"created_at": 1791763200,
"started_at": null,
"finished_at": null,
"artifacts": [],
"error": null,
"billing": {"status": "reserved", "currency": "CNY", "amount": null},
"metadata": {},
"request_id": "req_example"
}
```
命令检查 HTTP 失败并保存完整响应;任务 ID 缺失不能证明没有生成,应保留业务幂等键与响应核对。需要自动恢复提交时使用下方 Python 示例。已有 ID 后,复制下文脚本为 `media_client.py` 并执行:
```bash
export TASK_ID
python media_client.py
```
脚本每 5 秒查询原任务。生成成功后继续检查非空产物、每项的可读状态和账务;下载失败会报告并重新查询,已成功保存的产物在本次运行内不重复下载。总等待时间默认 30 分钟,超时会报错并保留任务 ID。重新运行仍设置同一个 `TASK_ID`,不会创建新的生成。
视频任务可能另带 `cover` 和 `last_frame`,每个产物分别保存,不把封面或尾帧当作视频。
3D 的流程完全相同,只需把提交地址换成 `/media/v1/3d/generations`,请求体换成例如 `{"model": "hy-3d-express", "prompt": "一只坐着的橘猫,卡通风格", "wait": 0}`。各模型参数见[视频生成](media-videos.md)和[3D 生成](media-3d.md)。
## 用 Python 跑通完整流程
把下面的脚本保存为 `media_client.py`。设置 `TASK_ID` 时只恢复已有任务;未设置时才提交示例视频。产物保存到 `output/`:先写 `.part`,检查 HTTP 与字节数后改成正式文件名。空产物、不可读、下载失败和账务待核对都不会静默成功或自动新建生成。需要 Python 3.8+ 和 `pip install requests`。
仅在明确要创建新任务时,先取消旧 `TASK_ID` 并设置持久保存的业务生成 ID(会产生生成费用):
```bash
unset TASK_ID
export BUSINESS_JOB_ID="order-20261015-video-001"
```
同一次生成在断网、超时或进程退出后恢复时,继续使用这个 ID、相同虚拟密钥和相同请求参数;脚本不会自动产生新的 ID。明确要发起另一笔生成时,才换一个新的业务 ID。若已经取得任务 ID,优先按上一节查询该任务,无需重新提交。不要把重启脚本当成新建业务,或用当前时间、随机 UUID 临时替换原 ID;这会失去原请求的幂等关系。平台只在[媒体请求约定](media-access.md)说明的保留期内保证重放关联。
**完整脚本 `media_client.py`(提交、轮询、校验下载与账务确认)**
```python
import os
import time
from pathlib import Path
import re
import requests
GATEWAY = os.environ.get("GATEWAY_ORIGIN", "https://platform.sgt.site")
HEADERS = {"Authorization": "Bearer " + os.environ["KEY"]}
EXTENSIONS = {"image/png": ".png", "image/jpeg": ".jpg", "image/webp": ".webp",
"video/mp4": ".mp4", "video/quicktime": ".mov"}
class DeliveryPending(RuntimeError):
pass
def file_extension(path, mime_type=""):
# 文件头优先于 MIME;ZIP 是包装,不把其中的 OBJ/贴图命名为 GLB。
with open(path, "rb") as file:
head = file.read(64)
for magic, extension in [(b"PK\x03\x04", ".zip"), (b"glTF", ".glb"),
(b"\x89PNG\r\n\x1a\n", ".png"), (b"\xff\xd8\xff", ".jpg"),
(b"Kaydara FBX Binary", ".fbx")]:
if head.startswith(magic):
return extension
if head[:4] == b"RIFF" and head[8:12] == b"WEBP":
return ".webp"
if head[4:8] == b"ftyp":
if head[8:12] == b"qt ":
return ".mov"
if head[8:12] in {b"isom", b"iso2", b"mp41", b"mp42", b"avc1"}:
return ".mp4"
# 通用二进制 MIME、请求参数和签名 URL 均不能证明具体 3D 格式。
return EXTENSIONS.get(mime_type.split(";", 1)[0].strip().lower(), ".bin")
def save_file(url, stem, mime_type=""):
if not isinstance(url, str) or not url.strip():
raise DeliveryPending("空下载地址;重新查询原任务")
if not url.startswith(("https://", "http://")):
raise DeliveryPending("下载地址不是 HTTP(S)")
stem = Path(stem)
stem.parent.mkdir(parents=True, exist_ok=True)
partial = Path(str(stem) + ".part")
# 独立下载请求;不使用带平台 Authorization 的 Session。
with requests.get(url, stream=True, timeout=300,
headers={"Accept-Encoding": "identity"}) as resp:
resp.raise_for_status()
if resp.status_code != 200:
raise DeliveryPending("下载未返回完整文件(HTTP 200)")
if any(kind in resp.headers.get("Content-Type", "") for kind in ("text/html", "application/json")):
raise DeliveryPending("下载返回了网页或 JSON,请核对原任务")
size = 0
with partial.open("wb") as file:
for chunk in resp.iter_content(1 << 20):
file.write(chunk)
size += len(chunk)
length = resp.headers.get("Content-Length")
if not size or (length is not None and not resp.headers.get("Content-Encoding") and size != int(length)):
raise DeliveryPending("下载为空或字节数不完整;保留 .part,重新查询原任务")
extension = file_extension(partial, resp.headers.get("Content-Type") or mime_type)
target = Path(str(stem) + extension)
partial.replace(target) # 完整读取成功后才使用正式文件名
if extension == ".bin":
print("格式未知,已保留原始文件;请用文件识别工具确认,不要只修改扩展名:", target)
print("已保存文件(不代表客户端可预览):", target)
return target
def explain(resp):
body = resp.json()
error = body.get("error", {})
return f"HTTP {resp.status_code} {error.get('code')}: {error.get('message')} (request_id={body.get('request_id')})"
def submit(path, body, idempotency_key):
"""提交生成。网络异常、5xx 或 idempotency_in_progress 时用同一个键重放。"""
headers = {**HEADERS, "Idempotency-Key": idempotency_key}
for _ in range(5):
try:
resp = requests.post(f"{GATEWAY}/media/v1/{path}", json=body, headers=headers, timeout=70)
except requests.RequestException:
time.sleep(5)
continue
if resp.status_code >= 500:
time.sleep(5)
continue
if resp.status_code == 409 and resp.json()["error"]["code"] == "idempotency_in_progress":
time.sleep(int(resp.headers.get("Retry-After", "5")))
continue
if resp.status_code >= 400:
raise RuntimeError(explain(resp)) # 参数、权限或预算问题,修正后再提交
return resp.json()
raise RuntimeError("提交结果未知:稍后用同一个幂等键和同样的请求体重放,不要换新键")
def fetch_task(task_id):
if not re.fullmatch(r"[A-Za-z0-9_-]+", task_id):
raise ValueError("请使用提交返回的非空任务 ID")
resp = requests.get(f"{GATEWAY}/media/v1/tasks/{task_id}", headers=HEADERS, timeout=30)
resp.raise_for_status()
task = resp.json()
if task.get("id") != task_id or not task.get("status"):
raise RuntimeError("查询响应缺少原任务身份或状态")
return task
def download(task, folder="output", saved=None):
saved = {} if saved is None else saved
items = task.get("artifacts") or []
if task.get("status") != "succeeded" or not items:
raise DeliveryPending("生成未成功或产物为空;继续查询原任务")
pending = []
for item in items:
index, kind = item.get("index"), item.get("kind", "unknown")
if not isinstance(index, int) or index < 0 or not re.fullmatch(r"[A-Za-z0-9_-]+", kind):
raise RuntimeError("无法识别产物身份,请保留响应核对")
identity = (index, kind) # 序号仅用于身份与命名,不代表格式或优先级
if item.get("state") != "available" or not item.get("url"):
pending.append(identity)
elif identity not in saved:
saved[identity] = save_file(item["url"], Path(folder) / f"{task['id']}-{index}-{kind}", item.get("mime_type", ""))
if pending or (task.get("error") or {}).get("stage") == "delivery":
raise DeliveryPending(f"产物尚未全部可读:{pending};继续查询原任务")
return list(saved.values())
def complete_task(task, folder="output", interval=5, timeout=1800):
task_id = task["id"]
deadline, saved = time.monotonic() + timeout, {}
while True:
if task.get("status") in {"failed", "cancelled", "expired"}:
raise RuntimeError(f"生成未成功:{task.get('error')};账务:{task.get('billing')};不自动重新生成")
if task.get("status") == "succeeded":
try:
download(task, folder, saved)
billing = task.get("billing") or {}
if billing.get("status") in {"settled", "refunded"} and billing.get("amount") is not None:
print("产物已保存,账务:", billing, ";请另行确认文件格式和客户端预览")
return task
print("文件已保存,账务仍待核对;继续查询原任务")
except (DeliveryPending, requests.RequestException) as error:
# 不输出异常内的完整签名 URL。下载异常只重查原任务,绝不调用 submit。
print("交付未完成:", type(error).__name__, ";将重新查询任务", task_id)
if time.monotonic() >= deadline:
raise TimeoutError(f"任务 {task_id} 未确认全部产物与账务;保留 ID 后续查询,不重新生成")
time.sleep(interval)
try:
task = fetch_task(task_id)
except requests.RequestException as error:
response = getattr(error, "response", None)
if response is not None and response.status_code < 500 and response.status_code != 429:
raise RuntimeError(f"查询被拒绝 HTTP {response.status_code};保留任务 {task_id} 核对权限") from None
print("查询暂不可用;退避后继续查询原任务")
if __name__ == "__main__":
if "TASK_ID" in os.environ:
# 已有任务优先恢复;这个分支只有查询和下载,不会提交生成。
complete_task(fetch_task(os.environ["TASK_ID"]))
raise SystemExit(0)
job_id = os.environ.get("BUSINESS_JOB_ID", "")
if not 1 <= len(job_id) <= 255 or not all(33 <= ord(char) <= 126 for char in job_id):
raise SystemExit("请设置持久保存的 BUSINESS_JOB_ID:1–255 个可见 ASCII 字符,不含空格;恢复同一次生成必须沿用原值")
task = submit("videos/generations", {
"model": "doubao-seedance-2-0-mini-260615",
"prompt": "镜头缓慢推近白色桌面上的蓝色陶瓷杯,柔和自然光",
"duration": 4,
"resolution": "480p",
"aspect_ratio": "16:9",
"generate_audio": False,
"metadata": {"job_id": job_id},
"wait": 0,
}, idempotency_key=job_id)
print("任务 ID:", task["id"])
complete_task(task)
```
换成图片或 3D 时,只需改 `submit` 的第一个参数(`images/generations` 或 `3d/generations`)和请求体。图片可以把 `wait` 设为 50,通常一次请求就能直接拿到结果。
## 文本模型
文本模型使用 OpenAI、Anthropic、Gemini 兼容协议,不走媒体任务流程,模型 ID 用另一个接口查询。`GET /v1/models` 返回这把密钥可用的全部模型,也包括图片、视频和 3D 模型;只看文本模型时,保留 `native_endpoint_types` 不为 `null` 的条目(见[模型列表:只列出文本模型](models.md#models-text-filter)):
```bash
curl -s "$GATEWAY_ORIGIN/v1/models" \
-H "Authorization: Bearer $KEY" \
| jq -r '.data[] | select(.native_endpoint_types != null) | .id'
```
再用其中的 ID 直接调用:
```bash
curl "$GATEWAY_ORIGIN/v1/chat/completions" \
-H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-d '{"model": "替换为上一步筛出的文本模型 ID", "messages": [{"role": "user", "content": "你好"}]}'
```
SDK 配置和四种协议的示例见[文本模型调用](llm-api.md),各模型的上下文窗口见[文本模型规格](llm-catalog.md)。
## 文档导航
| 我想… | 看这里 |
| --- | --- |
| 确认地址、密钥和请求头写法 | [地址与鉴权](access.md) |
| 查询可用的图片、视频、3D 和文本模型,看懂返回字段、名称映射和各模型参数 | [模型列表](models.md) |
| 生成或编辑图片(Seedream、Nano Banana / Gemini、GPT Image) | [图片生成](media-images.md) |
| 生成视频(Seedance、可灵、MiniMax H3) | [视频生成](media-videos.md) |
| 生成 3D 模型(混元、Tripo) | [3D 生成](media-3d.md) |
| 理解任务状态、产物下载和扣费 | [任务查询与结算](media-tasks.md) |
| 上传参考图、首帧或参考视频 | [参考文件与素材](media-files.md) |
| 了解等待、幂等和重试的规则 | [媒体请求约定](media-access.md) |
| 查某个错误码或报错信息的含义 | [媒体错误处理](media-errors.md) |
| 调用文本大模型 | [文本模型调用](llm-api.md);模型 ID 见[模型列表:只列出文本模型](models.md#models-text-filter) |
| 快速查所有接口路径 | [接口与状态总览](endpoints.md) |
## 调试与排错
调试时用 `curl -i` 同时看到 HTTP 状态和响应头。媒体接口的错误统一为下面的形状,`error.message` 会直接说明哪里不对:
```json
{
"error": {
"code": "unsupported_parameter",
"message": "Parameter seed is not supported by this model",
"stage": "validation",
"retryable": false
},
"request_id": "req_example"
}
```
| 现象 | 原因 | 处理 |
| --- | --- | --- |
| HTTP 401 `model_not_authorized` | 没带密钥、密钥填错或已失效 | 检查请求头是否为 `Authorization: Bearer <完整密钥>`,`Bearer` 后有一个空格,不带引号或尖括号 |
| HTTP 403 `model_not_authorized` | 密钥没有该模型权限,或受 IP 限制 | 联系管理员授权该模型或放行 IP |
| HTTP 404 `model_not_found` | 模型 ID 拼错、未授权,或不是媒体模型 | 从 `GET /media/v1/models` 的输出复制 ID |
| HTTP 400,报错 Model type does not match the submission path | 例如把视频模型提交到了图片地址 | 按模型 `type` 选择 `/images`、`/videos` 或 `/3d` 提交地址 |
| HTTP 400 `unsupported_parameter` | 该模型不接受这个字段,或这个参考素材角色 | 对照[模型列表](models.md#models-media-fields)中该模型的 `parameters` 删掉该字段 |
| HTTP 400 `invalid_request` | 字段拼错、类型不对(如 `"duration": "5"`)、值超范围、传了 `null` 或字段组合冲突 | 读 `error.message`,对照[常见报错对照](media-errors.md#media-errors-common) |
| HTTP 402 / 403 `insufficient_budget` | 额度或预算不足 | 联系管理员充值或调整额度 |
| HTTP 429 `rate_limited` | 请求太频繁,常见于轮询过快 | 按建议间隔轮询(图片 3 秒、视频 5 秒、3D 5–10 秒),同时跟踪多个任务时再拉长;等几秒后重试 |
| 任务 `status: "reconciling"` | 平台在核对提交结果 | 继续查询同一个任务,**不要**重新提交 |
| 任务 `status: "failed"` | 上游拒绝生成,例如内容审核不通过 | 看 `error.message`,调整提示词或素材后用新的幂等键重新提交 |
| 下载链接返回 403 | 可能是签名过期,也可能是上游文件不可用 | 先查询原任务取得当前链接;重签只更新授权,无法恢复已过期的上游文件,不能据此重新生成 |
网络超时或没收到响应时,生成可能已经开始:拿到过任务 `id` 就继续查询;没拿到 `id` 时用**原来的幂等键和原请求体**重放,平台会返回同一个任务,不会重复生成。完整规则见[媒体请求约定](media-access.md)。
需要平台协助排查时,请提供响应中的 `request_id`(或响应头 `X-Oneapi-Request-Id`)、任务 `id`、HTTP 状态和错误正文。**不要**发送你的密钥。
## 兼容接入
已有厂商 SDK,或需要平台媒体接口尚未声明的厂商参数时,可以使用[厂商原生兼容](official.md)入口。它保留厂商原有的请求与响应约定,按对应模型页确认字段、任务和结果格式。
平台 API 和兼容入口的模型名称、参数和任务查询方式不能混用。新应用请直接使用本页介绍的平台 API。