摘要:很多人第一次用 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 接入就会顺很多。