diff --git a/README.md b/README.md
index 5cf063465..a6c0e94b4 100644
--- a/README.md
+++ b/README.md
@@ -12,7 +12,7 @@
[](https://github.com/zyycn/codex-proxy-rs/pkgs/container/codex-proxy-rs)
[](https://opensource.org/license/mit)
-[快速开始](#快速开始) · [客户端接入](#客户端接入) · [运维](#运维) · [部署文档](deploy/README.md) · [架构](docs/architecture.md)
+[快速开始](#快速开始) · [客户端接入](#客户端接入) · [运维](#运维) · [部署](deploy/README.md) · [接口](docs/api.md) · [架构](docs/architecture.md)
@@ -21,14 +21,15 @@
## 能力
-| 领域 | 实现 |
-| -------- | ------------------------------------------------------------ |
-| 协议 | OpenAI Responses JSON、SSE、WebSocket 与模型目录 |
-| Provider | OpenAI OAuth、xAI OAuth,多账号隔离与自动刷新 |
-| 路由 | Client Key 平台绑定、Provider instance、模型映射与安全 fallback |
-| 延续 | OpenAI native/replay continuation;xAI 客户端完整历史 |
-| 管理 | Client Key、账号、模型目录、设置、观测、系统与 OAuth |
-| 计量 | 模型请求 Token、费用、延迟、账号与 Provider 归因 |
+| 领域 | 实现 |
+| -------- | --------------------------------------------------------------------------- |
+| 协议 | OpenAI Responses JSON、SSE、WebSocket 与模型目录 |
+| Provider | 固定 OpenAI、xAI 两个 Provider;各自管理 OAuth、账号、额度与模型目录 |
+| 路由 | Client Key 绑定 Provider、全局模型映射、会话亲和与同 Provider 安全 fallback |
+| 透明边界 | OpenAI 保持 Responses 语义透明;xAI 的协议适配只驻留在 `providers/xai` |
+| 延续 | OpenAI native/replay continuation;xAI 使用客户端提交的完整历史 |
+| 管理 | Client Key、账号、模型目录、设置、观测、系统更新与 OAuth |
+| 计量 | 模型请求 Token、费用、延迟、账号与 Provider 归因 |
## 快速开始
@@ -47,15 +48,14 @@ sudo chown "$(id -u):10001" deploy/config.yaml
chmod 0640 deploy/config.yaml
```
-分别生成三个密码:
+分别生成 PostgreSQL 与 Redis 密码:
```bash
openssl rand -hex 24
openssl rand -hex 24
-openssl rand -hex 24
```
-把结果写入 `deploy/config.yaml`:
+把两个结果写入 `deploy/config.yaml`,并单独设置管理员初始密码:
| 配置 | 约束 |
| -------------------------- | ---------------------------- |
@@ -64,7 +64,7 @@ openssl rand -hex 24
| `admin.default_password` | 至少 12 位,不能包含 `$` |
PostgreSQL、Redis 容器和应用通过同一份 `config.yaml` 使用这两个基础设施密码,不需要额外
-导出环境变量。
+导出环境变量。管理员密码只在首次创建管理员时生效。
Linux 需要允许容器组写入运行目录:
@@ -88,9 +88,10 @@ curl -i http://127.0.0.1:8080/healthz
### 3. 初始化
1. 使用 `admin@cpr.local` 与初始密码登录。
-2. 创建 Provider instance,并通过 OAuth 或 JSON 导入 OpenAI、xAI 账号。
-3. 按平台配置客户端模型到上游模型的映射;未配置的模型名原样透传。
-4. 创建 `sk_...` 客户端 Key,绑定平台并设置模型范围、速率与并发限制。
+2. 在账号页选择对应 Provider:OpenAI 支持 OAuth、账号文件与 Agent Identity;xAI 支持 OAuth
+ 和兼容账号文件导入。
+3. 按需配置客户端模型到上游模型的全局映射;未命中映射的模型名原样透传。
+4. 创建 `sk_...` Client Key,绑定 `openai` 或 `xai`,并设置模型范围、速率与并发限制。
> [!IMPORTANT]
> xAI 使用 OAuth session,不支持把 xAI API Key 作为上游 credential。
@@ -125,18 +126,22 @@ curl http://127.0.0.1:8080/v1/responses \
API 路由
-| 路由 | 用途 |
-| --------------------------- | ------------------- |
-| `POST /v1/responses` | JSON 或 SSE 透明代理 |
-| `GET /v1/responses` | WebSocket 透明代理 |
-| `POST /v1/responses/review` | OpenAI review 请求 |
-| `GET /v1/models` | 启用的公开模型列表 |
-| `GET /v1/models/{model_id}` | 公开模型详情 |
+| 路由 | 用途 |
+| ---------------------------------- | ------------------------------ |
+| `POST /v1/responses` | JSON 响应或 SSE Responses 流 |
+| `GET /v1/responses` | Responses WebSocket 升级 |
+| `POST /v1/responses/review` | review 子代理请求 |
+| `GET /v1/models` | 当前 Client Key 可用的模型列表 |
+| `GET /v1/models/catalog` | Codex 客户端展示用模型目录 |
+| `GET /v1/models/{model_id}/info` | Codex 客户端展示用模型详情 |
+| `GET /v1/models/{model_id}` | OpenAI 兼容模型详情 |
所有 `/v1/*` 路由都需要客户端 API Key。
+完整的客户端与管理端路由、鉴权和 mutation 规则见 [接口文档](docs/api.md)。
+
## 运维
```bash
@@ -153,14 +158,27 @@ docker compose -f deploy/compose.yaml up -d
```
> [!IMPORTANT]
-> `.runtime/` 保存 PostgreSQL、Redis、自更新状态和日志。删除该目录会永久清除运行状态。
+> `.runtime/` 保存 PostgreSQL、Redis、日志、OpenAI 会话锚点和自更新状态。删除该目录会永久清除
+> 运行状态。
Compose 默认只绑定 `127.0.0.1`。公网接入应使用 HTTPS 反向代理,转发 WebSocket
upgrade 与真实客户端 IP;不要暴露 PostgreSQL 或 Redis。
+## 维护与发布
+
+发布仓库版本必须从干净、已同步上游的发布分支运行:
+
+```bash
+release/publish
+```
+
+脚本会更新 `release/version.yaml`、创建约定提交与带注释的 `v` tag,并原子推送分支和 tag。
+不要手工修改版本后单独打 tag。
+
## 文档
- [部署](deploy/README.md)
+- [客户端与管理端接口](docs/api.md)
- [架构与数据边界](docs/architecture.md)
- [Release](https://github.com/zyycn/codex-proxy-rs/releases)
- [容器镜像](https://github.com/zyycn/codex-proxy-rs/pkgs/container/codex-proxy-rs)
diff --git a/deploy/README.md b/deploy/README.md
index 932d6771f..b54850da0 100644
--- a/deploy/README.md
+++ b/deploy/README.md
@@ -19,21 +19,21 @@ sudo chown "$(id -u):10001" deploy/config.yaml
chmod 0640 deploy/config.yaml
```
-分别执行三次以下命令:
+为 PostgreSQL 与 Redis 分别生成一个密码:
```bash
openssl rand -hex 24
```
-把结果写入 `deploy/config.yaml`:
+把两个 48 位十六进制结果分别写入 `deploy/config.yaml` 的:
- `store.database.password`
- `store.redis.password`
-- `admin.default_password`
-PostgreSQL 与 Redis 密码必须是 48 位十六进制字符。管理员初始化密码至少 12 位、不能是
-常见弱口令且不能包含 `$`。Compose 通过 `config.yaml` 的凭据桥接区引用同一密码,三个密码
-都不需要额外导出为环境变量,也不能嵌入连接 URL。
+另行设置 `admin.default_password`。它至少需要 12 个字符,不能是常见弱口令,也不能包含 `$`。
+
+PostgreSQL 与 Redis 密码必须是 48 位十六进制字符。Compose 通过 `config.yaml` 的凭据桥接区
+引用同一密码;三个值都不需要额外导出为环境变量,数据库和 Redis 密码也不能嵌入连接 URL。
Linux 上应用容器以 `10001:10001` 运行,需要允许该组写入应用数据和日志目录:
@@ -45,6 +45,11 @@ chmod 0770 .runtime/data .runtime/logs
`config.yaml` 通过 Compose `configs` 只读挂载。普通 Compose 对本地文件保留宿主机的
UID/GID 和 mode,因此配置由当前用户持有,并只向容器组 `10001` 开放读取权限。
+模板中的 `openai` / `xai` 只保留请求画像启动基线。OpenAI 的上游地址、WebSocket 池、额度刷新
+与 OAuth 设置,以及 xAI 的 OAuth、额度和模型目录策略,均由各自 Provider 使用代码内默认值管理;
+模板不重复列出这些默认项。运行后,OpenAI CLI/Desktop 与 xAI CLI 的官方发布检查只更新进程内
+版本字段,不回写 `config.yaml`;检查失败时继续使用上一份有效画像。
+
## 启动
```bash
@@ -78,80 +83,27 @@ cargo run -p codex-proxy-rs
后端会从当前目录向上查找 `deploy/config.yaml`。相对数据和日志目录以该文件所在目录解析;
Compose 只把监听地址和数据库、Redis 地址固定覆盖为容器内部服务名。
-## 持久化
+## 持久化与备份
Compose 使用以下绑定目录:
-- `.runtime/data` → 应用身份密钥、credential 加密 keyring 和更新状态
+- `.runtime/data` → OpenAI 会话锚点密钥、更新状态与临时更新目录
- `.runtime/logs` → 应用文件日志
- `.runtime/postgres` → PostgreSQL
- `.runtime/redis` → Redis AOF
普通 `docker compose down` 不会删除这些目录。删除 `.runtime` 会永久清除本地状态。
-PostgreSQL 由 `0001_initial.sql` 创建当前业务结构,Redis 保存可丢失的协调状态。
+PostgreSQL 是账号、Client Key、运行设置、请求记录与审计的权威存储;账号 credential 当前按
+Provider schema 以明文 JSON 保存于 PostgreSQL。Redis 只保存可重建、可过期的协调状态,例如
+会话亲和、lease、cooldown、OAuth pending flow 与套餐模型目录 cache。
+
+备份必须包含 `.runtime/postgres`。若希望保留短期 Redis 状态、OpenAI 会话亲和和更新状态,
+同时备份 `.runtime/redis` 与 `.runtime/data`;后两者丢失不会造成账号 credential 丢失,但会使
+缓存和会话锚点重新建立。
完整运行时、Provider、revision 与恢复边界见 [架构文档](../docs/architecture.md)。
-## Credential 加密 keyring
-
-首次启动会在 `.runtime/data/credential_keyring` 生成:
-
-- `active_key_id`:新 envelope 唯一使用的 active key ID。
-- `envelope_keys/.key`:active key 与只读历史解密 key。
-- `credential_fingerprint_hmac_key`:稳定 credential 去重域,不能随 envelope key 轮换。
-- `resource_pseudonym_hmac_key`:稳定资源伪名域,不能随 envelope key 轮换。
-
-目录权限固定为 `0700`,文件权限固定为 `0600`;符号链接、组/其他用户可读权限、无效 key ID
-或重复 key 材料都会让进程拒绝启动。Keyring 不进入 PostgreSQL,必须和 PostgreSQL 备份分开保存;
-丢失任何仍被数据库引用的 envelope key 后,对应 credential、Cookie 或 continuation 将无法恢复。
-
-Envelope key 使用“读旧写新”两阶段轮换。多节点必须为每个节点执行相同步骤:
-
-1. 生成相同的新 key ID 和 32-byte key,写入 `envelope_keys/.key`,保持当前
- `active_key_id` 不变。
-2. 滚动重启全部节点,使每个旧进程都先把新 key 加载为历史解密 key。
-3. 在全部节点原子替换 `active_key_id`,再滚动重启。已重启节点只用新 key 写入,尚未重启节点
- 仍能用第二步加载的 key 解密新 envelope。
-
-单机 Compose 可按以下方式准备和激活新 key;不要把命令输出或 key 文件内容写入日志:
-
-```bash
-new_key_id="ek-$(openssl rand -hex 16)"
-openssl rand -hex 32 | sudo sh -c \
- 'umask 077; cat > ".runtime/data/credential_keyring/envelope_keys/$1.key"' sh "$new_key_id"
-sudo chown 10001:10001 ".runtime/data/credential_keyring/envelope_keys/$new_key_id.key"
-
-# 先重启一次,让当前进程把新 key 作为历史 key 加载。
-docker compose -f deploy/compose.yaml restart codex-proxy-rs
-
-sudo env NEW_KEY_ID="$new_key_id" sh -c '
- umask 077
- printf "%s" "$NEW_KEY_ID" > .runtime/data/credential_keyring/active_key_id.next
- chown 10001:10001 .runtime/data/credential_keyring/active_key_id.next
- mv -f .runtime/data/credential_keyring/active_key_id.next \
- .runtime/data/credential_keyring/active_key_id
-'
-docker compose -f deploy/compose.yaml restart codex-proxy-rs
-unset new_key_id
-```
-
-历史 key 只有在所有引用计数均为零、备份也满足同一退役策略后才能删除:
-
-```sql
-select 'upstream_credentials' as source, count(*) from upstream_credentials where secret_key_id = $1
-union all
-select 'codex_account_cookies', count(*) from codex_account_cookies where secret_key_id = $1
-union all
-select 'conversation_items', count(*) from conversation_items where secret_key_id = $1
-union all
-select 'continuation_bindings', count(*) from continuation_bindings where secret_key_id = $1;
-```
-
-删除仍被引用的历史 key 不会降级或尝试其他 key;解密会以 unknown key fail closed。确认零引用后,
-从全部节点删除对应 `.key` 文件并滚动重启,才算完成退役。Fingerprint 与 resource pseudonym 两个
-HMAC key 不属于此轮换流程,不能用 envelope key 替换。
-
## 密码语义
- `admin.default_password` 只在首次创建管理员时使用。
@@ -159,9 +111,10 @@ HMAC key 不属于此轮换流程,不能用 envelope key 替换。
- Redis 在每次容器创建时使用 `redis.password`。
已有 PostgreSQL 数据目录后,直接修改 `database.password` 不会修改数据库用户密码,只会导致
-应用无法连接。轮换时必须先在 PostgreSQL 中修改用户密码,再同步更新 `config.yaml`。
+应用无法连接。轮换时必须先在 PostgreSQL 中修改用户密码,再同步更新 `config.yaml`。Redis
+密码变更后需要使用新密码重建或重新配置 Redis 数据目录。
-## 构建与升级
+## 镜像升级与源码构建
```bash
docker compose -f deploy/compose.yaml build codex-proxy-rs
@@ -175,6 +128,14 @@ docker compose -f deploy/compose.yaml pull codex-proxy-rs
docker compose -f deploy/compose.yaml up -d --no-build
```
+仓库维护者发布新版本时必须从干净且已同步上游的分支运行:
+
+```bash
+release/publish
+```
+
+该脚本负责更新 `release/version.yaml`、创建版本提交和带注释 tag,并原子推送分支与 tag。
+
构建元数据仍可作为一次性进程环境传入,不需要 `.env` 文件:
```bash
@@ -184,22 +145,22 @@ CPR_BUILD_TIME="$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
docker compose -f deploy/compose.yaml build codex-proxy-rs
```
-### 管理端 Release 更新
+### 管理端在线更新
-Compose 已显式装配正式更新所需的运行参数:
+Compose 已显式装配正式发布构建所需的运行参数:
- `CPR_UPDATE_REPOSITORY`:只接受 `owner/repository`;默认 `zyycn/codex-proxy-rs`。
- `CPR_GITHUB_API_BASE`:正式环境必须为 `https://api.github.com/repos`。
- `CPR_UPDATE_CHANNEL`:`stable` 会拒绝 prerelease。
-- `CPR_UPDATE_EXE_PATH`、`CPR_WEB_DIST_DIR`:分别指向容器内二进制和旧前端静态目录。
+- `CPR_UPDATE_EXE_PATH`、`CPR_WEB_DIST_DIR`:分别指向容器内二进制和前端静态目录。
- `CPR_UPDATE_TEMP_DIR`、`CPR_UPDATE_STATE_FILE`、`CPR_UPDATE_LOCK_FILE`:全部位于持久化的
`.runtime/data`。
- `CPR_ENABLE_SELF_RESTART=true`:更新或回滚完成后允许管理端请求重启;Docker 进程退出后由
Compose 的 `restart: unless-stopped` 拉起新进程。
-Release 必须提供当前 OS/架构的 `codex-proxy-rs___.tar.gz` 和
-`checksums.txt`。服务会在替换前再次查询远端最新版本,校验下载 host、声明大小、SHA-256 和 tar
-路径;二进制或静态资源任一替换失败时恢复旧文件。成功后的旧二进制和旧静态目录分别保留为
+Release 必须提供当前 OS/架构的 `codex-proxy-rs___.tar.gz` 与
+`checksums.txt`。服务会在替换前再次查询远端最新版本,校验下载 host、声明大小、SHA-256 和
+归档路径;二进制或静态资源任一替换失败时恢复旧文件。成功后的旧二进制和旧静态目录分别保留为
`*.backup`,管理端 rollback 会交换当前文件与这份备份。更新状态和跨进程锁可在以下位置排查:
```text
diff --git a/deploy/config.example.yaml b/deploy/config.example.yaml
index b3eca6cdb..3645ecb45 100644
--- a/deploy/config.example.yaml
+++ b/deploy/config.example.yaml
@@ -5,7 +5,7 @@
# sudo chown "$(id -u):10001" deploy/config.yaml
# chmod 640 deploy/config.yaml
#
-# 三个密码建议分别执行 `openssl rand -hex 24` 生成。
+# PostgreSQL 与 Redis 密码分别执行 `openssl rand -hex 24` 生成;管理员初始密码单独设置。
schema_version: 1
@@ -51,8 +51,9 @@ api:
request_id_header: 'x-request-id'
openai:
+ # 上游地址、WebSocket 池、额度刷新和 OAuth 使用 Provider 内置默认值;模板只保留画像启动基线。
wire_profile:
- # 该画像来自 Desktop 制品与 bundled Codex Core 的联合审计。
+ # Provider 每 24 小时读取官方 npm metadata 与 Desktop appcast,仅在进程内更新版本字段。
originator: 'Codex Desktop'
codex_version: '0.145.0'
desktop_version: '26.721.31836'
@@ -64,8 +65,9 @@ openai:
verified_at: '2026-07-25T00:00:00+08:00'
xai:
+ # OAuth 端点、额度与模型目录策略由 xAI Provider 固定管理;这里只声明请求画像启动基线。
wire_profile:
- # 对照 grok2api 当前推荐构建核验;更新前应重新抓包确认。
+ # Provider 每 24 小时读取官方 npm metadata,仅在进程内更新 client_version。
client_identifier: 'grok-shell'
client_version: '0.2.106'
client_mode: 'headless'
diff --git a/docs/api.md b/docs/api.md
new file mode 100644
index 000000000..60589daaf
--- /dev/null
+++ b/docs/api.md
@@ -0,0 +1,236 @@
+# Codex Proxy RS 接口
+
+本文列出 v3.0.0 当前公开 HTTP 接口。路由事实以
+`backend/crates/gateway-api/src` 中的 router 为准。
+
+## 1. 鉴权与公共约定
+
+### 客户端接口
+
+所有 `/v1/*` 请求都使用管理端创建的 Client Key:
+
+```http
+Authorization: Bearer sk_...
+```
+
+Client Key 固定绑定一个 Provider:`openai` 或 `xai`。同一次请求不会跨 Provider fallback。
+
+### 管理接口
+
+除登录、会话状态和登出外,所有 `/api/admin/*` 请求都需要以下任一鉴权方式:
+
+- 浏览器登录后得到的 `cpr_admin_session` Cookie;
+- `x-api-key: `。
+
+已鉴权的管理请求还必须携带非空 `x-request-id`。管理端响应统一带
+`Cache-Control: no-store`。
+
+普通成功响应使用以下信封:
+
+```json
+{
+ "code": 200,
+ "message": "OK",
+ "data": {}
+}
+```
+
+错误响应仍使用 `code`、`message`、`data`,其中 `data` 为 `null`。稳定业务码包括参数错误
+`40001`、会话缺失 `40101`、资源不存在 `40401`、revision 冲突 `40901`、上游失败 `50201`
+和依赖不可用 `50301`。
+
+### 配置 revision
+
+会改变路由快照或安全配置的 mutation 使用 `expectedConfigRevision` 做乐观并发控制。客户端应从
+账号、Client Key 或设置查询结果读取最新 `configRevision`;收到 `40901` 后重新读取,不应盲目重试
+旧 revision。
+
+## 2. 健康检查
+
+| 方法 | 路由 | 鉴权 | 说明 |
+| --- | --- | --- | --- |
+| `GET` | `/healthz` | 无 | Core、Store 和后台任务健康时返回 `204`,否则返回 `503` |
+
+## 3. Responses 与模型目录
+
+请求正文上限为 16 MiB。
+
+| 方法 | 路由 | 说明 |
+| --- | --- | --- |
+| `POST` | `/v1/responses` | OpenAI Responses JSON;`stream=true` 返回 SSE,否则返回完整 JSON |
+| `GET` | `/v1/responses` | 通过 HTTP Upgrade 建立 Responses WebSocket |
+| `POST` | `/v1/responses/review` | 使用同一 Responses 合同发起 review 子代理请求 |
+| `GET` | `/v1/models` | 返回当前 Client Key 所属 Provider 的可用公开模型 |
+| `GET` | `/v1/models/catalog` | 返回 Codex 客户端使用的模型目录 |
+| `GET` | `/v1/models/{model_id}/info` | 返回 Codex 客户端使用的单模型信息 |
+| `GET` | `/v1/models/{model_id}` | 返回 OpenAI 兼容的单模型详情 |
+
+OpenAI 路径保留客户端 Responses wire 语义;xAI 所需的协议转换只在 xAI Provider 内完成。模型映射
+是全局精确映射,未命中时模型名原样交给所属 Provider。
+
+## 4. 管理员认证
+
+| 方法 | 路由 | 请求 | 说明 |
+| --- | --- | --- | --- |
+| `POST` | `/api/admin/auth/login` | `{ username?, password }` | 创建管理员会话并设置 Cookie |
+| `GET` | `/api/admin/auth/status` | 无 | 返回当前 Cookie 是否已认证 |
+| `POST` | `/api/admin/auth/logout` | 无 | 删除当前会话并清除 Cookie |
+
+## 5. 账号
+
+账号 API 使用统一路由,不存在 Provider Instance 或 Provider 专属账号路由。需要 Provider 的请求只接受
+`provider: "openai" | "xai"`。
+
+| 方法 | 路由 | 主要 query/body | 说明 |
+| --- | --- | --- | --- |
+| `GET` | `/api/admin/accounts` | `page`、`pageSize`、`provider`、`search`、`status`、排序字段 | 分页查询账号与汇总 |
+| `GET` | `/api/admin/accounts/detail` | `accountId` | 查询账号详情、额度和本地用量 |
+| `GET` | `/api/admin/accounts/export` | `accountIds`、`confirm=export_sensitive_accounts` | 显式导出最多 200 个账号的敏感 Provider 文档 |
+| `POST` | `/api/admin/accounts/import` | `{ provider, expectedConfigRevision, data }` | 导入或按上游身份更新账号 |
+| `POST` | `/api/admin/accounts/refresh` | `{ accountId, expectedConfigRevision }` | 手工刷新账号 credential |
+| `POST` | `/api/admin/accounts/rotate` | OpenAI rotation 字段 | 手工替换 OpenAI OAuth token |
+| `POST` | `/api/admin/accounts/enable` | `{ provider, accountId, expectedConfigRevision }` | 启用账号 |
+| `POST` | `/api/admin/accounts/disable` | `{ provider, accountId, expectedConfigRevision }` | 禁用账号 |
+| `POST` | `/api/admin/accounts/delete` | `{ provider, accountIds, expectedConfigRevision }` | 批量删除 1–200 个账号 |
+| `GET` | `/api/admin/accounts/quota` | `accountId` | 读取当前额度,不强制访问上游 |
+| `POST` | `/api/admin/accounts/quota/refresh` | `{ accountId }` | 立即刷新该账号额度 |
+| `GET` | `/api/admin/accounts/models` | `accountId` | 优先读取该 Provider + 套餐的模型 cache,缺失时有限实时拉取 |
+| `POST` | `/api/admin/accounts/models/refresh` | `{ accountId }` | 强制拉取最新模型并覆盖 cache |
+| `GET` | `/api/admin/accounts/connection-test` | `accountId`、`modelId` | 通过 SSE 返回实时连接测试事件,不作为业务 Responses 用量记录 |
+| `POST` | `/api/admin/accounts/oauth/start` | OAuth start 字段 | 创建 OpenAI 或 xAI OAuth flow |
+| `POST` | `/api/admin/accounts/oauth/complete` | `{ provider, flowId, callbackUrl }` | 消费 OAuth callback 并写入账号 |
+
+账号列表支持以下稳定值:
+
+- `provider`: `all`、`openai`、`xai`;
+- `status`: `active`、`expired`、`quota_exhausted`、`disabled`、`banned`;
+- `sortBy`: `email`、`status`、`planType`、`usage`、`lastUsedAt`、`expiresAt`;
+- `sortDirection`: `asc`、`desc`。
+
+导入的 `data` 必须是 JSON object,内部 schema 由目标 Provider 独占解释:
+
+- OpenAI 接受 OAuth 账号文档、账号 bundle 和 Agent Identity;
+- xAI 从单账号 object 或 `accounts` 数组中提取 OAuth token;包装中的代理、并发、优先级等字段不参与认证;
+- xAI API Key 不是受支持的账号 credential;
+- 导入仍会通过 Provider 的身份或刷新链路校验,不能只凭文件外形写入账号。
+
+OpenAI 的 CPR 导出保持 OAuth 账号的既有字段;Agent Identity 账号则输出
+`authMode: "agentIdentity"`、`agentRuntimeId`、`agentPrivateKey` 和可选的
+`taskId`,不伪造 OAuth token 或过期时间。一个 `sourceFormat: "cpr"` 文档可以包含两种
+认证类型,导入时按每个账号的认证模式分别解析。
+
+```json
+{
+ "sourceFormat": "cpr",
+ "accounts": [{
+ "id": "acct_...",
+ "accountId": "...",
+ "userId": "...",
+ "authMode": "agentIdentity",
+ "agentRuntimeId": "...",
+ "agentPrivateKey": "...",
+ "taskId": "..."
+ }]
+}
+```
+
+OpenAI rotation 请求字段为:
+
+```json
+{
+ "provider": "openai",
+ "accountId": "acct_...",
+ "expectedConfigRevision": 1,
+ "expectedCredentialRevision": 1,
+ "accessToken": "...",
+ "refreshToken": "..."
+}
+```
+
+OAuth start 使用:
+
+```json
+{
+ "provider": "openai",
+ "expectedConfigRevision": 1,
+ "name": "account name",
+ "accountId": null,
+ "expectedCredentialRevision": null
+}
+```
+
+重新授权已有账号时,`accountId` 与 `expectedCredentialRevision` 必须同时提供。
+
+## 6. Client Key
+
+| 方法 | 路由 | 主要 query/body | 说明 |
+| --- | --- | --- | --- |
+| `GET` | `/api/admin/client-keys` | `cursor`、`limit`、`search`、`sortBy`、`sortDirection` | 游标分页查询 |
+| `POST` | `/api/admin/client-keys/create` | 创建字段 | 创建绑定 Provider 的 Client Key |
+| `GET` | `/api/admin/client-keys/reveal` | `id` | 显式读取完整明文 Key |
+| `POST` | `/api/admin/client-keys/update` | 更新字段 | 原子更新名称、Provider 和限额 |
+| `POST` | `/api/admin/client-keys/enable` | `{ id, expectedConfigRevision }` | 启用 |
+| `POST` | `/api/admin/client-keys/disable` | `{ id, expectedConfigRevision }` | 禁用 |
+| `POST` | `/api/admin/client-keys/delete` | `{ id, expectedConfigRevision }` | 删除 |
+
+创建字段为 `expectedConfigRevision`、`name`、可选 `label`、`providerKind`、`maxConcurrency`、
+`requestsPerMinute`。更新请求再增加 `id`。创建和 reveal 响应会返回完整明文 Key,调用方必须立即安全保存。
+
+## 7. 运行设置
+
+| 方法 | 路由 | 说明 |
+| --- | --- | --- |
+| `GET` | `/api/admin/settings` | 读取运行设置与 `configRevision` |
+| `POST` | `/api/admin/settings/update` | 原子替换全部运行设置 |
+| `GET` | `/api/admin/settings/admin-api-key` | 只返回管理 API Key 是否存在 |
+| `POST` | `/api/admin/settings/admin-api-key/delete` | 删除管理 API Key |
+| `POST` | `/api/admin/settings/admin-api-key/regenerate` | 重新生成并一次性返回完整管理 API Key |
+
+设置更新字段包括:
+
+```text
+expectedConfigRevision
+modelMappings
+refreshMarginSeconds
+refreshConcurrency
+maxConcurrentPerAccount
+requestIntervalMs
+rotationStrategy
+usageRetentionDays
+opsEventRetentionDays
+auditRetentionDays
+```
+
+`rotationStrategy` 可取 `smart`、`quota_reset_priority`、`round_robin`、`sticky`。
+
+## 8. Dashboard、用量与错误
+
+| 方法 | 路由 | 说明 |
+| --- | --- | --- |
+| `GET` | `/api/admin/dashboard/summary` | Dashboard 汇总;支持 `kind`、`startTime`、`endTime` |
+| `GET` | `/api/admin/dashboard/trend` | Dashboard 趋势;`kind=usage|latency|errors` |
+| `GET` | `/api/admin/usage/records` | 请求记录分页列表 |
+| `GET` | `/api/admin/usage/records/detail` | 按 `id` 查询请求详情 |
+| `GET` | `/api/admin/usage/records/summary` | 当前筛选条件的请求汇总 |
+| `GET` | `/api/admin/usage/insights/overview` | 用量、成本与成功率洞察 |
+| `GET` | `/api/admin/usage/insights/diagnostics` | 按维度聚合诊断 |
+| `GET` | `/api/admin/operations/errors` | 运维错误分页列表 |
+
+用量查询可组合页码/游标、时间范围、Provider、Client Key、账号、模型、route、transport、状态码、
+request/response/upstream ID、outcome 与搜索文本。诊断 `dimension` 可取 `model`、`account`、
+`apiKey`、`provider`、`transport`、`failureClass`、`status`。
+
+## 9. 版本、更新与重启
+
+| 方法 | 路由 | 主要 query/body | 说明 |
+| --- | --- | --- | --- |
+| `GET` | `/api/admin/system/version` | 无 | 当前构建、部署模式和可用更新 |
+| `GET` | `/api/admin/system/update/detail` | `refresh=true|false` | 读取或强制刷新 Release 详情 |
+| `GET` | `/api/admin/system/update/events` | 无 | SSE 更新事件流 |
+| `POST` | `/api/admin/system/update` | 可选 `{ targetVersion }` | 开始在线更新 |
+| `GET` | `/api/admin/system/update/status` | 无 | 查询当前更新或回滚状态 |
+| `POST` | `/api/admin/system/rollback` | 无 | 回滚到保留的上一版本 |
+| `POST` | `/api/admin/system/restart` | 无 | 请求进程重启 |
+
+在线更新仅在当前部署模式、Release 资产和进程重启能力都满足要求时可用。仓库发版流程见
+[部署文档](../deploy/README.md) 与根目录 [README](../README.md)。
diff --git a/docs/architecture.md b/docs/architecture.md
index 46713a627..3a10dfaa1 100644
--- a/docs/architecture.md
+++ b/docs/architecture.md
@@ -130,7 +130,7 @@ credential 更新使用 `credential_revision` CAS。认证永久失败、封禁
| `admin_audit_events` | 管理 mutation 审计 |
| `client_api_keys` | 客户端鉴权、限额与授权范围 |
| `runtime_settings` | 全局配置与 `config_revision` |
-| `provider_accounts` | 账号资料、加密 credential、revision、quota、cooldown |
+| `provider_accounts` | 账号资料、Provider-owned 明文 credential JSON、revision、quota、cooldown |
| `model_requests` | 请求、attempt、计费、交付与恢复事实 |
| `ops_events` | 脱敏运行事件 |
@@ -138,7 +138,8 @@ credential 更新使用 `credential_revision` CAS。认证永久失败、封禁
- 一个事实只存一次;可表达关系使用真实 FK 与支持索引。
- Provider 差异只进入受 schema/version 校验的 JSONB 边界。
-- secret 只保存加密 envelope;日志、Debug、API 和 audit 不输出 secret。
+- credential JSON 以 Provider 自己的 schema 明文保存在 `provider_credentials_json`;日志、Debug、API 和 audit
+ 不输出其中的 secret,Provider 只在需要发往上游时把值包装为运行时 secret。
- stale recovery 只把超时 `running` 请求收敛为 `incomplete`,不重放业务请求。
- `ops_events` 当前逐条同步持久化,`occurrence_count=1`;OpsFlush 可禁用,不存在隐藏内存聚合权威。
- retention 只删除已满足保留规则的历史事实,不改变运行中请求。
diff --git a/skills/codex-proxy-rs/SKILL.md b/skills/codex-proxy-rs/SKILL.md
index 83343c642..523af6584 100644
--- a/skills/codex-proxy-rs/SKILL.md
+++ b/skills/codex-proxy-rs/SKILL.md
@@ -19,9 +19,13 @@ description: Codex Proxy RS 仓库开发指南。Use when changing or auditing i
- `gateway-api` 只做协议适配;`gateway-core` 编排;Provider 独占 credential、catalog 与 transport;`gateway-store` 实现持久化端口。
- Provider 每次调用只选择一个 credential,不隐藏换号、业务 retry 或跨 Provider fallback。
- PostgreSQL 是持久化权威;Redis 只保存可恢复协调状态和 OAuth pending flow。
+- 当前只有 `openai` 与 `xai` 两个 Provider;账号归属 Provider,不存在 Provider Instance 层。
+- OpenAI 的 OAuth 与 Agent Identity credential 由 `providers/openai` 独占解析;账号导出使用 CPR 文档,Agent Identity
+ 只输出运行所需身份材料,不伪造 OAuth 字段。
- 测试放在各 package 的 `tests/`。禁止在生产 `src/` 写 test-only 代码。
- Vue 使用 `