摘要:很多人第一次用 Sora2 视频生成 API,会遇到一个很典型的问题:请求已经成功了,也拿到了任务 ID,但后面怎么都拿不到视频。原因通常不是模型没生成,也不是 4sAPI 没返回,而是把三个不同接口混成了一个:创建视频、获取视频任务状态、获取视频内容。Sora2 官方异步格式不是“发一个请求立刻返回 MP4”,而是先创建任务,再轮询状态,最后用专门的 content 接口取视频。本文基于 4sAPI 文档,讲清楚正确调用链路和常见坑。
关键词:Sora2、大模型API中转站、4sAPI、视频生成API、Sora API、异步任务、获取视频状态、获取视频内容、OpenAI兼容接口
适合读者:正在用 4sAPI 调 Sora2 视频模型的开发者、Dify/n8n/Coze 工作流用户、AI 视频工具开发者、自动化脚本作者,以及遇到“API 请求成功但拿不到视频”的用户。
资料来源:本文参考 4sAPI Apifox 文档中的 Sora 官方异步格式接口,包括 创建视频、获取视频任务状态 和 获取视频内容。文档显示创建接口为 POST https://4sapi.com/v1/videos,状态接口为 GET https://4sapi.com/v1/videos/{id},内容接口为 GET https://4sapi.com/v1/videos/{id}/content。
1. 开篇:为什么请求成功了,却拿不到视频
Sora2 视频 API 最常见的误区是:
我已经 POST 创建视频了,为什么响应里没有 MP4?
我已经 GET 查询状态了,为什么还是没有视频链接?
任务看起来成功了,视频到底去哪了?
这个问题的根因很简单:
Sora2 视频生成是异步任务,不是同步下载接口。
创建视频接口只负责创建任务。
查询状态接口只负责告诉你任务进度。
获取内容接口才负责拿视频。
如果你只做了前两步,就会出现“API 调通了,但拿不到视频”的感觉。
正确链路应该是:
POST /v1/videos
-> 拿到视频任务 id
-> GET /v1/videos/{id}
-> 轮询任务状态
-> 等任务完成
-> GET /v1/videos/{id}/content
-> 获取视频内容
很多人卡住,是因为少了最后一步。
2. 三个接口不是一回事
先把三个接口放在一张表里:
| 步骤 | 接口 | 方法 | 作用 | 会不会直接给视频 |
|---|---|---|---|---|
| 1 | /v1/videos |
POST | 创建视频任务 | 不一定 |
| 2 | /v1/videos/{id} |
GET | 查询任务状态和进度 | 不会 |
| 3 | /v1/videos/{id}/content |
GET | 获取视频内容 | 会 |
4sAPI 文档里,创建视频接口地址是:
POST https://4sapi.com/v1/videos
状态查询接口地址是:
GET https://4sapi.com/v1/videos/{id}
获取视频内容接口地址是:
GET https://4sapi.com/v1/videos/{id}/content
这三个接口的路径很像,但语义完全不同。
把状态接口当成视频下载接口,是最常见的错误。
3. 第一步:创建视频任务
创建视频使用:
POST https://4sapi.com/v1/videos
请求头:
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
请求体示例:
{
"prompt": "一只金色机器人在雨夜的赛博朋克街道上行走,电影感,慢镜头",
"model": "sora_video2",
"input_reference": "https://example.com/reference.png",
"size": "1920x1080",
"seconds": "8",
"n": 1,
"watermark": false,
"private": false,
"storyboard": false
}
cURL 示例:
curl --location 'https://4sapi.com/v1/videos' \
--header 'Authorization: Bearer YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--data '{
"prompt": "一只金色机器人在雨夜的赛博朋克街道上行走,电影感,慢镜头",
"model": "sora_video2",
"input_reference": "https://example.com/reference.png",
"size": "1920x1080",
"seconds": "8",
"n": 1,
"watermark": false,
"private": false,
"storyboard": false
}'
这里要注意两个点。
第一,文档提示没有特别说明时,不要用 form-data 格式,否则有些接口可能不兼容。创建视频建议按文档使用 application/json。
第二,创建接口的返回重点是任务信息,尤其是任务 id。你后面所有查询和下载,都要靠这个 id。
假设你拿到的任务 ID 是:
sora-2:task_01k770gx3je7dtxwmmph7efpcw
后面就用它查状态和取内容。
4. 第二步:查询视频任务状态
查询任务状态使用:
GET https://4sapi.com/v1/videos/{id}
示例:
curl --location --request GET 'https://4sapi.com/v1/videos/sora-2:task_01k770gx3je7dtxwmmph7efpcw' \
--header 'Authorization: Bearer YOUR_API_KEY' \
--header 'Content-Type: application/json'
这一步只回答一个问题:
任务现在怎么样了?
它不是下载视频。
4sAPI 的状态接口文档里也特别提示:使用这个接口后,并没有返回视频链接,可以使用下面的视频内容接口进行视频下载。
所以如果你在状态查询里没看到 MP4 URL,不要慌。
这不是异常,而是接口职责本来就不是取视频。
5. 第三步:获取视频内容
当状态接口显示任务完成后,再调用:
GET https://4sapi.com/v1/videos/{id}/content
示例:
curl --location 'https://4sapi.com/v1/videos/sora-2:task_01k770gx3je7dtxwmmph7efpcw/content' \
--header 'Authorization: Bearer YOUR_API_KEY'
这一步才是很多人漏掉的关键。
如果你已经确认任务成功,但拿不到视频,优先检查有没有调用这个接口:
/v1/videos/{id}/content
不是:
/v1/videos/{id}
多出来的 /content,就是状态查询和视频获取的分界线。
6. 推荐完整调用流程
实际开发时,不建议手动一步步点接口,而是写一个轮询流程。
伪代码如下:
createVideo()
-> taskId
while true:
status = getVideoStatus(taskId)
if status is completed:
video = getVideoContent(taskId)
save video
break
if status is failed:
throw error
sleep 5-10 seconds
JavaScript 示例:
const API_KEY = process.env.FOURSAPI_KEY;
const BASE_URL = "https://4sapi.com/v1";
async function createVideo() {
const res = await fetch(`${BASE_URL}/videos`, {
method: "POST",
headers: {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
prompt: "一只金色机器人在雨夜的赛博朋克街道上行走,电影感,慢镜头",
model: "sora_video2",
size: "1920x1080",
seconds: "8",
n: 1,
watermark: false,
private: false,
storyboard: false,
}),
});
if (!res.ok) {
throw new Error(`create failed: ${res.status} ${await res.text()}`);
}
return await res.json();
}
async function getVideoStatus(id) {
const res = await fetch(`${BASE_URL}/videos/${encodeURIComponent(id)}`, {
headers: {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
});
if (!res.ok) {
throw new Error(`status failed: ${res.status} ${await res.text()}`);
}
return await res.json();
}
async function getVideoContent(id) {
const res = await fetch(`${BASE_URL}/videos/${encodeURIComponent(id)}/content`, {
headers: {
Authorization: `Bearer ${API_KEY}`,
},
});
if (!res.ok) {
throw new Error(`content failed: ${res.status} ${await res.text()}`);
}
return await res.arrayBuffer();
}
实际返回字段以 4sAPI 当前接口为准。上面重点不是字段名,而是调用顺序:
create -> status -> content
7. 最常见的5个坑
7.1 只调用创建接口,等它直接返回视频
错误理解:
POST /v1/videos 应该直接返回视频链接。
正确理解:
POST /v1/videos 是创建异步任务。
视频生成需要时间,不能按同步接口理解。
7.2 把状态接口当下载接口
错误调用:
GET /v1/videos/{id}
然后一直找视频 URL。
正确调用:
GET /v1/videos/{id}/content
状态接口只看进度,内容接口才取视频。
7.3 没等任务完成就取内容
如果任务还在生成中,就去调用 /content,可能拿不到内容,或者返回未就绪。
建议先轮询状态,只有在状态完成后再取内容。
7.4 id没有正确编码
Sora2 的任务 ID 里可能包含冒号,例如:
sora-2:task_01k770gx3je7dtxwmmph7efpcw
在某些 SDK 或框架里,路径参数如果没有正确处理,可能会导致路由异常。
前端或 Node.js 里可以用:
encodeURIComponent(id)
不过也要注意:如果服务端要求原始路径形式,编码策略以实际测试为准。关键是不要把 id 截断、拆错或漏掉 sora-2: 前缀。
7.5 请求格式用错
创建视频文档中提示,没有特别说明不要用 form-data。建议创建任务时使用:
Content-Type: application/json
不要随手把参数塞成 multipart form。
8. 排障清单:照着查一遍
如果你现在卡在“拿不到视频”,按这个顺序检查:
1. 创建视频是否调用 POST /v1/videos?
2. 创建请求是否使用 application/json?
3. model 是否写成 sora_video2?
4. 是否拿到了任务 id?
5. 是否用 GET /v1/videos/{id} 查询状态?
6. 状态是否已经完成?
7. 是否调用 GET /v1/videos/{id}/content?
8. Authorization 是否每一步都带了 Bearer Key?
9. id 是否完整,是否被截断或编码错误?
10. 是否把状态接口误当成视频下载接口?
多数问题查到第 7 步就能定位。
9. 4sAPI使用建议:视频生成单独建Key
视频生成和文本模型不一样。
它通常更慢、更贵,也更适合异步任务队列。
建议在 4sAPI 后台给 Sora2 单独建一个 Key,例如:
sora2-video-prod
sora2-video-test
sora2-video-workflow
这样有几个好处:
- 视频生成成本单独统计;
- 测试环境和生产环境隔离;
- 某个工作流失控时,可以单独停掉;
- 更容易看日志定位是哪一步失败;
- 可以单独设置额度,避免视频任务把文本任务预算打穿。
如果你在 Dify、n8n、Coze 或自研系统里接 Sora2,最好把创建任务、轮询状态、获取内容拆成三个节点。
不要把它们糊成一个“调用 Sora2”节点。
因为异步视频任务天然就需要状态管理。
10. 标准工作流模板
在工作流工具里,可以这样设计:
节点1:创建视频任务
输入:prompt、size、seconds、reference
输出:task_id
节点2:等待
等待:5-10 秒
节点3:查询任务状态
输入:task_id
输出:status
节点4:条件判断
如果生成中 -> 回到节点2
如果失败 -> 输出错误信息
如果完成 -> 进入节点5
节点5:获取视频内容
输入:task_id
输出:视频文件或下载结果
这套流程比“请求一次就等结果”稳定得多。
如果你的平台支持重试,建议给状态查询加重试,但不要无限重试。可以设置最大等待时间,比如 5 分钟、10 分钟或按业务需求调整。
11. 总结:不是没生成,是你少调了一个接口
Sora2 视频 API 拿不到视频,最常见原因不是模型失败,而是调用链路不完整。
记住这三句话:
POST /v1/videos:创建视频任务
GET /v1/videos/{id}:查询任务状态
GET /v1/videos/{id}/content:获取视频内容
状态接口不会直接返回视频。
真正取视频,要走 /content。
如果你把这三个接口拆清楚,Sora2 的 4sAPI 接入就会顺很多。