首页 / 文档
快速开始
接口都是普通的 HTTP GET,返回 JSON。播放走 HLS,不提供网页版播放器。
概述
| Base URL | https://api.geektb.com/v1 |
|---|---|
| 响应格式 | application/json; charset=utf-8 |
| HTTP 方法 | 仅 GET 与 HEAD,其它方法返回 405 |
| API 版本 | 2026-10-15(路径里的 v1 为大版本) |
| 片库快照 | 2026-11-08,每个响应的 meta.snapshot 里也有 |
| 可用区 | us-west-1(CDN 边缘按就近调度) |
响应主体有两种形状。成功时是数据信封:
{
"data": [],
"meta": {
"total": 0,
"snapshot": "2026-11-08"
}
}
失败时是错误信封,HTTP 状态码同时反映错误类别:
{
"error": {
"type": "invalid_request_error",
"code": "unknown_endpoint",
"message": "No endpoint matches this path."
}
}
第一次请求
视频列表不需要密钥,直接请求即可:
curl -s https://api.geektb.com/v1/videos
返回里每个视频带 hls_master(master playlist)与 poster,
以及档位摘要的入口。要做清晰度列表请单独取单个视频。
认证
接口分两类:
- 元数据(
/v1/videos、/v1/videos/{id}、/v1/categories、/v1/renditions、/v1/playlists、 以及/v1/health、/v1/version、/v1/status、/v1/rate-limit)—— 开放只读,无需密钥,按来源 IP 限流。 - 播放与用量(
/v1/streams、/v1/usage)—— 需要 Bearer Token。
把 token 放在 Authorization 头里,前缀 Bearer:
curl -s "https://api.geektb.com/v1/streams?video_id=v_8f3a1c" \
-H "Authorization: Bearer sk_live_xxxxxxxxxxxxxxxx"
缺少该头返回 401 missing_api_key,token 无效或已吊销返回
401 invalid_api_key,两种情况的响应都带 WWW-Authenticate。
如何拿到 token
目前采用人工开通:发邮件到 api@geektb.com,写明用途、预估日播放量与回调域名,
一般一个工作日内在同一线程回复。开通后 token 形如 sk_live_ 加 32 位随机串,
只在服务端保存哈希,遗失只能重新签发。
不要把 token 写进 App 或网页的前端代码。移动端与网页播放请由你自己的服务端中转签发。
播放链路
典型流程四步,全部是普通 HTTP:
GET /v1/videos/{id}拿元数据与档位,决定首帧用哪一档。GET /v1/streams?video_id={id}签发播放会话,返回带签名的manifest_url与过期时间(默认 1800 秒)。- 把
manifest_url交给播放器(hls.js / AVPlayer / ExoPlayer 均可)。 master playlist 内含六档,播放器按带宽自选。 - 切片为 fMP4、6 秒一段,
Cache-Control: immutable,可被任意中间缓存复用。
签名绑定会话与来源网段,换设备或换网络需要重新签发;重新签发不计入额外配额, 但受同一条每分钟限流约束。
所有切片默认 AES-128 加密,密钥随签名地址下发,不单独暴露。 不使用 DRM(Widevine / FairPlay);若你的内容需要硬件级保护,这一版不适用。
错误处理
所有错误都遵循同一个信封,error.type 用于程序分支,
error.message 面向人阅读:
| error.code | HTTP | 含义 |
|---|---|---|
unknown_endpoint | 404 | 请求路径不匹配任何接口。检查路径拼写与版本前缀。 |
video_not_found | 404 | 视频 ID 不存在或已下架。 |
method_not_allowed | 405 | 接口只接受 GET / HEAD,响应带 Allow 头。 |
missing_api_key | 401 | 播放与用量接口未携带 Authorization 头。 |
invalid_api_key | 401 | 密钥无效、已吊销或不属于该环境。 |
too_many_requests | 429 | 超过当前套餐的每分钟上限,响应带 Retry-After。 |
forbidden_host | 421 | Host 头与本站不匹配,请求被拒绝。 |
限流
计数窗口固定 60 秒,按密钥(无密钥时按来源 IP)分别计量。超限时返回 429,
并带 Retry-After 秒数:
{
"error": {
"type": "rate_limit_error",
"code": "too_many_requests",
"message": "Too many requests. See Retry-After."
}
}
各套餐阈值可以从 /v1/rate-limit 读取,不要在客户端硬编码:
curl -s https://api.geektb.com/v1/rate-limit
缓存与条件请求
元数据是不可变快照(每次片库更新会换 meta.snapshot),因此响应带强校验器:
ETag:内容哈希,内容变化才会变。Cache-Control: public, max-age=300:5 分钟内可直接用本地副本。Last-Modified:快照发布时间。
带上 If-None-Match 可以把未变更的请求压成 304,不消耗响应体:
curl -s https://api.geektb.com/v1/videos \
-H 'If-None-Match: "6af5bd69-2e47"' \
-o /dev/null -w '%{http_code}\n' # 304
版本与弃用
路径里的 v1 是大版本,字段级的演进通过响应里的 meta 与
/v1/version 的 deprecations 数组公告。弃用条目包含
field、announced、remove_after 三个字段,
remove_after 距今至少 180 天。
当前无弃用字段:
{
"api_version": "2026-10-15",
"service_version": "2.3.1",
"base_url": "https://api.geektb.com/v1",
"deprecations": [],
"changelog": "https://api.geektb.com/changelog.html"
}
客户端示例
curl
curl -s https://api.geektb.com/v1/videos | jq '.data | length'
Python
import requests
r = requests.get("https://api.geektb.com/v1/videos", timeout=10)
r.raise_for_status()
for v in r.json()["data"]:
if v["duration_sec"] > 900:
print(v["id"], v["title"], v["duration_sec"])
Node.js(18+,内置 fetch)
const res = await fetch("https://api.geektb.com/v1/videos");
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const { data, meta } = await res.json();
console.log(meta.total, data.slice(0, 3).map(v => v.id));
获取支持
接口报错、视频缺失或需要提额,发邮件到 api@geektb.com,附上请求路径、
时间(UTC)与响应里的 X-Request-Id。每个响应都会带这个头,是我们查日志的唯一线索。
本站是只读接口服务,不提供网页版播放器或在线调试。调试请用 curl 或任意 HTTP 客户端。