开发者文档 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 表示已接受任务,不代表解析已经完成。

字段类型说明
urlstring · 必填受支持平台的完整 HTTPS 来源页面地址;整段分享文案应先提取其中的 URL。
action"resolve" · 必填第三方 API 提供解析任务;服务端下载功能仍仅供管理员使用。
Idempotency-Key请求头 · 8–128 字节一次逻辑请求的稳定标识。请求超时后,使用相同标识和相同 JSON 内容重试。
提交请求 · cURL
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、时间、域名和来源格式均为示例值。

202 Accepted
{
  "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。已经结束的任务可能保留原有终态;取消不会撤回成功结果或其已结算费用,必要时继续查询一次状态。

取消响应 · 200 OK
{
  "job_id": "019a0000-0000-7000-8000-000000000001",
  "status": "cancelled",
  "request_id": "req_example"
}

任务与结果

用返回的 job_id 按固定间隔查询,并设置查询次数上限。结果接口应由你配置的 API 地址拼接,不要将密钥转发到任意 URL。

查询任务 · cURL
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+ · server-side
// 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 · server-side
# 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 是每次任务的积分价格,不是货币金额。

余额响应 · 200 OK
{
  "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。

流水响应 · 200 OK
{
  "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 可帮助管理员排查,无需提供你的密钥。下面列出常见情况,具体参数校验和来源任务仍可能返回其他错误。

HTTPerror.code处理建议
400IDEMPOTENCY_KEY_REQUIRED提供长度为 8–128 字节、可在重试中复用的 Idempotency-Key 请求头。
401API_KEY_REQUIRED / API_KEY_INVALID提供有效的账户密钥;已撤销、到期或所属账户被停用的密钥不可调用。
402INSUFFICIENT_CREDITS可用积分不足以预留本次任务费用,请联系管理员充值。
403API_RESOLVE_ONLY请使用 action: "resolve",第三方密钥不能请求服务端下载。
404JOB_NOT_FOUND任务不存在、尚不可查询、已过期,或不属于当前账户。
409IDEMPOTENCY_CONFLICT同一幂等键已用于其他请求内容;网络重试必须保留原始请求体。
410API_JOB_EXPIRED / JOB_EXPIRED原任务已过期或未被受理;确实需要重新解析时,使用新的请求键。
422INVALID_URL / UNSUPPORTED_URL检查公开来源链接以及平台、链接类型是否受支持。
429RATE_LIMITED / ACCOUNT_RATE_LIMIT / ACCOUNT_CONCURRENCY_LIMIT等待额度恢复;有 Retry-After 时按其等待。查询接口也计入请求频率。
503QUEUE_FULL / SERVICE_UNAVAILABLE队列或依赖暂不可用。提交结果不确定时,重试应继续使用原请求键。

接入检查

  1. 先使用一个你有权处理的来源链接,验证实际结果,再逐步增加调用量。
  2. 保存自己的请求编号、job_id 和原始公开页面链接;不要用短期媒体地址作为永久内容标识。
  3. 限制查询次数,采用退避策略,遵守 Retry-After,并在任务终止时停止查询。幂等键避免重复任务,不会免除请求频率限制。
  4. 仅保存产品需要的字段,避免将密钥和来源访问凭据写入面向客户的日志。

Clifzo 解析受支持、可合法访问的公开内容。来源删除、登录、地域、年龄、付费、DRM 等访问条件都可能影响结果。API 不承诺支持所有平台、永久托管媒体文件或特定服务等级。

了解接入场景

匿名访问统计设置

本站自建统计仅统计页面访问次数和估算独立浏览器:随机标识在本机保留 30 天,服务端摘要与小时汇总最多 32 天,不保存浏览网址或原始 IP,也不关联任务内容。公开内容页另使用 Cloudflare Web Analytics 统计页面访问与性能,下载工作台不加载它。对此服务的设置更改在下次页面加载时生效;已加载的脚本可能继续完成当前页的统计。