面向:准备接入 ClavisSinica.org 九项中文语义能力的第三方开发者
版本:2026-09-10
基址:https://clavissinica.org
接一次,拿九个能力。 九项服务共用同一个端点、同一套认证、同一种请求形态、同一套错误码和计费规则。你需要写的分支只有一个:同步返回结果,还是提交后轮询。
POST /v1/chinese/run ← 唯一的业务端点
Authorization: Bearer ck-live_...
{"service_code": "<九选一>", "payload": {...}}
不要在你的代码里硬编码字段规格。 字段名、类型、长度上限、枚举取值、必填与否,全部在 GET /v1/chinese/services 里,零费用、任意有效 Key 可读。它由服务端的同一份定义生成——引擎校验用的就是它。启动时拉一次,比抄一份到本地可靠。
这不是客套话。产品站曾经维护过一份本地手抄的规格表,累计出过五处错误,每一处的根因都一样:规格只存在于服务端的代码里,而本地那份没人会提醒你更新。接上这个接口后他们删掉了本地三张表。
两步,因为一步式发 Key 存在账户接管风险。
# ① 申请 —— 返回 202,不返回 Key
curl -X POST https://clavissinica.org/v1/keys \
-H 'Content-Type: application/json' \
-d '{"email": "you@example.com", "env": "live"}'
# {"ok":true,"status":"verification_sent","expires_in":900}
# ② 点邮件里的链接(15 分钟内有效,只能用一次)
# Key 只在确认响应里出现这一次,服务端只存哈希,取不回来
ck-live_(连字符,不是下划线)。ck-test_ 是沙盒 Key,不扣真实 ckTokpk_live_ 前缀不能用在这里,会返回明确的 wrong_key_prefixAuthorization: Bearer ck-live_xxxxxxxx
只有这一种方式。 没有 X-API-Key 头,没有查询参数。用错了会返回 401 missing_or_invalid_authorization。
payload{
"service_code": "decompose.idiom",
"payload": {"idiom": "守株待兔"},
"idempotency_key": "your-id-001" // 可选
}
payload 对九个服务全部有效。 键名就是 /v1/chinese/services 里 fields[].name 的值。
另有一种快捷写法 {"input": "..."},只能设置单字段服务的主字段。它存在的唯一理由是历史兼容——如果你在新接入,直接忽略它,统一走 payload 就不会遇到「这个服务能不能用 input」这类判断。
顶层只接受四个键:service_code、input、payload、idempotency_key。其余的会被静默丢弃,但响应里会带一个 warnings 数组告诉你丢了什么:
"warnings": ["请求顶层的这些字段不被支持, 已忽略: explain_lang。..."]
建议把 warnings 打进日志。 有个接入方在顶层发了几个月的 explain_lang,它一直被丢弃,调用一切正常、返回一切正常,没有任何信号——这个 warnings 就是为那件事加的。
/v1/chinese/services 里每个服务都有 execution.mode。
直接返回结果,通常 0.1–1.5 秒。
{
"ok": true,
"service_code": "decompose.idiom",
"data": { ... },
"provenance": {"type": "lookup", "generated_fields": []},
"cktok_charged": 10,
"replayed": false,
"test_mode": false
}
decompose.lesson / decompose.wenyan_lesson)这两项要跑 45–90 秒,不适合挂住 HTTP 连接。
// 提交立即返回
{"ok": true, "task_id": "task_xxx", "status": "pending", "poll_url": "/v1/chinese/tasks/task_xxx"}
然后每几秒轮询一次 GET /v1/chinese/tasks/{task_id},直到 status 是 done 或 error。
provenance:分清查表还是生成每个响应都带:
"provenance": {"type": "lookup", "generated_fields": []}
type: "lookup" — 结果来自词典库,确定性的,同样输入永远同样输出type: "generative" — 有 LLM 参与,generated_fields 列出 data 里哪些顶层键是实时生成的如果你要把结果落库当作权威内容,看这个字段。 lookup 类可以直接引用;generative 类建议标注来源或人工过一遍。
| 层 | 阈值 | 触发时 |
|---|---|---|
| 每 IP | 1200 次/分钟 | nginx 直接 429 |
| 每账户 | 120 次/分钟 | scope: account_requests_per_minute |
| 每账户在途异步任务 | 3 | scope: account_inflight_tasks |
| 全局在途异步任务 | 20 | scope: global_task_queue_full |
{"detail": {"error": "rate_limited", "scope": "account_inflight_tasks",
"msg": "...", "limit": 3, "retry_after": 60}}
响应带 Retry-After 头,请遵守它,不要紧循环重试。 被限流的请求不扣 ckTok。
按 IP 那层刻意设得宽松——如果你是服务器端接入,你全部客户的流量会从同一个出口 IP 出来,按 IP 限额会让他们互相挤占。真正约束你的是按账户那三层。
错误码在 error,人类可读信息在 msg。参数类错误额外带 field 和 expected。
| HTTP | error | 含义 | 该不该重试 |
|---|---|---|---|
| 400 | invalid_param |
你的参数有问题,field 说是哪个 |
改了再发 |
| 400 | no_text |
OCR 没识别到文字 | 换张图 |
| 401 | missing_or_invalid_authorization |
认证头缺失或格式错 | 检查 Bearer |
| 401 | wrong_key_prefix |
用了 pk_live_ 等非 ck- 前缀 |
换 Key |
| 401 | invalid_or_revoked_key |
Key 无效或已吊销 | 换 Key |
| 404 | not_found |
接口正常,内容不在词典里 | 不要重试 |
| 413 | payload_too_large |
超过声明的体积上限 | 压缩后再发 |
| 415 | unsupported_media_type |
图片格式不支持 | 转成 JPEG/PNG/WebP |
| 429 | rate_limited |
见第 6 节 | 按 Retry-After |
| 502 | upstream_error |
连不上/超时,引擎根本没被调到 | 可以重试 |
| 502 | upstream_rejected |
上游返回失败且无法分类 | 谨慎重试 |
| 503 | upstream_unavailable |
上游依赖不可用 | 应该重试 |
注意 502/503 和 4xx 的区别:4xx 是你的请求有问题,重试没用;upstream_error 和 upstream_unavailable 跟你的请求无关,退避后重试是合理的。
扣了钱没拿到结果的,一律自动退款,响应里会带 note 说明。
早期版本有个兜底错误码叫 engine_rejected_input,凡是没带明确状态的错误都归到它名下——包括上游超时这种引擎根本没被调用的情况。有个接入方因此把自己脚本的一个粘贴错误,误判成了「服务端字段名和文档不一致」的结构性问题。
现在的错误码按真实成因分类,不会把服务端的故障说成你的输入有问题。
idempotency_key:同一个键重复提交只扣一次,返回第一次的结果,响应里 replayed: trueidempotency_key 配不同的请求体会返回 409,这是真实的误用,不会静默兼容GET /v1/account/balance / ledger / usage幂等的作用域是账户,不是 Key。 你在重试期间轮换了 Key,幂等仍然有效。
价格与调用形态以 GET /v1/chinese/services 为准,下面是选型参考。
| service_code | ckTok | 模式 | 产出 |
|---|---|---|---|
convert.script |
2 | 同步 | 简繁转换,四种方向 |
ocr.image |
6 | 同步 | 图片 → 文字 |
decompose.english_morph |
8 | 同步 | 英文词缀拆解、词根家族 |
decompose.idiom |
10 | 同步 | 成语释义、例句、出处引证、HSK 级、感情色彩 |
decompose.wenyan |
12 | 同步 | 逐字本义、说文引证、六书分类、古今义对比 |
decompose.sentence |
15 | 同步 | 分词、拼音、语法讲解、逐词下钻 |
decompose.passage |
20 | 同步 | 可读性判定、生词率、HSK 分布、优先字词、句式清单 |
decompose.lesson |
100 | 轮询 | 现代文课文七层教案 |
decompose.wenyan_lesson |
100 | 轮询 | 文言课文七层教案 |
decompose.passage 的 target_level 是 HSK 3.0 等级(1–9),不是学校年级。 按年级换算会让难度判定完全错位,而且不会报错decompose.idiom 返回中英双语(meaning_zh / meaning_en)。其余服务要么没有语言维度,要么固定单语decompose.sentence 的语法讲解恒为英文,不可切换decompose.passage 的 priority_words[].gain 是一句排好版的中文(如「本篇出现 3 次」),不是数值。原样展示,排序用 rankdecompose.passage 的句式分析只看前 40 句,grammar_patterns_coverage 会告诉你截断了多少;其余指标是全文的truncated: true 时所有指标都只反映前 3000 字符,不只是句式ocr.image 一次一张,base64 后上限 6MB,不接受 HEIC(iPhone 默认格式,需转 JPEG)。十页课本是十次调用ck-test_ 前缀,不扣真实 ckTok,每服务 50 次/天(decompose.wenyan_lesson 3 次/天)。适合 CI 和联调POST /v1/demo/run,同样的请求体形态,不需要 Key,10 次/天/IP,开放六项(不含两个异步教案服务和 ocr.image)import time, requests
BASE = "https://clavissinica.org"
class Clavis:
def __init__(self, key):
self.h = {"Authorization": f"Bearer {key}",
"Content-Type": "application/json"}
# 启动时拉一次规格,不在本地硬编码
self.spec = requests.get(f"{BASE}/v1/chinese/services",
headers=self.h, timeout=30).json()
self.mode = {s["service_code"]: s["execution"]["mode"]
for s in self.spec["services"]}
def run(self, code, payload, idem=None, _tries=0):
body = {"service_code": code, "payload": payload}
if idem:
body["idempotency_key"] = idem
r = requests.post(f"{BASE}/v1/chinese/run", headers=self.h,
json=body, timeout=180)
if r.status_code == 429 and _tries < 5:
time.sleep(int(r.headers.get("Retry-After", 30)))
return self.run(code, payload, idem, _tries + 1)
if r.status_code in (502, 503) and _tries < 3:
time.sleep(2 ** _tries)
return self.run(code, payload, idem, _tries + 1)
r.raise_for_status()
out = r.json()
for w in out.get("warnings", []):
print("WARN", w) # 别忽略这个
if self.mode.get(code) == "submit_poll":
return self._poll(out["task_id"])
return out
def _poll(self, task_id, timeout=300):
deadline = time.time() + timeout
while time.time() < deadline:
time.sleep(5)
t = requests.get(f"{BASE}/v1/chinese/tasks/{task_id}",
headers=self.h, timeout=30).json()
if t.get("status") in ("done", "error"):
return t
raise TimeoutError(task_id)
也可以用官方客户端,行为与上面这段完全一致:
pip install clavis-sinica
npm install @clavis-sinica-org/sdk
两个 registry 的命名习惯不同(PyPI 没有 scope 概念),是同一套 API。客户端替你做的就是上面这些:启动拉规格、429 按 Retry-After 退避、5xx 指数退避、异步自动轮询、打印 warnings。直接调 HTTP 同样受支持——客户端没有做任何你自己做不到的事。
GET /v1/chinese/services(零费用,唯一权威)pk_live_ 前缀,两边不通用