Admin API
目标读者:构建管理后端、开发者控制台或集成测试的开发者。
admin API 使用 contracts version 0.4.0-alpha.1,本地地址是 http://localhost:4100。它使用开发者 JWT bearer auth,不使用 project key。当前 Alpha 支持开发者注册登录、组织/项目、project key、邀请、反馈列表/详情、附件、状态和优先级更新、评论以及 audit logs。
Developer Console 和所有管理客户端都使用 /admin/v1/*。已删除的 Admin /v1/* 兼容路径返回 404;Public API 是独立服务,它的 /v1/* 路由仍是 canonical 路由。
通过 seed 或 bootstrap 创建的 system admin,其唯一额外的系统级能力是调用只读的 /admin/v1/system/users 查看所有用户的基础资料字段。system_admin 是账号 user_type,不会绕过任何组织或项目权限;所有 tenant API 都按目标组织或项目中的真实 membership role(owner、admin、developer、qa、viewer)授权。没有对应 membership 时,定向 tenant 请求返回 403,集合与 Developer Graph 也不会包含该 tenant;创建新组织会同时为创建者建立 owner membership。
账号须先验证邮箱才能访问组织与项目。JWT 绑定真实服务端会话;普通退出撤销当前会话,全部退出、改密、重置、禁用和注销撤销对应的全部会话。旧 Alpha 身份和数据不迁移。恢复、注销、SMTP 和数据保留边界见账号与安全。
路由表
/admin/v1/* 是唯一的管理命名空间。
| 区域 | Canonical 路由 |
|---|---|
| 数据生命周期 | POST /admin/v1/feedback/reports/{report_id}/deletion, POST /admin/v1/projects/{project_id}/deletion, POST /admin/v1/organizations/{organization_id}/deletion, GET /admin/v1/data-deletions/{deletion_id}, POST /admin/v1/projects/{project_id}/export |
| 存活与就绪 | GET /healthz, GET /readyz |
| 认证 | POST /admin/v1/auth/register, POST /admin/v1/auth/login, POST /admin/v1/auth/logout, GET /admin/v1/auth/me , POST /admin/v1/auth/verification/request, POST /admin/v1/auth/verification/confirm, POST /admin/v1/auth/password/reset/request, POST /admin/v1/auth/password/reset/confirm, POST /admin/v1/auth/password/change, POST /admin/v1/auth/logout-all, POST /admin/v1/auth/account/delete |
| 开发者图 | GET /admin/v1/developer-graph |
| 组织 | GET|POST /admin/v1/organizations, GET|PATCH /admin/v1/organizations/{organization_id}, GET /admin/v1/organizations/{organization_id}/members, PATCH|DELETE /admin/v1/organizations/{organization_id}/members/{user_id} |
| 组织邀请 | GET|POST /admin/v1/organizations/{organization_id}/developer-invitations, POST /admin/v1/organizations/{organization_id}/developer-invitations/{invitation_id}/revoke, POST /admin/v1/invitations/accept |
| 项目 | GET|POST /admin/v1/projects, GET|PATCH /admin/v1/projects/{project_id}, GET /admin/v1/projects/{project_id}/overview |
| 项目成员和邀请 | GET /admin/v1/projects/{project_id}/members, PATCH|DELETE /admin/v1/projects/{project_id}/members/{user_id}, GET|POST /admin/v1/projects/{project_id}/invitations, POST /admin/v1/projects/{project_id}/invitations/{invitation_id}/revoke |
| Project keys | GET|POST /admin/v1/projects/{project_id}/keys, POST /admin/v1/projects/{project_id}/keys/{key_id}/revoke |
| 反馈 | GET /admin/v1/feedback/reports, GET|PATCH /admin/v1/feedback/reports/{report_id} |
| 反馈评论 | GET|POST /admin/v1/feedback/reports/{report_id}/comments |
| 附件 | GET /admin/v1/feedback/reports/{report_id}/attachments, GET /admin/v1/feedback/reports/{report_id}/attachments/{attachment_id}/download-url |
| 审计和用户 | GET /admin/v1/audit-logs, GET /admin/v1/system/users |
附件下载接口返回精确的顶层对象 { "url": "https://...", "expires_at": "<RFC3339>" },不会再包裹 signed URL。
路径里的 report_id 是当前实现中的反馈报告 ID,等同于产品 GOAL 文本中的 feedback_id。
创建 Project Key 返回 { "key": { ...脱敏元数据... }, "plaintext_key": "enb_pk_..." }。plaintext_key 只复制一次;列表和撤销响应只返回脱敏 key 对象,响应字段不使用 project_key。
新建 Key 始终包含四字符的 last_four 脱敏尾码。若历史 Key 创建时尚未保存该事实,升级后的列表或撤销响应可以省略 last_four;API 不会为无法恢复的 secret 编造占位字符。
组织 owner 可以修改任意成员角色并授予 owner;组织 admin 只能修改或移除非 owner 成员,且不能授予 owner。降级或移除最后一名 owner 会返回 409 admin.organization_member.last_owner。相同角色的 PATCH 是幂等操作。移除组织成员关系不会删除该用户已有的直接项目成员关系。成功的角色变更与移除分别写入 organization_member.role_updated 或 organization_member.removed 审计;被拒绝和 no-op 请求不写审计。
分页与稳定排序
12 个 canonical list GET 都接受 limit 和 offset,并返回精确的顶层 { items, offset, limit, total, has_more };不会返回 cursor、next_cursor、嵌套 pagination、count 或 next_offset。
{
"items": [],
"offset": 0,
"limit": 50,
"total": 0,
"has_more": false
}
省略 limit 时使用 50,有效范围是 1 到 100;省略 offset 时使用 0,且不能为负数。显式空值、重复参数、非整数或越界值返回 400 admin.request.invalid。total 是在 tenant 授权与业务筛选后、应用 offset/limit 前的数量;has_more 只表示该筛选结果是否还有下一页。超出 total 的有效 offset 返回 200 和空 items,不是错误。
| List GET | 固定顺序 |
|---|---|
/admin/v1/organizations |
name ASC, id ASC |
/admin/v1/organizations/{organization_id}/members |
email ASC, user_id ASC |
/admin/v1/organizations/{organization_id}/developer-invitations |
created_at DESC, id DESC |
/admin/v1/projects |
name ASC, id ASC |
/admin/v1/projects/{project_id}/members |
email ASC, user_id ASC |
/admin/v1/projects/{project_id}/invitations |
created_at DESC, id DESC |
/admin/v1/projects/{project_id}/keys |
created_at DESC, id DESC |
/admin/v1/feedback/reports |
received_at DESC, id DESC |
/admin/v1/feedback/reports/{report_id}/attachments |
created_at ASC, id ASC |
/admin/v1/feedback/reports/{report_id}/comments |
created_at ASC, id ASC |
/admin/v1/audit-logs |
created_at DESC, id DESC |
/admin/v1/system/users |
email ASC, id ASC |
反馈列表只支持 project_id、status、category 和大小写不敏感的 search 筛选,search 匹配 title/description。Developer Graph 是一次返回当前用户、可见组织、项目和 membership 的导航聚合,不属于这 12 个 list GET,也不接受分页参数。
组织设置
PATCH /admin/v1/organizations/{organization_id} 只接受一个 JSON 对象,并要求其中恰好包含 name。它只修改显示名称;缺少字段、未知字段、重复字段或包含 slug 等额外字段都会返回 400 admin.organization.invalid。组织 ID 和 slug 始终不可变。
服务端先检查原始名称不超过 120 个 Unicode code point,再去除首尾 Unicode 空白;规范化后的名称不能为空。大小写和中间空白会原样保留。组织名称不要求唯一,另一个组织使用相同的规范化名称不会触发 409。
只有在该组织中拥有明确 owner 或 admin membership 的调用者可以改名。developer、qa 和 viewer 成员返回 403;system_admin 账号没有 tenant 绕过能力,同样必须显式拥有 owner 或 admin membership。规范化后的 no-op 返回 200 和完整的当前 { "organization": ... },不改变 updated_at,也不写审计。真实改名会与恰好一条 organization.name_updated 事件原子提交。事件以该组织为 target,不带 project_id,metadata 精确为 { "old_name": ..., "new_name": ... }。
把 bearer token 与组织 ID 放在本地环境变量中,不要把 secret 写进命令或文档:
ADMIN_API="${ADMIN_API:-http://localhost:4100}"
: "${ACCESS_TOKEN:?请先 export ACCESS_TOKEN}"
: "${ORG_ID:?请先 export ORG_ID}"
curl --fail-with-body -X PATCH "$ADMIN_API/admin/v1/organizations/$ORG_ID" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept-Language: zh-CN" \
-d '{"name":"Echo Nebula 工作室"}'
项目设置
PATCH /admin/v1/projects/{project_id} 可以更新 name、default_locale、default_environment 和 sdk_features。请求至少包含其中一个字段,拒绝其他所有顶层字段,并原样保留每个未出现的值。name 和 default_environment 会去除首尾空白但保留大小写;default_locale 必须精确为 zh-CN、en 或 ja,不会去除空白或规范化大小写。
一旦出现 sdk_features,它就是完整替换对象:必须同时提供布尔类型的 screenshot、recent_logs、breadcrumbs、offline_queue 和 contact。部分 feature 对象无效。slug 是只读字段,PATCH 不接受该字段,修改项目名称也不会改变 slug。
只有有效项目角色为 owner 或 admin 的调用者可以更新设置,即组织 owner/admin 或直接项目 admin;单独的 system_admin 身份不授予访问权。Developer、QA 和 viewer 返回 403。非法、空或未知字段返回 400 admin.project.invalid,项目不存在返回 404 admin.project.not_found;该操作没有 409 情况。
规范化后的 no-op 返回 200 和当前完整 { "project": ... },不改变 updated_at,也不写审计。一个或多个真实变更会原子提交,并写一条 project.updated。其 metadata 包含按固定顺序排列的 changed_fields,以及只含实际变更字段的 before 和 after;SDK feature 变化时,两侧都记录完整五项对象。
curl -X PATCH "http://localhost:4100/admin/v1/projects/proj_demo" \
-H "Authorization: Bearer <access_token>" \
-H "Content-Type: application/json" \
-H "Accept-Language: zh-CN" \
-d '{
"name": "Echo Arena",
"default_locale": "zh-CN",
"default_environment": "Production",
"sdk_features": {
"screenshot": true,
"recent_logs": false,
"breadcrumbs": true,
"offline_queue": true,
"contact": false
}
}'
存活、就绪与浏览器访问
GET /healthz 只证明 Admin API 进程可以响应。无需认证的 GET /readyz 会在有界超时内检查 PostgreSQL 和配置的对象存储 bucket。
| HTTP | 总体状态 | PostgreSQL | 对象存储 | 含义 |
|---|---|---|---|---|
200 |
ready |
ready |
ready |
租户数据和附件操作均已就绪。 |
200 |
degraded |
ready |
degraded |
文本管理仍可用,附件操作可能失败。 |
503 |
unavailable |
unavailable |
ready 或 degraded |
Admin API 无法提供租户数据。 |
所有就绪响应都使用 Cache-Control: no-store,且只返回 status、service 和两个组件状态;不会暴露依赖错误、端点、bucket 名称或凭证。
浏览器访问由 exact-origin ADMIN_CORS_ALLOWED_ORIGINS 列表控制。允许的 Origin 会原样写入 Access-Control-Allow-Origin;不匹配、伪通配或 null Origin 返回 403。响应包含 Vary: Origin,不会启用 credentialed CORS;没有 Origin 的非浏览器请求仍可访问。Bearer token 仍通过 Authorization header 发送。
最小示例
curl -X POST "http://localhost:4100/admin/v1/auth/register" \
-H "Content-Type: application/json" \
-H "Accept-Language: zh-CN" \
-d '{
"email": "new-developer@example.test",
"password": "change-me-123",
"display_name": "Demo Developer",
"preferred_locale": "zh-CN"
}'
curl -X POST "http://localhost:4100/admin/v1/auth/verification/confirm" \
-H "Content-Type: application/json" \
-d '{"email":"new-developer@example.test","password":"change-me-123","code":"<8-digit email code>"}'
curl -X POST "http://localhost:4100/admin/v1/auth/login" \
-H "Content-Type: application/json" \
-d '{
"email": "new-developer@example.test",
"password": "change-me-123"
}'
curl "http://localhost:4100/admin/v1/feedback/reports?project_id=proj_demo&status=new&category=bug&search=stuck&limit=50&offset=0" \
-H "Authorization: Bearer <access_token>"
curl -X POST "http://localhost:4100/admin/v1/feedback/reports/fbr_demo/comments" \
-H "Authorization: Bearer <access_token>" \
-H "Content-Type: application/json" \
-d '{"body":"QA 已确认截图上下文。"}'
curl "http://localhost:4100/admin/v1/audit-logs?project_id=proj_demo" \
-H "Authorization: Bearer <access_token>"
curl "http://localhost:4100/admin/v1/audit-logs?organization_id=org_demo" \
-H "Authorization: Bearer <access_token>"
合同中的反馈状态是 new、reviewing、resolved、ignored、archived。
合同中的反馈优先级是 unset、low、medium、high、urgent。
常见错误
- 不要在 admin 列表响应中暴露 project key 明文。
- 不要用
X-Project-Key认证 admin 路由。 - 不要把
system_adminuser type 当作 tenant role 或跨租户通行证。 - 读取 feedback、comments、attachments 或 audit logs 时不要绕过组织或项目成员权限检查。
- 不要移除最后一名组织 owner,也不要假设移除组织成员会撤销直接项目访问。
- 修改组织名称时,不要发送
slug、ID 或name之外的任何字段。 - 不要把
accept_token或token_hash写入 audit metadata。 - 不要把 project key 当成 admin 凭证使用。
- 不要发送不完整的
sdk_features对象,也不要尝试修改只读的项目 slug。 - 不要发送 cursor 参数,也不要从 list response 读取旧的嵌套 pagination 字段。
下一步
阅读 开发者用户图与邀请。
资源限制与启动配置策略见排障说明。