开发者文档 v1
Clifzo API 文档
提交公开媒体页面链接,读取来源当次可提供的结构化结果。从一次服务端请求开始,为每个逻辑任务保留一个稳定的请求键。
更新于
API 请求地址
https://YOUR_API_HOST使用这里的后端 API 地址,不要把文档站地址当成 API 地址。示例会读取当前部署的公开配置并更新。
当前页面未配置后端地址,请将 YOUR_API_HOST 替换为管理员提供的 API 地址。
接入与鉴权
请联系管理员创建 API 账户并确认充值到账,再登录私人控制台创建一个有名称的 API 密钥。当前版本由管理员开通账户和人工确认充值。
每个 API 请求都携带 Authorization: Bearer YOUR_API_KEY。密钥完整内容只在创建时展示一次,请妥善保存。同一账户的多把密钥共享积分,并可访问该账户的 API 任务。
长期密钥应保存在你的服务端,不要写入浏览器代码、客户端安装包、URL 参数、分析日志或客服截图。API 密钥不具有管理员权限。
第一次请求
提交一个受支持的公开来源 URL。收到 202 表示已接受任务,不代表解析已经完成。
| 字段 | 类型 | 说明 |
|---|---|---|
url | string · 必填 | 受支持平台的完整 HTTPS 来源页面地址;整段分享文案应先提取其中的 URL。 |
action | "resolve" · 必填 | 第三方 API 提供解析任务;服务端下载功能仍仅供管理员使用。 |
Idempotency-Key | 请求头 · 8–128 字节 | 一次逻辑请求的稳定标识。请求超时后,使用相同标识和相同 JSON 内容重试。 |
curl 'https://YOUR_API_HOST/v1/api/jobs' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: YOUR_UNIQUE_REQUEST_ID' \
-d '{"url":"https://www.youtube.com/watch?v=VbDupSuRf_Q","action":"resolve"}'受理响应示例。下文中的 ID、时间、域名和来源格式均为示例值。
{
"job_id": "019a0000-0000-7000-8000-000000000001",
"status": "queued",
"durability_confirmed": null,
"job_token": null,
"status_url": "https://YOUR_API_HOST/v1/api/jobs/019a0000-0000-7000-8000-000000000001",
"expires_at": "2026-10-23T08:00:00Z",
"request_id": "req_example",
"billing": {
"credits": 1,
"state": "enqueued"
}
}接口列表
| 方法 | 接口 | 用途 |
|---|---|---|
| POST | /v1/api/jobs | 提交解析任务 |
| GET | /v1/api/jobs/{job_id} | 查询任务状态和结果 |
| POST | /v1/api/jobs/{job_id}/cancel | 请求取消任务 |
| GET | /v1/api/balance | 查询可用与冻结积分 |
| GET | /v1/api/ledger | 查询账户积分流水 |
所有接口均需要有效的 Bearer 密钥。流水通过 limit(默认 50,最大 200)和 offset 分页;只能查询所属账户的任务。
取消响应 · 200 OK
POST /v1/api/jobs/{job_id}/cancel 不需要 JSON 请求体,返回 job_id、当前 status 和 request_id。已经结束的任务可能保留原有终态;取消不会撤回成功结果或其已结算费用,必要时继续查询一次状态。
{
"job_id": "019a0000-0000-7000-8000-000000000001",
"status": "cancelled",
"request_id": "req_example"
}任务与结果
用返回的 job_id 按固定间隔查询,并设置查询次数上限。结果接口应由你配置的 API 地址拼接,不要将密钥转发到任意 URL。
curl 'https://YOUR_API_HOST/v1/api/jobs/JOB_ID' \
-H 'Authorization: Bearer YOUR_API_KEY'queued- 正在排队,等待执行或解析渠道容量。
running- 正在解析。
succeeded- 任务已完成,读取 result.formats 或 result.collection。
failed / cancelled / expired- 停止查询;如有错误,读取 error.code、error.message 和 request_id。
提交响应用 job_id 标识任务,查询响应中的任务标识字段为 id。
{
"id": "019a0000-0000-7000-8000-000000000001",
"status": "succeeded",
"result": {
"title": "Example public work",
"formats": [
{
"format_id": "video-1",
"delivery_type": "direct",
"track_type": "audio_video",
"source_url": "https://media.example/video.mp4"
}
],
"collection": null
},
"result_expired": false,
"request_id": "req_example"
}- HTTP 请求成功也可能返回已失败的任务,业务侧需要同时检查 HTTP 状态和任务 status。
- 来源媒体地址可能比任务更早到期。请留意 expires_at、content_expires_at 和 result_expired;解析出的地址不是永久文件存储。
- 格式信息来自来源当次返回的内容。请检查 delivery_type、track_type 和 source_url;独立音视频轨道可能需要你自己的授权下载与合并流程,HLS/DASH 地址也不等同于一个 MP4 文件。
主页列表 · 部分字段
主页结果使用 result.collection.items,每项包含 id、title、thumbnail_url 和 webpage_url。列表用于发现作品,并不包含每个作品的完整媒体解析结果。存在 next_page_url 时,它是来源页面地址:以 action: "resolve" 提交一个新的 POST /v1/api/jobs 任务即可请求下一页。next_page_url 缺失或为 null 表示本次结果没有可用的下一页,应停止翻页,不要自行拼接游标。truncated 仅表示结果有边界,不保证已经取回整个主页。
{
"result": {
"collection": {
"items": [
{
"id": "work_example",
"title": "Example public work",
"thumbnail_url": null,
"webpage_url": "https://www.youtube.com/watch?v=VbDupSuRf_Q"
}
],
"truncated": false
}
}
}Node.js 与 Python
示例仅使用语言内置 HTTP 客户端,最多查询 60 次。首次提交前设置 CLIFZO_API_KEY,并持久保存 CLIFZO_REQUEST_ID;恢复同一逻辑任务时复用原 ID,明确创建新任务时才更换。
Node.js
// Node.js 22+; set these variables on your server.
const base = new URL(process.env.CLIFZO_API_BASE || 'https://YOUR_API_HOST').origin;
const key = process.env.CLIFZO_API_KEY;
const requestId = process.env.CLIFZO_REQUEST_ID; // persisted per logical task
if (!key || !requestId) throw new Error('Set API key and stable request ID');
async function call(path, body) {
const response = await fetch(base + path, {
method: body ? 'POST' : 'GET',
headers: { Authorization: 'Bearer ' + key,
...(body ? { 'Content-Type': 'application/json',
'Idempotency-Key': requestId } : {}) },
body: body ? JSON.stringify(body) : undefined,
redirect: 'error', signal: AbortSignal.timeout(15000),
});
const data = await response.json();
if (!response.ok) throw new Error(JSON.stringify(data.error));
return data;
}
const job = await call('/v1/api/jobs', {
url: 'https://www.youtube.com/watch?v=VbDupSuRf_Q', action: 'resolve',
});
for (let attempt = 0; attempt < 60; attempt++) {
await new Promise(resolve => setTimeout(resolve, 3000));
const task = await call('/v1/api/jobs/' + encodeURIComponent(job.job_id));
if (task.status === 'succeeded') { console.log(task.result); break; }
if (['failed', 'cancelled', 'expired'].includes(task.status)) {
throw new Error(JSON.stringify({ job_id: task.id,
error: task.error, request_id: task.request_id }));
}
if (attempt === 59) throw new Error('Polling budget exhausted; resume this job later');
}Python
# Python 3; environment variables are kept on your server.
import json, os, time
from urllib.request import Request, build_opener, HTTPRedirectHandler
base = os.environ.get('CLIFZO_API_BASE', 'https://YOUR_API_HOST').rstrip('/')
key = os.environ['CLIFZO_API_KEY']
request_id = os.environ['CLIFZO_REQUEST_ID'] # persisted per logical task
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
client = build_opener(NoRedirect)
def call(path, body=None):
headers = {'Authorization': 'Bearer ' + key}
data = None
if body is not None:
headers.update({'Content-Type': 'application/json',
'Idempotency-Key': request_id})
data = json.dumps(body).encode()
with client.open(Request(base + path, data=data, headers=headers),
timeout=15) as response:
return json.load(response)
job = call('/v1/api/jobs', {'url': 'https://www.youtube.com/watch?v=VbDupSuRf_Q', 'action': 'resolve'})
for attempt in range(60):
time.sleep(3)
task = call('/v1/api/jobs/' + job['job_id'])
if task['status'] == 'succeeded':
print(json.dumps(task['result'], ensure_ascii=False))
break
if task['status'] in ('failed', 'cancelled', 'expired'):
raise RuntimeError({'job_id': job['job_id'], 'error': task.get('error'),
'request_id': task.get('request_id')})
else:
raise TimeoutError('Polling budget exhausted; resume this job later')积分与分页
| 操作 | 积分 |
|---|---|
| 成功解析一个作品 | 1 积分 |
| 成功解析一页主页作品列表 | 2 积分 |
| 查询余额、流水、状态和已有结果 | 0 积分 |
| 失败、取消、到期或没有可用内容 | 0 积分 |
提交前从可用积分中预留本次费用,成功后结算,失败时释放。内部渠道回退与执行恢复不会额外收费;积分足够也仍受请求频率和并发任务限制。
主页每页是独立任务。确实需要下一页时,再用新的请求键提交 result.collection.next_page_url,每个成功的后续页为 2 积分,不会自动遍历整个主页。
主页列表不会再按其中的作品逐条扣费。另行提交某个作品进行详细解析,才按新任务扣 1 积分;最后一页返回空列表不会收费。
提交超时后复用原请求键与原内容重试。任务可能已被受理,不要仅因没有收到响应就重复创建另一个收费任务。
余额响应 · 200 OK
GET /v1/api/balance 返回 account_id、available_credits、frozen_credits 和 pricing。available_credits 可用于新任务,frozen_credits 是等待结算的预留积分;pricing.single 和 pricing.profile 是每次任务的积分价格,不是货币金额。
{
"account_id": "account_example",
"available_credits": 99,
"frozen_credits": 0,
"pricing": {
"single": 1,
"profile": 2
}
}流水响应 · 200 OK
GET /v1/api/ledger?limit=50&offset=0 返回 items、limit 和 offset,不提供总条数。按本次返回的条数增加 offset;条数小于 limit 时停止。created_at_ms 是毫秒级 Unix 时间戳,每条流水的 available_credits 和 frozen_credits 是该次变动后的钱包余额。
kind 包括 purchase(套餐到账)、recharge(充值)、gift(赠送)、refund(退款)、adjustment(调整)、freeze(预留)、consume(消费)和 release(释放)。amount 入账为正、消费为负,调整可正可负;freeze 与 release 的 amount 为 0,因为它们只在可用与冻结余额间转移,不改变积分总额。需要关联任务或订单时读取 job_id 或 order_id。
{
"items": [
{
"id": "ledger_example",
"account_id": "account_example",
"kind": "consume",
"amount": -1,
"available_credits": 99,
"frozen_credits": 0,
"reason": "Parse succeeded",
"actor": "system",
"job_id": "019a0000-0000-7000-8000-000000000001",
"order_id": null,
"created_at_ms": 1792656000000
}
],
"limit": 50,
"offset": 0
}错误与重试
结合 HTTP 状态与 error.code 判断错误。request_id 可帮助管理员排查,无需提供你的密钥。下面列出常见情况,具体参数校验和来源任务仍可能返回其他错误。
| HTTP | error.code | 处理建议 |
|---|---|---|
| 400 | IDEMPOTENCY_KEY_REQUIRED | 提供长度为 8–128 字节、可在重试中复用的 Idempotency-Key 请求头。 |
| 401 | API_KEY_REQUIRED / API_KEY_INVALID | 提供有效的账户密钥;已撤销、到期或所属账户被停用的密钥不可调用。 |
| 402 | INSUFFICIENT_CREDITS | 可用积分不足以预留本次任务费用,请联系管理员充值。 |
| 403 | API_RESOLVE_ONLY | 请使用 action: "resolve",第三方密钥不能请求服务端下载。 |
| 404 | JOB_NOT_FOUND | 任务不存在、尚不可查询、已过期,或不属于当前账户。 |
| 409 | IDEMPOTENCY_CONFLICT | 同一幂等键已用于其他请求内容;网络重试必须保留原始请求体。 |
| 410 | API_JOB_EXPIRED / JOB_EXPIRED | 原任务已过期或未被受理;确实需要重新解析时,使用新的请求键。 |
| 422 | INVALID_URL / UNSUPPORTED_URL | 检查公开来源链接以及平台、链接类型是否受支持。 |
| 429 | RATE_LIMITED / ACCOUNT_RATE_LIMIT / ACCOUNT_CONCURRENCY_LIMIT | 等待额度恢复;有 Retry-After 时按其等待。查询接口也计入请求频率。 |
| 503 | QUEUE_FULL / SERVICE_UNAVAILABLE | 队列或依赖暂不可用。提交结果不确定时,重试应继续使用原请求键。 |
接入检查
- 先使用一个你有权处理的来源链接,验证实际结果,再逐步增加调用量。
- 保存自己的请求编号、job_id 和原始公开页面链接;不要用短期媒体地址作为永久内容标识。
- 限制查询次数,采用退避策略,遵守 Retry-After,并在任务终止时停止查询。幂等键避免重复任务,不会免除请求频率限制。
- 仅保存产品需要的字段,避免将密钥和来源访问凭据写入面向客户的日志。
Clifzo 解析受支持、可合法访问的公开内容。来源删除、登录、地域、年龄、付费、DRM 等访问条件都可能影响结果。API 不承诺支持所有平台、永久托管媒体文件或特定服务等级。
了解接入场景