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(owneradmindeveloperqaviewer)授权。没有对应 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_updatedorganization_member.removed 审计;被拒绝和 no-op 请求不写审计。

分页与稳定排序

12 个 canonical list GET 都接受 limitoffset,并返回精确的顶层 { items, offset, limit, total, has_more };不会返回 cursornext_cursor、嵌套 paginationcountnext_offset

{
  "items": [],
  "offset": 0,
  "limit": 50,
  "total": 0,
  "has_more": false
}

省略 limit 时使用 50,有效范围是 1100;省略 offset 时使用 0,且不能为负数。显式空值、重复参数、非整数或越界值返回 400 admin.request.invalidtotal 是在 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_idstatuscategory 和大小写不敏感的 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

只有在该组织中拥有明确 owneradmin membership 的调用者可以改名。developerqaviewer 成员返回 403system_admin 账号没有 tenant 绕过能力,同样必须显式拥有 owneradmin 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} 可以更新 namedefault_localedefault_environmentsdk_features。请求至少包含其中一个字段,拒绝其他所有顶层字段,并原样保留每个未出现的值。namedefault_environment 会去除首尾空白但保留大小写;default_locale 必须精确为 zh-CNenja,不会去除空白或规范化大小写。

一旦出现 sdk_features,它就是完整替换对象:必须同时提供布尔类型的 screenshotrecent_logsbreadcrumbsoffline_queuecontact。部分 feature 对象无效。slug 是只读字段,PATCH 不接受该字段,修改项目名称也不会改变 slug。

只有有效项目角色为 owneradmin 的调用者可以更新设置,即组织 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,以及只含实际变更字段的 beforeafter;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 readydegraded Admin API 无法提供租户数据。

所有就绪响应都使用 Cache-Control: no-store,且只返回 statusservice 和两个组件状态;不会暴露依赖错误、端点、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>"

合同中的反馈状态是 newreviewingresolvedignoredarchived。 合同中的反馈优先级是 unsetlowmediumhighurgent

常见错误

下一步

阅读 开发者用户图与邀请

资源限制与启动配置策略见排障说明

数据生命周期