# 快速开始 > 复制本页命令,几分钟内跑通:查询可用模型、生成一张图片、提交并轮询视频或 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。