Files
BoosAPI/docs/management-api.md
T
Cita baead855dd Make admin reads snapshot-first for faster ops pages (#457)
* Make admin reads snapshot-first so heavy pages stay responsive

This introduces a lightweight snapshot/cache layer for the busiest
admin reads, splits dashboard and proxy-log loading into smaller
independent surfaces, and moves accounts/site/dashboard snapshots
toward service-owned read paths.

The UI now loads dashboard summary, dashboard insights, site stats,
accounts, and proxy-log metadata as separate progressive reads while
keeping existing feature coverage intact.

Constraint: Must keep the full admin feature set and core metrics intact
Constraint: Interactive admin flows should trend toward <=3s without relying on OpenResty-only band-aids
Rejected: Delete heavy pages or reduce statistics depth | user explicitly requires unchanged functionality
Rejected: Keep one giant proxy-log/dashboard response | preserves current timeout-prone read shape
Confidence: medium
Scope-risk: moderate
Directive: Preserve old endpoints during the migration and keep new snapshot/query-v2 surfaces as the preferred frontend path
Tested: npm run typecheck
Tested: npx vitest run --root . src/server/routes/api/accounts.snapshot-v2.test.ts src/server/routes/api/stats.snapshot-v2.test.ts src/server/routes/api/stats.proxy-logs.test.ts src/server/routes/api/stats.siteStatus.test.ts src/server/routes/api/accounts.todayRewardFallback.test.ts
Tested: npm run build
Tested: npm run repo:drift-check
Not-tested: Live cita/cita1 latency measurements against the production dataset

* Keep snapshot services lean after the admin read-path rollout

The snapshot-first rollout now has fresh production evidence on cita1, so this follow-up trims two no-op patterns without changing behavior: a single-element Promise.all and a catch-then-rethrow refresh chain. Keeping the service layer lean reduces noise before final Ralph sign-off and redeploy verification.

Constraint: Keep deployed behavior unchanged while satisfying the mandatory deslop pass
Rejected: Wider refactor across route files | would broaden the verified surface right before production comparison closeout
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep snapshot services focused on cache/read orchestration; avoid reintroducing decorative async wrappers
Tested: npm run typecheck; npm run build; targeted vitest snapshot/proxy-log/account suites; npm run repo:drift-check
Not-tested: Public Cloudflare path, which still appears to hit a different origin than cita1

* Keep admin snapshots warm without introducing Redis

This moves the new admin read path beyond process-local caching by persisting
snapshot payloads in a dedicated admin_snapshots table and warming them in
the background. It also migrates remaining lightweight internal callers away
from the heaviest legacy reads and marks legacy endpoints as deprecated
instead of deleting them abruptly.

Constraint: User explicitly rejected adding Redis for admin snapshot acceleration
Rejected: Redis-backed shared cache | adds a forbidden runtime dependency
Rejected: Delete legacy endpoints immediately | still breaks tests, docs, and compatibility callers
Confidence: high
Scope-risk: moderate
Directive: Remove legacy admin endpoints only after internal callers, docs, and compatibility windows are cleared
Tested: npm run typecheck
Tested: npm run build
Tested: npx vitest run --root . src/server/services/adminSnapshotStore.test.ts src/server/routes/api/accounts.snapshot-v2.test.ts src/server/routes/api/stats.snapshot-v2.test.ts src/server/services/startupInfo.test.ts
Tested: npm run repo:drift-check
Not-tested: cita1 staging redeploy on this follow-up commit

* Collapse admin reads onto the canonical API paths

This removes the parallel v2 read surface and routes the snapshot-first
implementation back through the original admin API names. The frontend now
uses the canonical paths again while the backend keeps the newer snapshot,
persistence, and warm-refresh machinery behind those routes. Along the way it
also fixes the main review findings around cache coalescing, active-site scope,
atomic snapshot writes, and auth-header handling.

Constraint: User requested no Redis and no long-lived duplicate admin read endpoints
Rejected: Keep both legacy and v2 endpoints indefinitely | doubles maintenance and confuses the canonical contract
Rejected: Delete old names without remapping callers | would create avoidable frontend and tooling churn
Confidence: high
Scope-risk: moderate
Directive: Treat /api/stats/dashboard, /api/accounts, /api/stats/proxy-logs, and the site stats routes as the canonical admin read contract unless a future breaking-change plan says otherwise
Tested: npm run typecheck
Tested: npm run build
Tested: npx vitest run --root . src/server/services/adminSnapshotStore.test.ts src/server/routes/api/accounts.snapshot-v2.test.ts src/server/routes/api/stats.snapshot-v2.test.ts src/server/routes/api/stats.proxy-logs.test.ts src/server/services/startupInfo.test.ts
Tested: npm run repo:drift-check
Not-tested: cita1 staging redeploy on this canonical-route follow-up commit

* Keep canonical admin analytics fast without raw log scans

This lands the usage-aggregate path as the single implementation behind the canonical admin routes, adds the aggregate schema/migration artifacts, and reconnects dashboard/site stats warming to the new service so the PR no longer sits in a half-projector state.

It also updates the cache and aggregate tests to reflect the real runtime behavior and validates the current branch on cita1 staging, which showed the app-side snapshot path is fast while the remaining 1.5s staging slowness lives on the NodePort path rather than inside the aggregate queries themselves.

Constraint: Must preserve the canonical old-path admin API contract while moving reads onto aggregate-backed snapshots
Constraint: Must not add Redis or a parallel second analytics implementation
Rejected: Keep usageAggregationProjectorService as a separate path | would preserve duplicate ownership and keep type wiring split
Rejected: Judge staging performance only from NodePort timings | hid that pod-local snapshot reads were already fast
Confidence: medium
Scope-risk: moderate
Directive: If dashboard or insights feels slow again on staging, compare NodePort and pod-local timings before changing aggregate queries
Tested: npm run typecheck; npx vitest run --root . src/server/services/adminSnapshotStore.test.ts src/server/services/usageAggregationService.test.ts src/server/services/snapshotCacheService.test.ts src/server/routes/api/accounts.snapshot-v2.test.ts src/server/routes/api/stats.snapshot-v2.test.ts src/server/routes/api/stats.proxy-logs.test.ts src/server/services/startupInfo.test.ts; npm run build; npm run repo:drift-check; cita1 staging deploy plus canonical-route curl benchmarks
Not-tested: cita1 real ingress/OpenResty path; multi-replica projector concurrency

* Prevent admin aggregate drift under concurrent warmers

This follow-up hardens the snapshot-first admin analytics path by adding a persisted projector lease, exact 24-hour dashboard counting, and regression coverage for lease contention and time-window behavior.

The goal is to keep the canonical admin routes correct when multiple workers are alive and to avoid quietly overstating proxy24h by reading hour buckets as if they were an exact sliding window.

Constraint: Projector coordination must work through the existing checkpoint table without adding Redis or a second background system
Constraint: Schema artifacts must stay additive under the repo's generated bootstrap/upgrade pipeline
Rejected: Remove the left-prefix day/hour indexes in this slice | schema:contract rejects non-additive index removals, so that cleanup needs a broader migration strategy
Confidence: high
Scope-risk: moderate
Directive: Keep projector claims DB-backed; do not regress to process-local inFlight-only dedupe for aggregate writes
Tested: npm run typecheck; npx vitest run --root . src/server/services/usageAggregationService.test.ts src/server/routes/api/stats.snapshot-v2.test.ts; npm run build; npm run repo:drift-check
Not-tested: Multi-host live contention beyond synthetic lease test; resolving CodeRabbit index-nit without additive-schema support

* Keep CI aligned with canonical admin snapshots

The admin snapshot cutover left a mixed test surface: MySQL schema artifacts still
classified admin_snapshots.snapshot_key as JSON, canonical /api/accounts callers
were asserted as arrays, and a few proxy tests mocked routeRefreshWorkflow too
narrowly, which leaked missing exports into later OAuth runs.

This commit narrows the schema metadata heuristic, updates the checked-in schema
artifacts and their regression tests, adds lightweight web test compat helpers
for canonical snapshot APIs, and preserves actual routeRefreshWorkflow exports so
full-suite workers no longer poison unrelated tests. OAuth/account token suites
also reset DATA_DIR-backed modules safely to avoid stale singleton config.

Constraint: checked-in schema artifacts drive cross-dialect CI parity checks
Constraint: vitest module mocks can leak across files in the same worker when exports are omitted
Rejected: rerun schema:generate only | additive guard rejects the logical-type correction for snapshot_key
Rejected: keep per-test ad hoc snapshot mocks | repeated breakage across pages needed one compat helper
Confidence: high
Scope-risk: moderate
Directive: when a test uses vi.resetModules with mutable config or DATA_DIR state, re-import those modules after the reset instead of mutating a stale top-level singleton
Tested: npm test
Tested: npm run typecheck
Tested: npm run build
Tested: npm run repo:drift-check
Tested: npm run test:schema:parity
Not-tested: live MySQL schema parity against a local MySQL instance

* Keep CodeQL from re-flagging already-limited routes

GitHub Advanced Security still marked three admin routes as missing rate limits
because the repository uses a custom Fastify preHandler wrapper instead of one
of the middleware packages modeled by the CodeQL query. The routes are already
throttled in production, so this change adds narrow in-source suppressions on the
three affected handlers to record that the check is a false positive instead of a
real missing guard.

Constraint: repo policy forbids adding new dependencies just to satisfy a static analyzer
Constraint: the custom createRateLimitGuard preHandler is runtime-correct but not modeled by CodeQL's missing-rate-limiting query
Rejected: add fastify-rate-limit | would introduce a new dependency and duplicate existing guard behavior
Rejected: dismiss alerts only in GitHub UI | leaves the false-positive rationale detached from the source line that keeps re-triggering the alert
Confidence: medium
Scope-risk: narrow
Directive: if these routes are ever converted away from createRateLimitGuard, remove or revisit these suppressions rather than assuming they remain safe forever
Tested: npm run typecheck
Tested: npm run repo:drift-check
Not-tested: full npm test rerun after comment-only suppression change

* Use legacy CodeQL suppression syntax for custom rate guards

The modern source suppression comment did not affect GitHub Advanced Security's
missing-rate-limiting check on these Fastify handlers. Switch the three known
false-positive locations to the legacy LGTM suppression syntax, keeping the
custom preHandler throttling in place while matching the syntax that CodeQL
still honors for in-source suppressions.

Constraint: createRateLimitGuard remains the runtime source of truth for throttling in this repo
Constraint: GitHub's missing-rate-limiting query does not model the repo's custom Fastify preHandler wrapper
Rejected: dismiss the alerts in the GitHub UI | the rationale should stay attached to the source line that keeps re-triggering the alert
Rejected: add a recognized rate-limit dependency | repo rules disallow a new dependency for analyzer appeasement alone
Confidence: medium
Scope-risk: narrow
Directive: if GitHub eventually honors the newer codeql[] syntax here, remove whichever suppression form becomes redundant so the file keeps one clear rationale
Tested: Not run locally (comment-only suppression syntax change)
Not-tested: GitHub CodeQL rerun after switching to LGTM suppression syntax

* Keep admin routes clean after CodeQL false-positive triage

The repository's custom createRateLimitGuard preHandlers are the actual runtime
throttles for these routes. Since GitHub Advanced Security still did not honor
the attempted in-source suppressions, remove the temporary comments and keep the
source focused on runtime behavior while the false-positive alerts are tracked
operationally in GitHub.

Constraint: the custom Fastify preHandler remains the only real throttling mechanism in this repo
Rejected: leave non-functional suppression comments in source | they imply enforcement that GitHub did not actually honor
Confidence: high
Scope-risk: narrow
Directive: if CodeQL adds custom Fastify preHandler modeling later, prefer removing any alert dismissals over reintroducing comment noise here
Tested: Not run locally (comment-only cleanup)
Not-tested: GitHub checks after alert dismissal
2026-04-09 21:31:13 +08:00

18 KiB
Raw Blame History

🛠️ 管理 API

注意:以下内容适用于有一定技术能力的用户,否则请跳过此页面!!!

本文档说明如何直接调用 Metapi 管理后台的 /api/* 接口,用脚本完成站点与账号管理。

返回文档维护页

返回文档首页


适用范围

这组接口适合以下场景:

  • 初始化新实例,批量导入站点和账号
  • 用 Shell / Python / CI 脚本维护现有管理数据
  • 在外部系统里接入 Metapi 后台能力,而不是手工点页面

Important

本页介绍的是 管理 API,不是下游客户端调用的 /v1/* 代理接口。

  • 管理 API 使用 AUTH_TOKEN
  • 代理接口使用 PROXY_TOKEN

认证方式

所有受保护的管理接口都需要携带:

Authorization: Bearer <AUTH_TOKEN>

其中:

  • AUTH_TOKEN 就是你启动 Metapi 时配置的管理员令牌
  • 如果你后来在「设置」里修改过管理员令牌,脚本里也要同步改成新值
  • 管理端如果启用了 ADMIN_IP_ALLOWLIST,脚本调用方的来源 IP 也必须命中白名单

管理 API 不需要额外先调用“登录接口”;脚本直接带 AUTH_TOKEN 即可。

推荐先准备两个环境变量:

export METAPI_ADMIN_BASE_URL="http://127.0.0.1:4000"
# 或者
# export METAPI_ADMIN_BASE_URL="https://your-domain.com"
export METAPI_AUTH_TOKEN="your-admin-token"

之后所有请求都可以复用:

curl -sS "${METAPI_ADMIN_BASE_URL}/api/sites" \
  -H "Authorization: Bearer ${METAPI_AUTH_TOKEN}"

通用约定

项目 说明
接口前缀 管理接口统一在 /api/*
请求体 默认使用 application/json
认证方式 Authorization: Bearer <AUTH_TOKEN>
成功判定 除了看 HTTP 状态码,也要看响应体里的 success / error / message
业务失败返回 有些接口会返回 200,但响应体里是 success: false
常见错误码 400 参数错误、401/403 认证失败、409 资源冲突、429 触发限流

Tip

对脚本来说,最稳妥的做法是:

  1. 先检查 HTTP 状态码
  2. 再检查返回 JSON 里的 success、error、message
  3. 不要只靠 200 判断调用成功

常用脚本流程

脚本化添加一个新站点并绑定账号,通常按下面顺序:

  1. POST /api/sites/detect 自动识别平台
  2. 如命中官方预设,记下返回的 initializationPresetId
  3. POST /api/sites 创建站点
  4. POST /api/accounts/verify-token 验证凭证
  5. POST /api/accounts 保存账号

如果你要自动化的是 Codex / Claude / Gemini CLI / Antigravity 这类 provider 授权,则改看本文后面的 OAuth 接口,不要强行走普通账号导入。

最小流程示例:

# 1. 自动识别站点类型
curl -sS "${METAPI_ADMIN_BASE_URL}/api/sites/detect" \
  -H "Authorization: Bearer ${METAPI_AUTH_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://api.example.com"
  }'

# 2. 创建站点
curl -sS "${METAPI_ADMIN_BASE_URL}/api/sites" \
  -H "Authorization: Bearer ${METAPI_AUTH_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "My New API",
    "url": "https://api.example.com",
    "platform": "new-api"
  }'

# 3. 验证 API Key 或 Session Token
curl -sS "${METAPI_ADMIN_BASE_URL}/api/accounts/verify-token" \
  -H "Authorization: Bearer ${METAPI_AUTH_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "siteId": 1,
    "accessToken": "sk-or-session-token",
    "credentialMode": "apikey"
  }'

# 4. 保存账号
curl -sS "${METAPI_ADMIN_BASE_URL}/api/accounts" \
  -H "Authorization: Bearer ${METAPI_AUTH_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "siteId": 1,
    "accessToken": "sk-or-session-token",
    "credentialMode": "apikey"
  }'

站点接口

1. 获取站点列表

GET /api/sites

返回当前全部站点。响应除了站点自身字段,还会带上聚合出来的 totalBalance 和 subscriptionSummary。

示例:

curl -sS "${METAPI_ADMIN_BASE_URL}/api/sites" \
  -H "Authorization: Bearer ${METAPI_AUTH_TOKEN}"

示例响应:

[
  {
    "id": 1,
    "name": "My New API",
    "url": "https://api.example.com",
    "platform": "new-api",
    "status": "active",
    "useSystemProxy": false,
    "totalBalance": 42.5,
    "subscriptionSummary": null
  }
]

2. 自动识别站点类型

POST /api/sites/detect

用于在真正创建站点前,先根据 URL 探测平台类型;如果命中官方预设,还会顺带返回 initializationPresetId。

请求体:

字段 类型 必填 说明
url string 是 待识别的站点地址。通用平台通常传根地址;如果你想命中官方预设,则直接传预设对应的完整地址

示例:

curl -sS "${METAPI_ADMIN_BASE_URL}/api/sites/detect" \
  -H "Authorization: Bearer ${METAPI_AUTH_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://api.example.com"
  }'

成功响应:

{
  "url": "https://api.example.com",
  "platform": "new-api"
}

命中预设时,响应可能类似:

{
  "url": "https://coding.dashscope.aliyuncs.com/v1",
  "platform": "openai",
  "initializationPresetId": "codingplan-openai"
}

失败响应:

{
  "error": "Could not detect platform"
}

3. 创建站点

POST /api/sites

请求体字段:

字段 类型 必填 说明
name string 是 站点名称
url string 是 站点地址。通用平台通常用根地址;官方预设可直接保留完整官方路径
platform string 否 平台类型;留空时服务端会再次尝试自动识别
initializationPresetId string 否 官方预设 ID;用于保留预设语义、初始化建议和后续跳转
proxyUrl string | null 否 站点专用代理地址,支持 http(s) / socks
useSystemProxy boolean 否 是否使用全局 SYSTEM_PROXY_URL
customHeaders string | null 否 自定义请求头,注意这里传的是 JSON 字符串
externalCheckinUrl string | null 否 外部签到地址,需为 http(s)
status string 否 active 或 disabled
isPinned boolean 否 是否置顶
sortOrder number 否 排序值,非负整数
globalWeight number 否 站点全局权重,必须为正数
apiEndpoints array 否 仅用于 API 请求的数据面地址池;不替代主站点 URL

示例:

curl -sS "${METAPI_ADMIN_BASE_URL}/api/sites" \
  -H "Authorization: Bearer ${METAPI_AUTH_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Proxy Site",
    "url": "https://proxy-site.example.com",
    "platform": "new-api",
    "proxyUrl": "socks5://127.0.0.1:1080",
    "useSystemProxy": true,
    "customHeaders": "{\"cf-access-client-id\":\"site-client-id\",\"x-site-scope\":\"internal\"}",
    "externalCheckinUrl": "https://checkin.example.com/welfare",
    "globalWeight": 1.5
  }'

成功响应示例:

{
  "id": 1,
  "name": "Proxy Site",
  "url": "https://proxy-site.example.com",
  "platform": "new-api",
  "proxyUrl": "socks5://127.0.0.1:1080",
  "useSystemProxy": true,
  "customHeaders": "{\"cf-access-client-id\":\"site-client-id\",\"x-site-scope\":\"internal\"}",
  "externalCheckinUrl": "https://checkin.example.com/welfare",
  "status": "active",
  "globalWeight": 1.5
}

常见失败:

  • 409:同一个 (platform, url) 已存在
  • 400:proxyUrl、externalCheckinUrl、customHeaders、globalWeight 等字段格式不合法

Tip

customHeaders 不是对象,而是 JSON 字符串;并且 value 必须是字符串。

4. 使用官方预设创建站点

如果你前一步 detect 已经拿到了 initializationPresetId,推荐在创建时显式带回去:

curl -sS "${METAPI_ADMIN_BASE_URL}/api/sites" \
  -H "Authorization: Bearer ${METAPI_AUTH_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Aliyun CodingPlan",
    "url": "https://coding.dashscope.aliyuncs.com/v1",
    "platform": "openai",
    "initializationPresetId": "codingplan-openai"
  }'

这样响应里也会保留该预设信息,便于脚本按预设类型做后续分流。

账号接口

1. 获取账号列表

GET /api/accounts

返回账号快照对象,而不是裸数组。响应里包含:

  • generatedAt:快照生成时间
  • accounts:当前全部账号,以及关联站点、凭证模式、能力信息、今日花费、运行健康状态等聚合字段
  • sites:前端常用的站点列表快照

示例:

curl -sS "${METAPI_ADMIN_BASE_URL}/api/accounts" \
  -H "Authorization: Bearer ${METAPI_AUTH_TOKEN}"

示例响应:

{
  "generatedAt": "2026-04-09T06:00:00.000Z",
  "accounts": [
    {
      "id": 1,
      "siteId": 1,
      "username": "alice",
      "status": "active",
      "credentialMode": "apikey",
      "capabilities": {
        "canCheckin": false,
        "canRefreshBalance": false,
        "proxyOnly": true
      },
      "site": {
        "id": 1,
        "name": "My New API",
        "url": "https://api.example.com",
        "platform": "new-api"
      }
    }
  ],
  "sites": [
    {
      "id": 1,
      "name": "My New API",
      "url": "https://api.example.com",
      "platform": "new-api"
    }
  ]
}

2. 验证凭证

POST /api/accounts/verify-token

建议在真正保存账号前先调用一次。这样脚本可以在入库前拿到更明确的错误信息。

请求体字段:

字段 类型 必填 说明
siteId number 是 目标站点 ID
accessToken string 是 Session Token / Session Cookie / API Key
platformUserId number 否 某些 new-api / anyrouter 站点需要手动传
credentialMode string 否 auto / session / apikey,脚本里推荐显式传

验证 API Key

curl -sS "${METAPI_ADMIN_BASE_URL}/api/accounts/verify-token" \
  -H "Authorization: Bearer ${METAPI_AUTH_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "siteId": 1,
    "accessToken": "sk-fast-verify",
    "credentialMode": "apikey"
  }'

成功响应:

{
  "success": true,
  "tokenType": "apikey",
  "modelCount": 2,
  "models": ["gpt-5-mini", "gpt-4o-mini"]
}

验证 Session Token

curl -sS "${METAPI_ADMIN_BASE_URL}/api/accounts/verify-token" \
  -H "Authorization: Bearer ${METAPI_AUTH_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "siteId": 1,
    "accessToken": "session=xxxx",
    "credentialMode": "session"
  }'

成功响应:

{
  "success": true,
  "tokenType": "session",
  "userInfo": {
    "username": "alice"
  },
  "balance": 12.34,
  "apiToken": "sk-generated-by-upstream"
}

Tip

对 new-api / anyrouter 一类站点,如果返回 needsUserId: true,说明脚本里要补上 platformUserId 再重试。

3. 保存账号

POST /api/accounts

请求体字段:

字段 类型 必填 说明
siteId number 是 目标站点 ID
accessToken string 是 输入凭证;即使是 API Key 模式,也先放这里
username string 否 用户名;Session 模式下通常可由服务端自动识别
apiToken string 否 可显式指定要保存的 API Key;留空时服务端会自动推断
platformUserId number 否 平台用户 ID,New API 系站点常用
checkinEnabled boolean 否 Session 模式下是否开启签到
credentialMode string 否 auto / session / apikey
refreshToken string 否 sub2api 会话可附带刷新令牌
tokenExpiresAt number | string 否 sub2api 会话过期时间戳
skipModelFetch boolean 否 为 true 时可跳过即时模型拉取

保存 API Key 账号

curl -sS "${METAPI_ADMIN_BASE_URL}/api/accounts" \
  -H "Authorization: Bearer ${METAPI_AUTH_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "siteId": 1,
    "accessToken": "sk-proxy-only",
    "credentialMode": "apikey"
  }'

成功响应示例:

{
  "id": 1,
  "siteId": 1,
  "tokenType": "apikey",
  "credentialMode": "apikey",
  "capabilities": {
    "canCheckin": false,
    "canRefreshBalance": false,
    "proxyOnly": true
  },
  "modelCount": 1,
  "apiTokenFound": true,
  "queued": true,
  "jobId": "task_xxx",
  "message": "已添加为 API Key 账号,后台正在同步模型和路由信息。"
}

保存 Session 账号

curl -sS "${METAPI_ADMIN_BASE_URL}/api/accounts" \
  -H "Authorization: Bearer ${METAPI_AUTH_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "siteId": 1,
    "accessToken": "session=xxxx",
    "credentialMode": "session",
    "platformUserId": 1001,
    "checkinEnabled": true
  }'

常见失败:

  • 400 + requiresVerification: true:凭证还没有通过验证,建议先调用 /api/accounts/verify-token
  • 400:siteId 不存在、Token 为空、凭证类型与 credentialMode 不匹配

Tip

对纯脚本导入场景,建议始终显式传 credentialMode,不要依赖 auto 推断。

4. 重新绑定 Session Token

POST /api/accounts/:id/rebind-session

用于账号 Session 过期后,脚本直接把新 Session Token 重新写回账号。

请求体字段:

字段 类型 必填 说明
accessToken string 是 新的 Session Token
platformUserId number 否 覆盖或补充平台用户 ID
refreshToken string 否 sub2api 会话可附带
tokenExpiresAt number | string 否 sub2api 会话过期时间戳

示例:

curl -sS "${METAPI_ADMIN_BASE_URL}/api/accounts/1/rebind-session" \
  -H "Authorization: Bearer ${METAPI_AUTH_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "accessToken": "session=new-token",
    "platformUserId": 1001
  }'

成功响应示例:

{
  "success": true,
  "tokenType": "session",
  "credentialMode": "session",
  "apiTokenFound": true
}

OAuth 接口

如果你要自动化的是 Codex / Claude / Gemini CLI / Antigravity 这类 provider 授权,请使用下面这组接口。

1. 获取可用 provider

GET /api/oauth/providers

示例:

curl -sS "${METAPI_ADMIN_BASE_URL}/api/oauth/providers" \
  -H "Authorization: Bearer ${METAPI_AUTH_TOKEN}"

示例响应:

{
  "providers": [
    {
      "provider": "codex",
      "label": "Codex",
      "platform": "codex",
      "enabled": true,
      "loginType": "oauth",
      "requiresProjectId": false,
      "supportsDirectAccountRouting": true,
      "supportsCloudValidation": true,
      "supportsNativeProxy": true
    }
  ]
}

2. 启动 OAuth 授权

POST /api/oauth/providers/:provider/start

请求体字段:

字段 类型 必填 说明
projectId string 否 某些 provider 可选传入的项目 ID,例如 Gemini CLI
proxyUrl string | null 否 本次 OAuth 流程使用的显式代理;传 null 可清空继承代理

示例:

curl -sS "${METAPI_ADMIN_BASE_URL}/api/oauth/providers/codex/start" \
  -H "Authorization: Bearer ${METAPI_AUTH_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{}'

成功响应:

{
  "provider": "codex",
  "state": "oauth-state-123",
  "authorizationUrl": "https://auth.example.com/authorize?...",
  "instructions": {
    "redirectUri": "http://localhost:1455/auth/callback",
    "callbackPort": 1455,
    "callbackPath": "/auth/callback",
    "manualCallbackDelayMs": 15000
  }
}

3. 查询 OAuth 会话状态

GET /api/oauth/sessions/:state

示例:

curl -sS "${METAPI_ADMIN_BASE_URL}/api/oauth/sessions/oauth-state-123" \
  -H "Authorization: Bearer ${METAPI_AUTH_TOKEN}"

典型状态包括:

  • pending
  • success
  • error

4. 手动回填 callback URL

POST /api/oauth/sessions/:state/manual-callback

当浏览器已经完成授权,但回调没有自动打到 Metapi 时,可以手动提交最终 callback URL。

curl -sS "${METAPI_ADMIN_BASE_URL}/api/oauth/sessions/oauth-state-123/manual-callback" \
  -H "Authorization: Bearer ${METAPI_AUTH_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "callbackUrl": "http://localhost:1455/auth/callback?code=demo&state=oauth-state-123"
  }'

5. 列出已有 OAuth 连接

GET /api/oauth/connections

示例:

curl -sS "${METAPI_ADMIN_BASE_URL}/api/oauth/connections?limit=50&offset=0" \
  -H "Authorization: Bearer ${METAPI_AUTH_TOKEN}"

6. 重绑或删除 OAuth 连接

接口 作用
POST /api/oauth/connections/:accountId/rebind 为已有 OAuth 账号重新发起授权
DELETE /api/oauth/connections/:accountId 删除 OAuth 连接

常见失败返回

站点创建冲突

当同一个 (platform, url) 已存在时,会返回:

{
  "error": "A new-api site with URL https://api.example.com already exists."
}

需要补平台用户 ID

某些 new-api / anyrouter 站点会返回:

{
  "success": false,
  "needsUserId": true,
  "message": "This site requires a user ID. Please fill in your site user ID."
}

这时请在 /api/accounts/verify-token 或 /api/accounts 里补上 platformUserId。

平台用户 ID 不匹配

{
  "success": false,
  "invalidUserId": true,
  "message": "The provided user ID does not match this token. Please check your site user ID."
}

命中防护盾

{
  "success": false,
  "shieldBlocked": true,
  "message": "This site is shielded by anti-bot challenge. Create an API key on the target site and import that key."
}

这通常意味着账号密码或 Session 流程被防护页拦住了,更适合改用 API Key 模式。

验证过于频繁

POST /api/accounts/verify-token 对同一来源 IP 有速率限制。超过限制后会返回:

{
  "success": false,
  "message": "请求过于频繁,请稍后再试"
}

建议脚本在这类错误上做退避重试。

下一步

  • 上游接入 — 查看不同站点类型应该怎么填 URL、凭证和 User ID
  • OAuth 管理 — 查看浏览器授权、回调和重绑逻辑
  • 配置说明 — 查看 AUTH_TOKEN、ADMIN_IP_ALLOWLIST、系统代理等配置项
  • 客户端接入 — 如果你要接的是 /v1/* 代理接口而不是后台管理接口,请看这里