准备 API Key
- 登录 bestai.codes 控制台,在「API 密钥」里新建一个 Key,分组选择 grok。只有 grok 分组的 Key 能调用视频接口;用其它分组的 Key 会返回 404「Videos API is not supported for this platform」。
- 每个请求都带上请求头
Authorization: Bearer 你的Key。 - 接口地址用
https://api.bestai.codes/v1,https://bestai.codes/v1也可以,两者等价。
下文示例把 Key 放在环境变量 BESTAI_KEY 里,请勿把 Key 写进代码仓库。
调用流程
POST /v1/videos/generations
立即返回 request_id,不会等待视频生成完。
GET /v1/videos/{request_id}
每 5 秒查一次,直到状态变为 done 或 failed。查询不收费。
GET video.url
完成后返回 MP4 直链,下载不需要 Key。
线上实测:一个 5~10 秒、720p 的视频通常 30~60 秒生成完,15 秒的视频约 1.5 分钟。
1创建任务
请求体只接受 JSON(Content-Type: application/json)。支持三种模式:文生视频(只给 prompt)、图生视频(给一张首帧图 image)、参考图视频(给若干张参考图 reference_images)。
请求参数
| 参数 | 类型 | 说明 |
|---|---|---|
| model必填 | string | 固定为 grok-imagine-video-1.5。 |
| prompt | string | 画面描述,中英文都可以。文生视频和参考图视频必填;只传 image 的图生视频可以省略。 |
| duration | integer | 视频时长,1~15 秒,默认 8。也接受 "8" 这样的整数字符串。 |
| aspect_ratio | string | 默认 16:9。可选:16:99:161:14:33:43:22:3绿色为线上已成功生成过的比例。 |
| resolution | string | 默认 720p。可选:480p720p1080p参考图视频最高 720p。1080p 参数可用,但线上还没有成功样本,建议优先用 720p。 |
| image | object | 图生视频的首帧图:{"url": "…"}。url 可以是公网 https 图片地址,也可以是 data:image/jpeg;base64,…。 |
| reference_images | array | 参考图视频的参考图列表:[{"url": "…"}, …],建议不超过 7 张。必须同时提供 prompt;不能和 image 同时使用。 |
last_frame、output、storage_options,返回 400「视频生成 JSON 请求无效: json: unknown field …」。生成接口也不收 video(返回 400「视频生成不支持 video 输入」);要以一段视频为输入,请用下面的视频编辑接口。文生视频
curl https://api.bestai.codes/v1/videos/generations \
-H "Authorization: Bearer $BESTAI_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "grok-imagine-video-1.5",
"prompt": "一只橘猫缓慢转头看向镜头,固定镜头,午后窗边的自然光",
"duration": 8,
"aspect_ratio": "16:9",
"resolution": "720p"
}'图生视频(首帧图)
{
"model": "grok-imagine-video-1.5",
"prompt": "镜头缓慢推近,人物抬头微笑,背景保持不动",
"image": { "url": "https://example.com/first-frame.jpg" },
"duration": 5,
"aspect_ratio": "9:16",
"resolution": "720p"
}参考图视频
{
"model": "grok-imagine-video-1.5",
"prompt": "参考图中的角色在清晨的池塘边散步,镜头平稳跟随",
"reference_images": [
{ "url": "https://example.com/character-front.png" },
{ "url": "https://example.com/character-side.png" }
],
"duration": 8,
"resolution": "720p"
}返回
{ "request_id": "video_cD_859ohP3_-airCdVSx5wYg" }视频编辑(修改已有视频)
按提示词修改一段已有视频:保留原片的动作、镜头和时长,只改画面内容,例如换衣服、加物件、换风格。它不是「拿视频当动作参考、生成一段新视频」——成片就是原视频改过之后的样子。
提交后同样返回 request_id,之后的查询状态和下载与视频生成完全相同。
请求参数
| 参数 | 类型 | 说明 |
|---|---|---|
| model必填 | string | 固定为 grok-imagine-video(注意不是 1.5)。传 grok-imagine-video-1.5 会返回 400。 |
| prompt必填 | string | 要怎么改,中英文都可以,例如「给猫戴一顶红色毛线帽,其它保持不变」。 |
| video必填 | object | 原视频:{"url": "…"}。url 必须是公网 https 的 MP4 直链,打开就能直接下载到视频文件。网盘或中转站的分享页面(例如 gofile 的页面链接)不是直链,不能用。 |
duration、aspect_ratio、resolution、image、reference_images:成片的时长、分辨率和画幅都跟原视频一致。示例
curl https://api.bestai.codes/v1/videos/edits \
-H "Authorization: Bearer $BESTAI_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "grok-imagine-video",
"prompt": "给猫戴一顶红色毛线帽,其它保持不变",
"video": { "url": "https://example.com/source.mp4" }
}'- 线上实测:1 秒、480×480 的原视频,约 20 秒编辑完成,成片同为 1 秒、480×480。
grok-imagine-video只用于编辑;拿它调用/v1/videos/generations会返回 400,生成请用grok-imagine-video-1.5。- 视频延长(
/v1/videos/extensions)暂未开放,调用返回 404。
2查询任务状态
用同一个 Key 查询。建议每 5 秒查一次。进度按 5% 左右的步长更新,刚开始可能会停在 1% 一段时间,这是正常的。
生成中,progress 为 0~99。
{
"status": "pending",
"model": "grok-imagine-video-1.5",
"progress": 45
}已完成,video.url 是成片地址。
{
"status": "done",
"model": "grok-imagine-video-1.5",
"progress": 100,
"video": {
"url": "https://bestai.codes/grok2api/v1/media/videos/vid_…",
"duration": 15,
"respect_moderation": true
}
}失败。失败的任务不会自动重试,需要重新提交。
{
"status": "failed",
"error": {
"code": "service_unavailable",
"message": "上游服务暂不可用"
}
}respect_moderation 表示成片经过了上游的内容审核,固定为 true,可以忽略。
3下载视频
状态为 done 时,直接下载 video.url:
curl -L -o output.mp4 "https://bestai.codes/grok2api/v1/media/videos/vid_…"
- 地址是 MP4 直链,不需要带 Key,浏览器和播放器可以直接打开,支持断点续传(Range 请求)。
- 单个视频通常几 MB(线上平均约 6 MB)。
- 请在拿到地址后尽快下载并自行保存,不要把这个地址当作长期存储。
GET /v1/videos/{request_id}/content在 bestai 上不可用(返回 404),请一律使用video.url。
请求体生成器
选好参数,复制生成的命令即可提交任务。命令里的 Key 使用环境变量 $BESTAI_KEY,页面不会接触你的 Key。
curl https://api.bestai.codes/v1/videos/REQUEST_ID \
-H "Authorization: Bearer $BESTAI_KEY"完整示例
提交、轮询、下载一条龙。遇到 503(并发已满)会自动等待后重试提交。
import os
import time
import requests
API = "https://api.bestai.codes/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['BESTAI_KEY']}"}
def create_video(payload, retries=5):
for attempt in range(retries):
r = requests.post(f"{API}/videos/generations", json=payload, headers=HEADERS, timeout=60)
if r.status_code == 503: # 并发已满,稍后重试
time.sleep(10 * (attempt + 1))
continue
r.raise_for_status()
return r.json()["request_id"]
raise RuntimeError("提交失败:服务繁忙,请稍后再试")
def wait_video(request_id, interval=5, timeout=900):
deadline = time.time() + timeout
while time.time() < deadline:
r = requests.get(f"{API}/videos/{request_id}", headers=HEADERS, timeout=30)
r.raise_for_status()
job = r.json()
if job["status"] == "done":
return job["video"]["url"]
if job["status"] == "failed":
err = job.get("error", {})
raise RuntimeError(f"生成失败 {err.get('code')}: {err.get('message')}")
print(f"生成中 {job.get('progress', 0)}%")
time.sleep(interval)
raise TimeoutError(f"等待超时:{request_id}")
def download(url, path):
with requests.get(url, stream=True, timeout=300) as r:
r.raise_for_status()
with open(path, "wb") as f:
for chunk in r.iter_content(1 << 20):
f.write(chunk)
request_id = create_video({
"model": "grok-imagine-video-1.5",
"prompt": "一只橘猫缓慢转头看向镜头,固定镜头,午后窗边的自然光",
"duration": 8,
"aspect_ratio": "16:9",
"resolution": "720p",
})
print("任务已提交:", request_id)
url = wait_video(request_id)
download(url, "output.mp4")
print("已保存 output.mp4")import { writeFile } from "node:fs/promises";
const API = "https://api.bestai.codes/v1";
const headers = {
Authorization: `Bearer ${process.env.BESTAI_KEY}`,
"Content-Type": "application/json",
};
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
async function createVideo(payload, retries = 5) {
for (let attempt = 0; attempt < retries; attempt++) {
const res = await fetch(`${API}/videos/generations`, {
method: "POST", headers, body: JSON.stringify(payload),
});
if (res.status === 503) { await sleep(10_000 * (attempt + 1)); continue; }
if (!res.ok) throw new Error(`提交失败 ${res.status}: ${await res.text()}`);
return (await res.json()).request_id;
}
throw new Error("提交失败:服务繁忙,请稍后再试");
}
async function waitVideo(requestId, intervalMs = 5000, timeoutMs = 900_000) {
const deadline = Date.now() + timeoutMs;
while (Date.now() < deadline) {
const res = await fetch(`${API}/videos/${requestId}`, { headers });
if (!res.ok) throw new Error(`查询失败 ${res.status}: ${await res.text()}`);
const job = await res.json();
if (job.status === "done") return job.video.url;
if (job.status === "failed") throw new Error(`生成失败 ${job.error?.code}: ${job.error?.message}`);
console.log(`生成中 ${job.progress ?? 0}%`);
await sleep(intervalMs);
}
throw new Error(`等待超时:${requestId}`);
}
const requestId = await createVideo({
model: "grok-imagine-video-1.5",
prompt: "一只橘猫缓慢转头看向镜头,固定镜头,午后窗边的自然光",
duration: 8,
aspect_ratio: "16:9",
resolution: "720p",
});
console.log("任务已提交:", requestId);
const url = await waitVideo(requestId);
const video = await fetch(url);
await writeFile("output.mp4", Buffer.from(await video.arrayBuffer()));
console.log("已保存 output.mp4");#!/usr/bin/env bash
set -euo pipefail
API=https://api.bestai.codes/v1
REQUEST_ID=$(curl -sf "$API/videos/generations" \
-H "Authorization: Bearer $BESTAI_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"grok-imagine-video-1.5","prompt":"一只橘猫缓慢转头看向镜头,固定镜头","duration":8,"aspect_ratio":"16:9","resolution":"720p"}' \
| jq -r .request_id)
echo "任务已提交:$REQUEST_ID"
while true; do
JOB=$(curl -sf "$API/videos/$REQUEST_ID" -H "Authorization: Bearer $BESTAI_KEY")
STATUS=$(echo "$JOB" | jq -r .status)
case "$STATUS" in
done) URL=$(echo "$JOB" | jq -r .video.url); break ;;
failed) echo "生成失败:$(echo "$JOB" | jq -c .error)"; exit 1 ;;
*) echo "生成中 $(echo "$JOB" | jq -r .progress)%"; sleep 5 ;;
esac
done
curl -L -o output.mp4 "$URL"
echo "已保存 output.mp4"错误与排查
提交或查询时的 HTTP 错误
| 状态码 | 典型信息 | 原因与处理 |
|---|---|---|
| 400 | duration 必须在 1 到 15 秒之间 / aspect_ratio 必须是 … / json: unknown field … | 参数不合法。按「请求参数」一节检查取值,删掉未列出的字段。 |
| 400 | 文本生视频必须提供 prompt / 参考图视频 resolution 最高 720p | 模式与参数组合不对。文生视频要有 prompt;参考图视频要有 prompt 且分辨率不超过 720p。 |
| 401 | API key is required … / Invalid API key | 没带 Key 或 Key 无效。检查 Authorization: Bearer 请求头。 |
| 403 | 余额或额度不足 | 到控制台充值或调整 Key 的额度。 |
| 404 | Videos API is not supported for this platform | Key 不在 grok 分组。新建一个 grok 分组的 Key。 |
| 404 | This endpoint is not supported by grok2api | 调用了不支持的地址,例如 /v1/videos/{id}/content。下载请用 video.url。 |
| 415 | 视频生成仅支持 application/json | 请求体必须是 JSON,不支持 multipart 上传。图片请传 URL 或 base64。 |
| 503 | upstream_saturated:上游账号当前均达到并发上限 | 站点并发已满。等 10 秒左右重试,重试间隔逐次加长。 |
任务状态为 failed 时的错误码
| error.code | 含义 | 处理 |
|---|---|---|
| service_unavailable | 上游服务暂不可用 | 稍后重新提交。持续出现请联系站点管理员。 |
| invalid_argument | 模型或参数不被上游接受 | 检查 model 是否为 grok-imagine-video-1.5,以及参数组合。 |
| internal_error | 其它上游错误,具体原因见 error.message | 常见原因见下。 |
internal_error 的常见 message:
- Failed to read request body: length limit:首帧图或参考图太大,超出了上游的请求大小限制。压缩图片后重试,见下方「使用建议」。
- invalid-argument:图片格式或内容不被接受,换一张标准的 JPEG 或 PNG 再试。
- 上游返回 502:上游临时故障,过一会儿重新提交即可。
使用建议
- 图片尽量用公网 https 地址。用 base64 时,先把图片压缩为 JPEG 或 WebP,长边不超过 2048 像素,体积控制在 2 MB 以内。线上失败的任务里,最常见的原因就是图片过大。
- 先用 480p、短时长调提示词。效果满意后再换 720p 和目标时长,出片更快。
- 批量生成要排队提交。整个站点共享有限的生成并发,一次性提交太多任务会收到 503。
- 轮询间隔 5 秒即可,不要更频繁。客户端等待上限建议设为 15 分钟。
- 及时保存成片。拿到
video.url后尽快下载到自己的存储。 - 费用以控制台的用量记录为准;查询任务状态不收费。
- Python 用户注意:站点前有 Cloudflare 防护,Python 标准库
urllib的默认 User-Agent 会被拦截(403)。用requests,或在urllib里自行设置 User-Agent。