unified-generation-api 上新增 mint / revoke 两个接口。Mai 用它按 email 铸 sk_live_ key、用户 disable 时吊销。计费、定价、ignis 建账户都与这两个接口无关。
两个接口都挂在 unified-generation-api 上,只接受 master key。铸出来的是普通 sk_live_ 用户 key —— 跟 dashboard 早就在发的那种完全一样,落同一张 api_keys 表。Mai 派发任务前 get-or-create,disable 用户时 revoke。
sk_live_ key。token 明文只返一次。X-Worker-Auth-Key header。用户 key 调这俩 → 403。/generate + /task)和"Authorization: Bearer sk_live_… 自动注入 user_email"在 api-contract 那页。
传 email(+ 可选 source),返回一把新 key。token 明文仅此一次,丢了只能 revoke 重铸。
# 请求 POST /admin/keys X-Worker-Auth-Key: <master key> # 不是 Bearer,是这个 header Content-Type: application/json { "email": "user@x.com", "source": "ignis" } # 返回 201(token 明文只此一次) { "key_id": "key_1781602556958_1da36a3d", "token": "sk_live_lz8yUO3IEZXOv70f0tJxvj07NKG...", "email": "user@x.com", "source": "ignis", "expires_at": null # source=ignis 不过期 }
| 字段 | 位置 | 说明 |
|---|---|---|
| body · 必填 | 用户邮箱,自动 trim + 小写。格式非法返 400。 | |
| source | body · 可选 | Mai 传 "ignis"。ignis → 不过期;其它/缺省 → 90 天 TTL(可用 ttl_days 覆盖)。 |
| key_id | 返回 | 吊销时用它定位。形如 key_<ts>_<rand>。 |
| token | 返回 | sk_live_ 明文,只返一次,Mai 必须存下。 |
| expires_at | 返回 | ignis 返 null;其它返 "YYYY-MM-DD HH:MM:SS"(UTC)。 |
按 email 或 key_id 吊销(二选一)。幂等:已吊销/不存在再吊不报错,只是 revoked 计数为 0。
# 请求 —— 按 email(吊销该用户所有 active key) POST /admin/keys/revoke X-Worker-Auth-Key: <master key> Content-Type: application/json { "email": "user@x.com" } # 或 { "key_id": "key_..." } # 也支持 —— 按路径吊销单把 DELETE /admin/keys/key_1781602556958_1da36a3d X-Worker-Auth-Key: <master key> # 返回 200(幂等) { "ok": true, "revoked": 1 } # revoked = 本次实际吊销条数
| 入参 | 语义 |
|---|---|
把该 email 所有 active key 置为 revoked。Mai 清残留首选。 | |
| key_id | 只吊销这一把(POST body 或 DELETE 路径)。 |
| revoked | 返回:本次真正从 active→revoked 的行数。0 = 本来就没有 active 的。 |
email 和 key_id 都没传 → 400。两个都传时优先用 key_id。
复用已有的 api_keys 表,只新增了一列 source(migration 0032_add_source_to_api_keys.sql),用来区分"接口发放"还是"手动创建"。其余列、约束全没动。
api_keys 里的行都是手动在 dashboard 创建的。今后通过 POST /admin/keys 接口铸的 key 带上调用方传的 source(Mai 传 "ignis"),库里一眼能看出哪些是接口发的。历史手动 key 的 source 保持 NULL —— NULL 就代表"手动创建"。
-- migration 0032(additive + 幂等) ALTER TABLE api_keys ADD COLUMN IF NOT EXISTS source TEXT; -- 只对非 NULL 建部分索引(历史手动 key 都是 NULL,不进索引) CREATE INDEX IF NOT EXISTS idx_api_keys_source ON api_keys (source) WHERE source IS NOT NULL;
| 来源 | source 列 | 含义 |
|---|---|---|
| 历史 / dashboard 手动创建 | NULL | 本次迁移前就有的行,保持 NULL。 |
| 接口 mint,Mai 传 ignis | "ignis" | 通过 POST /admin/keys 铸,带 source。 |
| 接口 mint,不传 source | NULL | 接口可不传,落 NULL。 |
IF NOT EXISTS),不影响任何现有行为;verifyAuth / 鉴权路径完全没动。source 一起写入。id / user_email 把 status 改 revoked。expires_at 列是 NOT NULL,ignis key 在库里落超长哨兵 9999-12-31 满足约束,返回时按契约返 null。在 test worker 上端到端跑了 11 个场景,全过。
| # | 场景 | 预期 | 结果 |
|---|---|---|---|
| 1 | mint source=ignis | expires_at:null + token | ✓ |
| 2 | 新 key 调 /models | 200 | ✓ |
| 3 | 用户 key 调 mint | 403 | ✓ |
| 4 | revoke by email | {ok:true,revoked:1} | ✓ |
| 5 | 吊销后 key 调 /models | 401 | ✓ |
| 6 | 重复 revoke(幂等) | {ok:true,revoked:0} | ✓ |
| 7 | mint 默认 source | expires_at 90 天后 | ✓ |
| 8 | revoke by key_id | revoked:1 | ✓ |
| 9 | DELETE /admin/keys/{id} | revoked:1 | ✓ |
| 10 | 非法 email | 400 | ✓ |
| 11 | revoke 空 body | 400 | ✓ |
推荐流程,避免一个 email 攒出多把 active key。
POST /admin/keys/revoke {email} 清掉可能的残留,再 POST /admin/keys {email, source:"ignis"} 铸新的。POST /admin/keys/revoke {email},同时清掉本地表记录。X-Worker-Auth-Key header,不是 Authorization: Bearer。/generate 时,用户 token 走 Authorization: Bearer sk_live_…,会自动注入 user_email 做计费归属。