# 鉴权与通用约定

服务地址、Bearer Token、请求格式与安全约定。

- 请求发送到统一 **Go 程序**中的 ATHub 内容模块；账户功能使用 `8001` 端口的 `/api/v1/*`，内容功能使用 `8003` 端口的 `/api/v2/*`。
- 对外服务地址：`https://api.albatextus.com`。本文全部调用示例使用此 HTTPS 地址。
- 自托管时，内容接口默认监听 `http://127.0.0.1:8003`，由服务端 `ATHUB_HTTP_ADDR` 决定。
- API 域名由外部反向代理直接转发到 ATHub 后端 `8003`。账户前端不提供或转发 `/api/v2/*`；OAuth 与账户 `/api/v1/*` 仍可通过前端同源入口访问。
- 所有内容路径统一带 `/api/v2/` 前缀，不接受末尾 `/`，例如使用 `/api/v2/upload`，没有 `/athub` 中间路径。旧 `/api/upload`、`/upload` 和末尾带 `/` 的路径不再兼容。
- 生产环境使用 HTTPS。接口面向服务端或采集程序，不提供浏览器跨域 CORS 支持。

### 请求头

| 键 | 必填 | 值 |
| --- | --- | --- |
| `Authorization` | 所有请求必填 | `Bearer <API_KEY>`，唯一有效凭据为登录用户在账户接口创建的 ATHub 个人 API Key。 |
| `Content-Type` | 有 JSON 请求体的 POST/PUT 必填 | `application/json`，也接受 `application/json; charset=utf-8`。 |
| `Accept` | 可选 | 推荐 `application/json`。 |

个人 Key 用于标识调用用户，目前拥有共享内容库的读写权限，不按用户隔离小说。
不要放进网页、客户端公开包或日志。原全局服务密钥、旧版个人密钥、OAuth access token、
Cookie 和用户身份请求头均不能替代新 Key。Key 默认永久有效，停用、重置为旧 Key 或用户停用后，调用返回 `401`。

账户功能在 `8001` 提供以下接口，要求账户登录会话；写操作还需
`X-AlbaTextus-Client: account-center`，不接受跨站请求。通过前端同源入口访问时可使用相同路径。

| 方法 | 账户接口 | 作用 |
| --- | --- | --- |
| POST | `/api/v1/account/key` | 提交 `{}` 自动取得唯一 Key；可选 `name`，不接受 `expires_at`。首次生成返回一次性明文，重复请求只返回已有 Key 的元数据。 |
| GET | `/api/v1/account/key` | `keys` 数组只包含当前 Key 的前缀、停用状态、最后调用时间和调用次数，不返回明文或哈希。 |
| POST | `/api/v1/account/key/reset` | 提交 `{}`，停用旧 Key 并生成新的永久 Key，返回一次性明文，保留历史调用记录。 |
| DELETE | `/api/v1/account/key/{id}` | 停用自己的 Key，保留历史调用记录。 |
| GET | `/api/v1/account/athub/usage` | 自己的总次数、成功/失败/待完成次数，按方法、接口、端口和状态码分组统计；可选 `days=1`、`7` 或 `30` 获取指定范围及按端口聚合的趋势。 |
| GET | `/api/v1/account/athub/calls?limit=50&offset=0` | 自己的调用明细，`limit` 为 1–100，`offset` 为 0–1000000。 |

首次生成或重置返回 `201`，结构为 `{"key":{...},"api_key":"ath_..."}`；明文只返回一次。
已有 Key 时，重复自动申请返回 `200 {"key":{...}}`，不会再次返回明文。
每个用户只有一个当前 Key，默认永久有效。停用后刷新页面不会恢复启用；通过主动重置生成新 Key。
前端 `/account/key` 在首次访问时自动申请 Key，支持复制一次性明文、重置和停用。
重置后的旧 Key 保留为历史记录，不再接受内容调用，已有统计继续保留。
管理员可在用户详情中禁用 Key，限制属于账户级别，用户无法通过重置或自动申请绕过。
被限制时，用户重置接口返回 `403 athub_admin_disabled`，内容调用返回 `401 invalid_api_key`。
封禁账户会同步禁用 Key；解封账户不会自动恢复 Key，需管理员解除 Key 限制后由用户重置。
已在执行中的请求可以完成，后续调用会校验最新状态。

管理员使用 `GET /api/v1/admin/users/{id}/athub-key` 查看状态，使用
`PUT /api/v1/admin/users/{id}/athub-key` 提交 `{"disabled":true,"updated_at":"..."}` 禁用，
`disabled:false` 解除限制。接口遵守现有管理员角色层级、登录会话与来源检查，禁止普通用户调用。
管理员只能读取元数据，不能获取用户 Key 的明文或哈希。

鉴权表和调用表放在账户数据库；内容数据库保存平台、作者、标签库和小说内容。
内容请求先写入待完成记录，再执行业务并补全状态码、耗时、响应字节数。
记录包括正常请求和可归属用户的失败请求；未知 Key 的失败请求以匿名记录保存。
健康检查和账户管理请求不计入内容调用次数。不记录密钥、请求头、查询字符串或小说正文；
未知接口以 `/api/v2/{unknown}` 归类，防止记录敏感路径和无限扩张的统计项。
账户数据库不可用、无法校验 Key 或无法开始调用记录时，内容接口返回 `503`，不执行业务。
程序中断或记录完成失败时保留 `status=null` 的待完成记录，不把它计作成功。

除鉴权外，没有额外签名字段。GET/DELETE 参数放在 URL 查询字符串；POST/PUT 数据放在 JSON 请求体。

### 编码、类型和限制

| 项目 | 规则 |
| --- | --- |
| 请求体 | 一个 UTF-8 JSON 对象，无 BOM。拒绝顶层数组、`null`、多段 JSON 和未知字段。 |
| 文件上传 | 将 `.json` 文件内容作为整个 HTTP 请求体发送，不使用 `multipart/form-data`。 |
| 大小上限 | 每个请求体最多 **8 MiB = 8,388,608 字节**，包含 JSON 结构、转义字符等。 |
| 单批上传 | `/api/v2/upload` 最多 200 个卷、1000 个章节，还须满足 8 MiB 限制。超过时分批发送。 |
| ID、排序 | JSON number，必须是 `1` 至 `2147483647` 的整数；不能用数字字符串或小数。可空 ID 另见字段表。 |
| 时间 | 服务端生成，响应为 RFC 3339 时间字符串；示例使用 UTC。请求不能提交 `id`、`createdAt`、`updatedAt`。 |
| 文本 | `title` 和平台 `name` 去除首尾空白后不能为空；所有存储文本都禁止 NUL 字符 `\u0000`。 |
| 正文 | 纯文本，保留换行、首尾空白和缩进，但不能全部为空白。无 HTML 渲染、TXT/EPUB 解析或编码转换。 |
| 图片 | `coverUrl` 只存字符串，不接收图片二进制，也不会抓取远程封面。 |
| 查询参数 | 拒绝未知键和同名参数重复出现，例如 `id=1&id=2`。 |
| 请求时限 | 每次 API 处理的数据库上下文期限为 15 秒；上传是同步操作，不返回后台任务 ID。 |

成功响应没有统一的 `data` 外壳：单条返回对象，列表直接返回数组。
所有响应带 `Cache-Control: no-store`；有 JSON 的响应使用 `application/json; charset=utf-8`。
`204` 响应没有响应体，也没有响应 JSON。

### 通用分页参数

分页列表接口采用下列字段，响应不含 `total`、`nextCursor` 或页码；`/api/v2/toc` 为全量目录对象，不使用分页：

| 查询键 | 类型 | 必填 | 默认 | 说明 |
| --- | --- | --- | --- | --- |
| `limit` | integer | 否 | `20` | `1` 至 `100`，每次最多返回的条数。 |
| `offset` | integer | 否 | `0` | `0` 至 `2147483647`，跳过的条数。 |

空列表返回 `[]`。按 `offset += limit` 继续读取；返回不足 `limit` 条即到达末页。
分页没有跨请求快照保证，上传和读取同时进行时可能看到数据变化。
