agent-download 是一个异步视频下载服务:提交视频 URL(v1 支持 YouTube,douyin/微信视频号/小红书已预留接口),服务在云端容器内完成下载,视频存入 R2,完成后 48 小时自动删除。
Base URL:https://agent-download.kukukala.com
认证
所有 /api/* 请求需要鉴权,两种凭证任一通过即可:
| 凭证 | 方式 | 适用 |
|---|---|---|
x-auth-token 头 | x-auth-token: <API_TOKEN> | 程序调用一律用这个,长期有效。Token 在控制台「API Token」按钮查看。 |
| UI session cookie | POST /ui/login(表单 user/pass)签发,12 小时有效 | 仅供浏览器控制台使用。 |
缺失/错误返回 401 {"error":"UNAUTHORIZED"}。登录连续失败 5 次锁 15 分钟。无鉴权路径:/doc、/login.html、POST /ui/login、/file/*(用独立文件 token,见下)。
端点总览
| 方法 | 路径 | 用途 |
|---|---|---|
| POST | /api/download | 提交下载(单个或批量,异步建任务) |
| GET | /api/job/:id | 查询任务状态/进度/下载地址 |
| GET | /api/status | 容量/频道池/实例/最近任务总览 |
| GET | /api/settings | 读取运行时设置 |
| PUT | /api/settings | 修改运行时设置(仅对新任务生效) |
| GET | /api/token | 查看 API Token(仅 cookie 会话) |
| GET | /file/:jobId?token=… | 下载成品视频(支持 Range) |
POST /api/download — 提交下载
立即返回任务列表(异步执行);用 /api/job/:id 轮询结果。
参数
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
url | string | 与 urls 二选一 | 单个视频 URL |
urls | string[] | 与 url 二选一 | 批量 URL,上限 20 个,服务并行调度 |
resolution | string | 否 | 360/480/720/1080/2160/max 或纯数字,默认 1080。请求高度拿不到时下载最接近的可用高度 |
请求示例
curl -X POST https://agent-download.kukukala.com/api/download \
-H "x-auth-token: $TOKEN" -H "Content-Type: application/json" \
-d '{"url":"https://www.youtube.com/watch?v=dQw4w9WgXcQ","resolution":"1080p"}'
响应示例
{ "jobs": [ { "jobId": "j_a1b2c3d4", "status": "queued", "platform": "youtube" } ] }
批量且含无效 URL 时逐个处理,无效项原样带回:
{ "jobs": [
{ "jobId": "j_a1b2c3d4", "status": "queued", "platform": "youtube" },
{ "url": "https://v.douyin.com/xxx", "rejected": true, "errorCode": "platform_not_supported" }
] }
jobId,不重复建任务。只接受单视频 URL,播放列表/频道/直播地址返回 unsupported_url。成品上限 2GB(服务端可调)。GET /api/job/:id — 查询任务
curl -H "x-auth-token: $TOKEN" https://agent-download.kukukala.com/api/job/j_a1b2c3d4
{
"jobId": "j_a1b2c3d4", "platform": "youtube", "status": "done", "progress": 100,
"title": "…", "sizeBytes": 104857600, "durationS": 245.3,
"error": null,
"downloadUrl": "/file/j_a1b2c3d4?token=…",
"expiresAt": 1759286400000, "attempt": 1
}
状态机:queued → dispatched → downloading → uploading → done | failed,另有 expired(48h 到期)。progress 为 0–1 浮点。error 形如 {"code":"youtube_blocked","message":"…","retryable":true}(见错误码表)。downloadUrl 仅 done 时返回,随查询实时签发、48h 内有效。
GET /api/status — 服务状态
curl -H "x-auth-token: $TOKEN" https://agent-download.kukukala.com/api/status
{
"capacity": { "maxInstances": 1, "jobsPerInstance": 2, "globalInflightCap": 2,
"inflight": 2, "queued": 3,
"note": "预算值(软约束):实际并发由派发闸控制,实例布局由 FC 决定" },
"stats": { "youtube": { "attempts24h": 12, "successes24h": 11, "lastAttemptAt": …, "lastSuccessAt": … } },
"supportedPlatforms": ["youtube"],
"channels": { "youtube": { "target": 5, "current": 5, "inflight": 2, "queued": 3, "recentFailRate": 0.1 } },
"instances": [ { "instanceId": "i_x", "startedAt": 1759200000000, "lastHeartbeatAt": 1759200090000, "activeJobs": ["j_a1b2c3d4"] } ],
"jobs": [ /* 最近 50 条,元素同 /api/job 响应 */ ]
}
实例 90 秒内心跳视为存活;current 是频道并发池的当前上限(服务按失败自动降半、成功回升,介于 1 与 target 之间)。stats 为 24h 尝试/成功(rollup 历史 + 存活任务双源合并)与全时段最后成功时点。
GET / PUT /api/settings — 运行时设置
curl -X PUT https://agent-download.kukukala.com/api/settings \
-H "x-auth-token: $TOKEN" -H "Content-Type: application/json" \
-d '{"maxInstances": 2, "jobsPerInstance": 2, "channelLimits": {"youtube": {"target": 3}}}'
| 字段 | 默认 | 说明 |
|---|---|---|
maxInstances | 1 | 实例预算(1–5) |
jobsPerInstance | 2 | 每实例并发预算(1–4);全局并发上限 = 两者乘积 |
channelLimits.平台.target | youtube: 5 | 频道池并发上限(1–10),先试高并发,失败自动降 |
GET /file/:jobId?token=… — 下载成品
downloadUrl 自带签名 token,无需再带 x-auth-token。支持 HEAD、Range 断点(206/416)、Content-Length,可直接喂给播放器。
# 整段下载
curl -OJ "https://agent-download.kukukala.com/file/j_a1b2c3d4?token=…"
# 断点续传(Range)
curl -H "Range: bytes=1048576-" -o part.mp4 "https://agent-download.kukukala.com/file/j_a1b2c3d4?token=…"
生命周期
48 小时计时从任务完成时开始(不是提交时)。到期后:410 Gone;URL token 失效同样 410;任务不存在 404。已开始的下载不会被主动中断,但过期后无法发起新请求。
渠道健康与 AI 自愈(spec §7.2–§7.4)
GET /api/health — 渠道健康总览
curl -H "x-auth-token: $TOKEN" $BASE/api/health
{ "platforms": { "youtube": {
"healthy": "unknown", // healthy|unhealthy(连败中)|unknown(未探测)
"lastProbeAt": 1759200000000, "lastResult": { "status": "failed", "errorCode": "youtube_blocked", "at": … },
"failStreak": 3, // channel 类连败(探针失败累加、成功清零)
"infraFails": 0, "unknownCodes": 0,
"sampleStale": false, // true=样本失效(token 过期/视频删除),连败与修复触发冻结
"urls": ["https://www.youtube.com/watch?v=…"], // 样本 URL
"expected": { "titleKeywords": […], "descriptionKeywords": […], "durationSRange": [60,300],
"coverSha256": "…", "ordShas": […], "imageCount": 3 } // sha 基准由首探成功自动写回(B5),只读
} } }
POST /api/health/:platform — 手动探针
用该渠道当前样本 URL 建一个 kind=probe 任务并立即补派。同渠道同时至多 1 个活跃探针(409 probe_active);未配样本 409 no_sample_url。每周 cron 自动全渠道巡检一轮。
PUT /api/health/samples — 样本与预期基准编辑
curl -X PUT $BASE/api/health/samples -H "x-auth-token: $TOKEN" -H "Content-Type: application/json" \
-d '{"platform":"youtube","urls":["https://www.youtube.com/watch?v=…"],
"expected":{"titleKeywords":["关键词"],"descriptionKeywords":["正文词"],"durationSRange":[60,300]}}'
bad_sample_url);coverSha256/imageCount/ordShas 为自动基准不接受手填(400 expected_readonly)。换样本 URL 会清空既有 sha 基准并复位 sampleStale——新样本首次探针成功后自动重记。GET /api/repairs · POST /api/repair/:platform · POST /api/repair/:id/rollback · GET /api/repair/:id/log
| 端点 | 说明 |
|---|---|
GET /api/repairs | 最近 50 条修复(id/状态/trigger/retry_of 链/基线→候选 sha/错误) |
POST /api/repair/:platform | 手动派发修复(限速:每渠道 1 次/小时、3 次/天,429 repair_rate_limited) |
POST /api/repair/r_xxx/rollback | 回滚到该修复的 base bundle(仅当前指针仍是其候选时;否则 409 rollback_stale) |
GET /api/repair/r_xxx/log | codex 执行日志(已脱敏:token/GLM key/签名 URL 打码) |
修复生命周期:pending → running(排他领取)→ published|failed|expired|conflicted。自动触发条件:探针 channel 类连败 ≥3 且样本未失效(R1)。conflicted(发布时基线已被人移走)不立即重派——下次触发时自动以 retry_of 挂链新派一次。
POST /internal/repair/:id/:action — FC 侧回调(内部)
claim(排他领取,REPAIR_TOKEN)/ candidate(候选 zip,内容寻址封存)/ verify(验证结果与 CAS 发布,VERIFY_TOKEN 独立凭据,repair token 调它 401)/ patch(diff 留痕)/ failed / log。仅限 FC 容器回调,不走 API token。
探针错误分类(N9,决定连败/触发)
| 类 | 错误码 | 效果 |
|---|---|---|
| channel | douyin_parse_failed douyin_risk_control xhs_risk_control xhs_parse_failed youtube_blocked po_token_missing permanent | 连败 +1;连败 ≥3 且样本未失效 → 自动派修复 |
| sample | youtube_video_unavailable xhs_token_expired | 不计连败,置 sampleStale(冻结触发),UI 提示换样本 |
| infra | proxy_unreachable timeout internal dispatch_failed probe_failed | 只计 infra 计数;未知新码保守按 infra 并告警 |
典型调用时序(提交 + 轮询 + 下载)
TOKEN="your-api-token"; BASE="https://agent-download.kukukala.com"
JOB=$(curl -s -X POST $BASE/api/download -H "x-auth-token: $TOKEN" \
-H "Content-Type: application/json" \
-d '{"url":"https://www.youtube.com/watch?v=…","resolution":"1080p"}' \
| python3 -c 'import json,sys;print(json.load(sys.stdin)["jobs"][0]["jobId"])')
until [ "$(curl -s -H "x-auth-token: $TOKEN" $BASE/api/job/$JOB \
| python3 -c 'import json,sys;print(json.load(sys.stdin)["status"])')" = "done" ]; do
sleep 5
done
curl -s -H "x-auth-token: $TOKEN" $BASE/api/job/$JOB \
| python3 -c 'import json,sys;print(json.load(sys.stdin)["downloadUrl"])'
# 拿到 /file/... 相对 URL,拼上 $BASE 直接下载
urls 数组一次提交,服务按频道池并发调度(池满时排队),逐个用返回的 jobId 轮询即可。错误码
| code | retryable | 含义 |
|---|---|---|
proxy_unreachable | 是 | 下载容器代理不可用(订阅节点故障) |
youtube_blocked | 是 | 源站风控/需要登录(IP 或客户端被拒) |
po_token_missing | 是 | 缺少 PO Token(YouTube 新风控,需升级下载程序) |
too_large | 是 | 超出成品/临时空间上限(默认 2GB) |
timeout | 是 | 下载超时(15 分钟无进度,自动重试一次) |
unsupported_url | 否 | 非单视频 URL(播放列表/频道/直播) |
platform_not_supported | 否 | 平台尚未开放(douyin/视频号/小红书预留中) |
permanent | 否 | 视频不存在/私有/已删除等永久失败 |
internal | 视情况 | 服务内部错误 |
retryable=true 的失败会触发服务端自动重试(attempt+1,换执行目录重下);触发并发自适应降档。重试仍失败则终态 failed,重新提交即可。
平台支持
| 平台 | 状态 | 域名示例 |
|---|---|---|
| YouTube | ✅ 可用 | youtube.com/watch、youtu.be/… |
| 抖音 | ⏳ 预留 | douyin.com、v.douyin.com |
| 微信视频号 | ⏳ 预留 | — |
| 小红书 | ⏳ 预留 | xiaohongshu.com |
预留平台返回 platform_not_supported,接口形状(提交/轮询/下载)与 YouTube 完全一致——后续开放只是服务端解锁,调用方无需改动。