证书

GET/api/certs

返回你名下的证书列表。

字段说明
id证书 ID,用于站点的 cert_id
name证书名
domain覆盖的域名
typelets 表示系统自动签发并自动续期

上传证书(自动化续期)

POST/api/certs/upload

把你自己签的证书推上来。按域名幂等:同一个域名已经有证书时原地替换内容(证书 ID 不变), 所以已经绑好这张证书的站点不需要任何改动就会立刻用上新证书。跑一百次和跑一次结果相同 —— 这正是 certbot / acme.sh 的 deploy-hook 需要的语义。

参数必填说明
domain证书对应的域名,必须是你名下的站点域名
cert_pem证书 PEM。建议传 fullchain(叶子证书 + 中间证书)
key_pem私钥 PEM。不能带口令加密
name证书名,默认用域名

入口会先做这几项校验,任何一项不过都返回 400不写入任何东西:

校验不过时的含义
PEM 能解析、取得到有效期文件损坏或根本不是证书
证书未过期传上去只会让浏览器报红
证书的 SAN 覆盖 domain传错文件了(最常见)。只看证书里真实的 SAN,不看你声明的域名
证书与私钥配对证书和私钥不是一对
curl -X POST https://cdn.treeidc.cn/api/certs/upload \
     -H "Authorization: Bearer $HSCDN_KEY" \
     -H "Content-Type: application/json" \
     -d "{\"domain\":\"www.example.com\",
          \"cert_pem\":\"$(awk '{printf "%s\\n", $0}' fullchain.pem)\",
          \"key_pem\":\"$(awk '{printf "%s\\n", $0}' privkey.pem)\"}"

返回:

{
  "ok": true,
  "id": "12",
  "created": false,          // false = 替换了已有的那张(站点绑定不变)
  "domain": "www.example.com",
  "not_after": "2026-12-08T09:00:00.000Z",
  "key_match": "ok",         // "unverified" = 非 RSA 证书,本进程验不了配对(不是错误)
  "bound_sites": []          // 新建时会把还没绑证书、且域名被覆盖的站点自动绑上
}

接进 certbot / acme.sh

用 deploy-hook,每次续期成功后自动推上来:

# /etc/letsencrypt/renewal-hooks/deploy/hscdn.sh
#!/bin/bash
set -e
D="$RENEWED_DOMAINS"
python3 - "$RENEWED_LINEAGE" "$D" <<'PY'
import json, os, sys, urllib.request
lineage, domain = sys.argv[1], sys.argv[2].split()[0]
body = json.dumps({
    "domain": domain,
    "cert_pem": open(f"{lineage}/fullchain.pem").read(),
    "key_pem":  open(f"{lineage}/privkey.pem").read(),
}).encode()
req = urllib.request.Request(
    "https://cdn.treeidc.cn/api/certs/upload", data=body, method="POST",
    headers={"Authorization": "Bearer " + os.environ["HSCDN_KEY"],
             "Content-Type": "application/json"})
print(urllib.request.urlopen(req, timeout=30).read().decode())
PY
为什么不用 POST /api/certs 做续期 —— 那条是「新建一张证书」。拿它做自动续期会每次多出一张同域名的新记录, 而你的站点仍然绑在旧的那张上(自动补绑只管「还没绑证书」的站点)。 于是每一步都返回 200、看起来续期成功了,而边缘会继续递旧证书直到它过期。 自动化请一律用本条 /api/certs/upload

下载证书

GET/api/certs/{id}/download

取回证书的完整材料,用于装到你自己的其它服务器上。

curl -H "Authorization: Bearer $HSCDN_KEY" \
     https://cdn.treeidc.cn/api/certs/7/download
{
  "ok": true,
  "id": 7,
  "domain": "www.example.com",
  "name": "www.example.com",
  "cert_pem": "-----BEGIN CERTIFICATE-----\n…(证书链)…",
  "key_pem":  "-----BEGIN PRIVATE KEY-----\n…(私钥)…"
}
返回含义
200成功。key_pem 可能为 null(只上传了证书链、没有私钥的情形)
404 证书不存在ID 不存在或不属于你 —— 两者返回同一句话
404 还没有签发成功证书还在签发中或签发失败,暂时没有可下载的内容
这个接口会返回私钥明文。每次调用都会在你的操作日志里留下一条 cert_download 记录(见账户信息的操作记录)。请不要把响应写进日志文件或 CI 输出。

控制台的「证书管理」页面每行也有下载按钮:

情况下载到什么
证书有私钥(绝大多数)一个 <域名>.zip,里面是 <域名>.pem<域名>.key
只有证书链、没有私钥直接下 <域名>.pem
为什么打包成 zip 而不是分别下两个文件:浏览器会把同一次点击里的第二个下载当作「多文件下载」拦下来(Chrome 会弹权限提示,不点就静默丢弃),结果只拿到证书、拿不到私钥。打成一个包就只有一次下载动作,不会被拦。
中文域名的文件名会用 punycode 形式(如 中文.comxn--fiq228c.com.zip)—— 直接用中文会被文件系统与浏览器的字符限制压成下划线,不同域名还会撞成同一个文件名。

绑定到站点

curl -X POST https://cdn.treeidc.cn/api/sites/123/update \
  -H "Authorization: Bearer $HSCDN_KEY" \
  -H "Content-Type: application/json" \
  -d '{"cert_id": 7, "https_enabled": true}'
只能绑定属于你自己的证书。引用别人的 cert_id 会返回 403

自动签发与手工上传的区别

自动签发(type: "lets")手工上传
续期系统自动续需要你自己换
到期风险过期会导致 HTTPS 不可用
如果你用的是手工上传的证书,建议把「到期前告警」做进自己的监控:定期拉 /api/certs,对临近到期的证书提前提醒。