← Clavis Sinica Docs · OpenAPI · llms.txt · Get a key · English

Clavis Sinica API 接入指南

面向:准备接入 ClavisSinica.org 九项中文语义能力的第三方开发者 版本:2026-09-10 基址https://clavissinica.org


0 先读这一段

接一次,拿九个能力。 九项服务共用同一个端点、同一套认证、同一种请求形态、同一套错误码和计费规则。你需要写的分支只有一个:同步返回结果,还是提交后轮询

POST /v1/chinese/run          ← 唯一的业务端点
Authorization: Bearer ck-live_...
{"service_code": "<九选一>", "payload": {...}}

不要在你的代码里硬编码字段规格。 字段名、类型、长度上限、枚举取值、必填与否,全部在 GET /v1/chinese/services 里,零费用、任意有效 Key 可读。它由服务端的同一份定义生成——引擎校验用的就是它。启动时拉一次,比抄一份到本地可靠。

这不是客套话。产品站曾经维护过一份本地手抄的规格表,累计出过五处错误,每一处的根因都一样:规格只存在于服务端的代码里,而本地那份没人会提醒你更新。接上这个接口后他们删掉了本地三张表。


1 拿 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 只在确认响应里出现这一次,服务端只存哈希,取不回来

2 认证

Authorization: Bearer ck-live_xxxxxxxx

只有这一种方式。 没有 X-API-Key 头,没有查询参数。用错了会返回 401 missing_or_invalid_authorization


3 请求形态:一律用 payload

{
  "service_code": "decompose.idiom",
  "payload": {"idiom": "守株待兔"},
  "idempotency_key": "your-id-001"      // 可选
}

payload 对九个服务全部有效。 键名就是 /v1/chinese/servicesfields[].name 的值。

另有一种快捷写法 {"input": "..."},只能设置单字段服务的主字段。它存在的唯一理由是历史兼容——如果你在新接入,直接忽略它,统一走 payload 就不会遇到「这个服务能不能用 input」这类判断。

顶层只接受四个键service_codeinputpayloadidempotency_key。其余的会被静默丢弃,但响应里会带一个 warnings 数组告诉你丢了什么:

"warnings": ["请求顶层的这些字段不被支持, 已忽略: explain_lang。..."]

建议把 warnings 打进日志。 有个接入方在顶层发了几个月的 explain_lang,它一直被丢弃,调用一切正常、返回一切正常,没有任何信号——这个 warnings 就是为那件事加的。


4 两种执行模式

/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},直到 statusdoneerror


5 provenance:分清查表还是生成

每个响应都带:

"provenance": {"type": "lookup", "generated_fields": []}

如果你要把结果落库当作权威内容,看这个字段。 lookup 类可以直接引用;generative 类建议标注来源或人工过一遍。


6 限流:四层,超限不收费

阈值 触发时
每 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 限额会让他们互相挤占。真正约束你的是按账户那三层。


7 错误处理

错误码在 error,人类可读信息在 msg。参数类错误额外带 fieldexpected

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_errorupstream_unavailable 跟你的请求无关,退避后重试是合理的。

扣了钱没拿到结果的,一律自动退款,响应里会带 note 说明。

一条真实教训

早期版本有个兜底错误码叫 engine_rejected_input,凡是没带明确状态的错误都归到它名下——包括上游超时这种引擎根本没被调用的情况。有个接入方因此把自己脚本的一个粘贴错误,误判成了「服务端字段名和文档不一致」的结构性问题。

现在的错误码按真实成因分类,不会把服务端的故障说成你的输入有问题。


8 计费与幂等

幂等的作用域是账户,不是 Key。 你在重试期间轮换了 Key,幂等仍然有效。


9 九项能力速览

价格与调用形态以 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 轮询 文言课文七层教案

几条反直觉的事实,早知道省时间


10 沙盒与免费试用


11 一份最小可用的客户端

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 同样受支持——客户端没有做任何你自己做不到的事。


12 参考