Dracalon OpenAPI 开发者文档
Dracalon 开放接口的鉴权、签名、端点与接入指南,面向第三方凭证调用方。
简介
Dracalon OpenAPI 是给第三方合作方用的开放接口。调用时用 AccessKey 凭证加 HMAC-SHA256 签名鉴权,再配合接口授权、限流和调用日志,让对外开放的数据始终可控、可审计。
所有接口都在专用子域 https://openapi.dracalon.com 下,路径形如 /v1/...。它是机器到机器(服务端到服务端)的接口,不走浏览器,别在前端页面里暴露 Secret。
接口分两类。背景图列表完全公开,不用凭证、直接 GET 就能拿到;其余接口要先申请凭证(AccessKey)并获得授权才能调用。
准备:获取凭证
背景图列表是公开接口,不用凭证就能直接调。下面这些步骤只针对其余需要授权的接口。
- 通过 申请入口 提交申请,我们会为你创建一对
AccessKey ID与AccessKey Secret。 Secret仅在创建 / 重置时返回一次,请妥善保存;一旦泄露,立即通过 /contact 申请重置。- 我们会按你的需要给凭证授权可调用的接口;调用没授权的接口会返回
40106。 - 可选:为凭证绑定 IP 白名单、单独的限流阈值、过期时间。
鉴权与签名
背景图列表是公开接口,不需要任何认证头和签名,直接 GET 就行;下面的签名规则只适用于其余接口。
请求头
| 头 | 必填 | 说明 |
|---|---|---|
X-Access-Key |
是 | AccessKey ID |
X-Timestamp |
是 | Unix 时间戳(秒),需在服务器时间 ±300 秒内 |
X-Nonce |
是 | 随机串(≤128 字符),360 秒内不可重复(防重放) |
X-Signature |
是 | 见下,小写 hex |
X-Sign-Method |
否 | 默认 HMAC-SHA256,传别的值会被拒 |
X-Request-Id |
否 | 便于端到端排障;不传则服务端生成 |
待签名串(Canonical String)
7 行,用 \n 连接,顺序固定:
HTTP_METHOD # 大写,如 POST
REQUEST_PATH # 如 /v1/account/profile
CANONICAL_QUERY # 查询串按键名排序后的 RFC3986 编码;无 query 时为空行
BODY_SHA256 # 原始 body 的 sha256(小写 hex);空 body 为空串的 sha256
ACCESS_KEY_ID
TIMESTAMP
NONCE
最终签名:
signature = lowercase_hex( HMAC_SHA256(canonical_string, AccessKey_Secret) )
⚠️
BODY_SHA256必须对实际发送的原始 body 字节计算:先把 JSON 序列化成字符串,对该字符串求 sha256,再把同一个字符串作为 body 发出。不要序列化两次,字段顺序或空格只要不一样就会验签失败。⚠️ 你签名用的
REQUEST_PATH必须与你实际请求的 URL 路径逐字一致,统一为/v1/...。
参考实现
PHP:
// $query:URL 查询参数(无则传 []);$body:JSON body 数组(GET 等无 body 传 null)
function sign(string $method, string $path, array $query, ?array $body, string $ak, string $secret): array {
$ts = (string)time();
$nonce = bin2hex(random_bytes(8));
ksort($query); // 顶层按键名排序(嵌套数组需递归排序)
$canonicalQuery = http_build_query($query, '', '&', PHP_QUERY_RFC3986);
$raw = $body === null ? '' : json_encode($body, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$canonical = implode("\n", [
strtoupper($method), $path, $canonicalQuery, hash('sha256', $raw), $ak, $ts, $nonce,
]);
$sig = hash_hmac('sha256', $canonical, $secret);
// GET 带参数时,URL 用同样规则拼 query:$host . $path . '?' . $canonicalQuery
return [
'headers' => [
'Content-Type' => 'application/json',
'X-Access-Key' => $ak,
'X-Timestamp' => $ts,
'X-Nonce' => $nonce,
'X-Signature' => $sig,
'X-Sign-Method' => 'HMAC-SHA256',
],
'body' => $raw, // POST 发这个原始串;GET 为空串
];
}
Python:
import time, os, json, hashlib, hmac, requests
from urllib.parse import urlencode, quote
# query: dict of query params ({} if none); body: dict for JSON body (None for GET / no body)
def call(method, host, path, query, body, ak, secret):
ts, nonce = str(int(time.time())), os.urandom(8).hex()
# sort by key + RFC3986 (quote_via=quote -> space as %20, symmetric with the server)
canonical_query = urlencode(sorted(query.items()), quote_via=quote)
raw = "" if body is None else json.dumps(body, ensure_ascii=False, separators=(",", ":"))
canonical = "\n".join([
method.upper(), path, canonical_query,
hashlib.sha256(raw.encode()).hexdigest(), ak, ts, nonce,
])
sig = hmac.new(secret.encode(), canonical.encode(), hashlib.sha256).hexdigest()
headers = {
"X-Access-Key": ak, "X-Timestamp": ts, "X-Nonce": nonce,
"X-Signature": sig, "X-Sign-Method": "HMAC-SHA256",
}
if raw:
headers["Content-Type"] = "application/json"
url = f"{host}{path}" + (f"?{canonical_query}" if canonical_query else "")
return requests.request(method, url, data=raw.encode() if raw else None, headers=headers)
开放接口清单
| 接口 | 方法 | 路径 | 鉴权 | 说明 |
|---|---|---|---|---|
| 小说列表 | GET | /v1/novel/list |
任意有效凭证 | 分页查询已上架小说,支持分类/标签/篇幅/平台/关键词筛选(半开放) |
| 小说详情 | GET | /v1/novel/detail |
任意有效凭证 | 按 slug 查询单本已上架小说详情(半开放) |
| 小说分类法 | GET | /v1/novel/taxonomy |
任意有效凭证 | 一次下发分类/标签/facet 分组/平台清单(半开放) |
| 随机小说 | GET | /v1/novel/random |
任意有效凭证 | 按筛选条件随机抽取 1-5 本已上架小说(半开放) |
| 推荐小说 | POST | /v1/novel/recommend |
需授权 | 凭证绑定会员名义提交小说推荐,走后台审核流程 |
| 推荐游戏 | POST | /v1/game/recommend |
需授权 | 凭证绑定会员名义提交游戏推荐,走后台审核流程 |
| 背景图列表 | GET | /v1/background/list |
公开 | 按渠道拉取生效中的背景图列表(凭证授权) |
| 可信域名列表 | GET | /v1/trusted-domain/list |
需授权 | 按用途(scope)拉取对外公开的可信域名白名单(凭证授权) |
| 当前会员资料 | GET / POST | /v1/account/profile |
任意有效凭证 | 读取当前 AccessKey 绑定会员的基础资料 |
| 当前凭证信息 | GET / POST | /v1/account/credential |
任意有效凭证 | 读取当前 AccessKey 的凭证与授权概要 |
| 已授权接口列表 | GET / POST | /v1/system/authorized-apis |
任意有效凭证 | 列出当前凭证已授权的开放接口 |
字段级规格与示例:见 小说库接口 与 游戏库接口;其余接口的字段在后台「开放接口」目录里查看。能不能调用,取决于你的凭证有没有被授权。
通用响应与错误码
统一响应信封:
{
"code": 1, // 1 = 成功;其余为失败
"msg": "ok", // 失败时是可直接提示的原因
"data": { "request_id": "..." } // request_id 与 X-Request-Id 对应,便于排障
}
code === 1视为成功,其余一律按失败处理。- 业务校验失败(缺字段 / 防重 / 超配额等)返回
code = 0,HTTP200。 - 鉴权 / 签名失败返回下列业务码 + 对应 HTTP 状态:
| code | HTTP | 含义 |
|---|---|---|
1 |
200 | 成功 |
40101 |
401 | 缺少认证头 |
40102 |
401 | AccessKey 无效 |
40103 |
401 | 签名校验失败 / 签名算法不支持 |
40104 |
401 | AccessKey 已过期 |
40105 |
403 | IP 不在白名单 |
40106 |
403 | 未授权访问该接口 |
40107 |
401 | 请求已重放(nonce 重复) |
40108 |
401 | 时间戳无效 / 超窗口 |
40109 |
401 | AccessKey 已禁用 |
42901 |
429 | 请求频率过高 |
40404 |
404 | 当前接口未开放(路径 / 方法不匹配) |
50301 |
503 | 服务暂不可用 |
限流与防重放
- 单 IP 默认 300 次/分钟;单凭证 默认 120 次/分钟(可为单个凭证单独配置)。超限返回
42901。 X-Nonce单次有效(360 秒内不可重复);X-Timestamp超出服务器时间 ±300 秒就会被拒签,记得把服务器时间和 NTP 对齐。
接入清单
- 通过 申请入口 申请凭证,确认已授权你需要的接口(背景图列表无需此步)。
- 妥善保存
Secret(只返回一次)。 - 按上文签名规则发起请求,路径统一用
/v1/...。 - 遇到问题,把响应里的
request_id反馈给我们,方便排查。
安全建议
- 不要在前端页面或日志中暴露
AccessKey Secret。 - 全程使用 HTTPS。
- 为高风险凭证配置 IP 白名单,并定期轮换 Secret。
- 为不同合作方申请独立凭证,不要共用。