API 参考
第三方授权 v3 的 REST 端点:client 登记、授权、令牌、签名调用、请求生命周期、撤销与运营端停用。
基础约定
| 项 | 约定 |
|---|---|
| 服务地址 | https://password.tminos.com |
| 格式 | 请求与响应均为 JSON(Content-Type: application/json),UTF-8 |
| 时间 | UTC,ISO 8601,精确到秒,例如 2026-10-23T08:30:00Z |
| 二进制值 | base64url,无填充 |
| ID | client_id 以 pmc_ 开头;grant_id、request_id 为 UUID |
| 错误 | { "error": "<code>" },见错误码 |
| 请求体上限 | 400,000 字节 |
使用 Swift SDK 时无需直接调用这些端点。本页面向需要自行实现协议或排查问题的开发者。
认证方式
| 方式 | 用于 | 凭据 |
|---|---|---|
| 账号会话 | 登记 client、同意授权、设备端与用户端管理 | Authorization: Bearer <会话令牌>,由 SRP 登录获得,30 分钟有效 |
| client 认证 | POST /api/oauth/token、POST /api/oauth/revoke | public:PKCE verifier(换 token)或审批钥签名(撤销);confidential:client_secret |
| 签名调用 | 全部第三方数据端点 | Authorization: Bearer <access_token> 加审批钥签名,见签名 |
签名
除换 token 外,所有第三方数据调用都必须同时携带 access token 与该授权审批私钥的签名。public client 的撤销请求也需要签名。
| 请求头 | 值 |
|---|---|
Authorization | Bearer <access_token> |
x-pm-timestamp | 当前 Unix 时间(秒) |
x-pm-nonce | 8–128 个字符的随机串,同一授权内不得重复 |
x-pm-signature | 对规范串的 Ed25519 签名,base64url |
规范串(UTF-8,各段以 \n 连接,末尾无换行):
{METHOD}\n{PATH}\n{TIMESTAMP}\n{NONCE}\n{SHA256_HEX(BODY)}
METHOD:大写的 HTTP 方法。PATH:不含查询串的路径,例如/api/oauth/requests/3f6b…/patch。BODY:实际发送的请求体字节。签名后不得再改动请求体(包括重新序列化)。无请求体时取空串,其 SHA-256 为e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855。- 时间戳与服务器时间相差不得超过 ±300 秒。
- nonce 在同一授权内 10 分钟内只能使用一次;即使签名校验失败,已提交的 nonce 也视为已使用。
GET /api/oauth/account HTTP/1.1
Host: password.tminos.com
Authorization: Bearer 3q2-7wEXAMPLEACCESSTOKEN
x-pm-timestamp: 1761177600
x-pm-nonce: n-0123456789abcdef
x-pm-signature: KngAgL1nChfn4NugEnlNlesKA8EtmrhpsL1GU9pU_Z0AcwkQRzP9iasdaFfe3nEVcvCGUT92eWHzzZCEX849DQ
上例签名可用测试向量中的公开测试密钥复现。
签名失败的错误码:signature_required(缺请求头)、signature_invalid(格式或签名不对)、timestamp_out_of_window、nonce_replayed。
登记 client
POST /api/oauth/clients
登记一个接入应用。认证:账号会话。MimiKey for Mac 的「开发者接入」页面调用的就是这个端点。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
name | string | 是 | 1–100 个字符 |
description | string | 否 | 用途说明 |
client_type | string | 否 | public 或 confidential,缺省 confidential |
platforms | string[] | 否 | 最多 10 个,每个 1–32 个小写字母、数字、_ 或 -;仅用于展示 |
redirect_uris | string[] | 是 | 1–10 个,规则见回调地址规则 |
brand | object | 否 | color(#RRGGBB)、logo_url(https,不超过 500 字符)、homepage;其他键忽略 |
seal_public_key | string | 否 | 仅 confidential:32 字节 X25519 公钥。public client 的 seal 公钥在每次授权时上送 |
service_public_key | null | 否 | 已停用,非空时返回 400 mode_retired |
POST /api/oauth/clients HTTP/1.1
Authorization: Bearer <会话令牌>
Content-Type: application/json
{
"name": "我的 AI 助手",
"description": "帮我登录网站完成对账",
"client_type": "public",
"platforms": ["ios", "macos"],
"redirect_uris": ["com.example.ai://mimikey/callback"],
"brand": { "color": "#1d4ed8", "logo_url": "https://cdn.example.com/logo.png", "homepage": "https://example.com" }
}
{
"client_id": "pmc_Xk3vQ9pL2mN8rT4w",
"client_type": "public",
"name": "我的 AI 助手",
"platforms": ["ios", "macos"],
"brand": { "color": "#1D4ED8", "logo_url": "https://cdn.example.com/logo.png", "homepage": "https://example.com" }
}
confidential client 的响应另含 client_secret,只返回这一次;服务器只保存其哈希。
错误:invalid_input、invalid_client_type、invalid_redirect_uris、invalid_public_key、invalid_platforms、invalid_brand、mode_retired。
GET /api/oauth/clients
列出当前账号登记的 client。认证:账号会话。
{
"clients": [
{
"client_id": "pmc_Xk3vQ9pL2mN8rT4w",
"name": "我的 AI 助手",
"description": "帮我登录网站完成对账",
"client_type": "public",
"redirect_uris": ["com.example.ai://mimikey/callback"],
"platforms": ["ios", "macos"],
"brand": { "color": "#1D4ED8" },
"has_seal_key": false,
"has_service_key": false,
"disabled": false,
"platform_disabled": false,
"active_grants": 3,
"created_at": "2026-10-23T08:00:00Z"
}
]
}
disabled 表示开发者已停用,platform_disabled 表示被平台运营方停用。
DELETE /api/oauth/clients/{id}
停用一个 client。认证:账号会话。该 client 的全部授权立即撤销、令牌失效、未完成请求过期,不可恢复。响应 { "ok": true };不存在或已停用时返回 404 client_not_found。
授权(MimiKey App 侧)
以下端点由 MimiKey App 用用户的账号会话调用,接入方不需要实现,列出供理解协议。
深链
SDK 打开以下深链启动 MimiKey:
| 用途 | URL |
|---|---|
| 授权 | mimikey://authorize?client_id=…&redirect_uri=…&state=…&code_challenge=…&code_challenge_method=S256&approval_key=…&seal_key=… |
| 处理请求 | mimikey://request?id=…&client_id=…&redirect_uri=…&state=… |
| 参数 | 说明 |
|---|---|
client_id | 登记得到的 ID |
redirect_uri | 登记过的回调地址,逐字匹配 |
state | 至少 16 字节的随机串;回调中原样返回,不符时 SDK 必须丢弃回调 |
code_challenge | base64url(SHA-256(code_verifier)),43–128 个字符 |
approval_key | 本次授权的审批公钥(Ed25519,32 字节) |
seal_key | 本次授权的 seal 公钥(X25519,32 字节) |
id | 请求 ID |
回调参数见回调状态汇总。
GET /api/oauth/authorize/info
同意卡所需的数据。查询参数:client_id。
{
"client": {
"id": "pmc_Xk3vQ9pL2mN8rT4w",
"name": "我的 AI 助手",
"description": "帮我登录网站完成对账",
"client_type": "public",
"brand": { "color": "#1D4ED8", "logo_url": "https://cdn.example.com/logo.png", "homepage": "https://example.com" },
"platforms": ["ios", "macos"]
},
"vaults": [
{ "vault_id": "8d4c…", "name": null, "kind": "personal", "default": true },
{ "vault_id": "b2a1…", "name": "家庭", "kind": "shared", "default": false }
]
}
只返回该用户拥有的库。为兼容 v2,client 中还包含 client_id、seal_public_key、service_public_key。错误:404 client_not_found、403 client_disabled。
POST /api/oauth/authorize
记录用户的同意并签发授权码。
| 字段 | 必填 | 说明 |
|---|---|---|
client_id | 是 | |
redirect_uri | 是 | 必须是登记值之一 |
state | 否 | 原样返回 |
approval_key_public | 是 | 32 字节 Ed25519 公钥 |
seal_public_key | public 必填 | 32 字节 X25519 公钥;confidential 缺省时使用登记时的 seal 公钥 |
code_challenge | public 必填 | 43–128 个 base64url 字符 |
code_challenge_method | 随 challenge | 只支持 S256 |
policy | 是 | ask 或 trust |
selections | 是 | 1–32 项 { "vault_id": "…", "mode": 1 };mode: 2 返回 400 mode_retired |
响应:{ "code": "…", "state": "…" }。授权码 10 分钟有效、只能使用一次。
令牌
POST /api/oauth/token
不需要会话。public client 不得携带 client_secret(携带时返回 400 invalid_client)。
授权码换令牌:
| 字段 | 必填 | 说明 |
|---|---|---|
grant_type | 否 | authorization_code(缺省) |
code | 是 | 回调中的授权码 |
client_id | 是 | |
redirect_uri | 是 | 与授权时一致 |
code_verifier | public 必填 | 43–128 个字符,[A-Za-z0-9-._~] |
client_secret | confidential 必填 |
POST /api/oauth/token HTTP/1.1
Content-Type: application/json
{
"grant_type": "authorization_code",
"code": "q8V0…",
"client_id": "pmc_Xk3vQ9pL2mN8rT4w",
"redirect_uri": "com.example.ai://mimikey/callback",
"code_verifier": "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
}
{
"access_token": "3q2-7wEXAMPLEACCESSTOKEN",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "Zp9-EXAMPLEREFRESHTOKEN",
"refresh_expires_in": 2592000,
"grant_id": "6f1d2c3b-4a59-4e68-8b7c-9d0e1f2a3b4c",
"account_id": "0b7e…",
"vaults": [{ "vault_id": "8d4c…", "mode": 1 }],
"client_type": "public",
"account_identity_public_key": "9Xwg4YSHxLN9dr2JSRYL_wfRh_0iafGiNzQtT5blNys"
}
注意 授权码在第一次兑换尝试时即被消耗。PKCE 校验失败(
invalid_grant)、回调地址不符等错误之后,同一授权码不能再用,需要重新授权。
续期:
| 字段 | 必填 | 说明 |
|---|---|---|
grant_type | 是 | refresh_token |
refresh_token | 是 | |
client_id | 是 | |
client_secret | confidential 必填 |
续期响应与上例相同,但不含 vaults(授权范围不会因续期改变)。
- 轮换: 每次续期都会作废所用的 refresh token 并签发新的;新 refresh token 自本次续期起 30 天有效。
- 复用检测: 已作废的 refresh token 再次出现时返回
401 refresh_token_reused,并立即吊销该授权的全部令牌。收到后必须停止自动重试,引导用户重新授权。 - 续期不需要签名;但续期得到的 access token 仍只能配合审批钥签名使用。
| 响应字段 | 说明 |
|---|---|
access_token | 访问令牌,3600 秒有效 |
refresh_token | 续期令牌,30 天滑动有效 |
grant_id | 授权 ID,参与结果与补丁的 AAD |
account_id | 授权用户的账号 ID |
vaults | 授权的库(仅授权码兑换时返回) |
client_type | public 或 confidential |
account_identity_public_key | 账号身份公钥(X25519),用于密封修改补丁;账号尚未生成身份密钥时为 null |
撤销
POST /api/oauth/revoke
撤销某个令牌所属的授权。幂等:未知令牌也返回 { "ok": true }。
| client 类型 | 请求体 | 认证 |
|---|---|---|
| public | { "token": "<access_token>", "client_id": "…" } | 用该授权的审批私钥签名(请求头同签名) |
| confidential | { "token": "…", "client_id": "…", "client_secret": "…" } | client_secret |
撤销后,该授权的 access token 与 refresh token 立即失效,未完成的请求全部置为 expired。
账号
GET /api/oauth/account
签名调用。返回授权用户的账号身份公钥与本授权的库。
{
"account_id": "0b7e…",
"identity_public_key": "9Xwg4YSHxLN9dr2JSRYL_wfRh_0iafGiNzQtT5blNys",
"vaults": [
{ "vault_id": "8d4c…", "name": null, "kind": "personal", "mode": 1 }
]
}
identity_public_key 在账号尚未生成身份密钥时可能为 null,此时无法发起 update_item。
请求
请求对象
POST /api/oauth/requests、PUT …/patch 与 GET /api/oauth/requests/{id} 返回同一种对象(创建时只含部分字段):
| 字段 | 说明 |
|---|---|
request_id | 请求 ID |
state | draft、pending、approved、completed、denied、expired、cancelled |
op | 操作类型(仅 GET) |
vault_id | 目标库;pick_item 跨库时为 null(仅 GET) |
item_id | 目标条目(仅 GET) |
created_at、expires_at、state_changed_at | 时间 |
waiting_on、waiting_reason | 当前轮到谁,见下表 |
existing | create_item 去重命中已有请求时为 true(仅创建) |
deny_reason | denied 时设备给出的原因,例如 conflict(可能缺省) |
result_enc | completed 时的密封结果(pick_item、get_item、list_items、create_item) |
picked_item | pick_item 完成时:{ id, title, url, kind } |
created_item | create_item 完成时:{ id, title, url, kind } |
saved_out_of_scope | create_item 存到授权范围外时为 true,此时没有 result_enc 和 created_item |
updated_item | update_item 完成时:{ id, revision },没有 result_enc |
状态与等待
| 状态 | waiting_on | waiting_reason | 含义 |
|---|---|---|---|
draft | client | patch | update_item 等你上传补丁 |
pending(get_item / list_items,ask) | user | approval | 等用户批准 |
pending(pick_item) | user | pick | 等用户选条目 |
pending(create_item) | user | input | 等用户在安全卡上填写 |
pending(update_item) | user | review | 等用户确认修改 |
approved | device | no_unlocked_device | 已批准,等任一已解锁的设备执行 |
completed / denied / expired / cancelled | null | null | 已结束 |
选条目、新建与修改请求没有 approved 中间状态:卡上的操作本身就是批准。服务器在读取时惰性判定过期:超过 expires_at 的未结束请求会显示为 expired。
POST /api/oauth/requests
签名调用。创建一个请求。
| 通用字段 | 说明 |
|---|---|
op | pick_item、get_item、create_item、update_item、list_items |
vault_id | 目标库,必须在本授权内且为模式 1;pick_item 可为 null |
reason | 可选,显示在卡上,最多 500 个字符(超出部分截断),不得包含凭据 |
每个授权最多同时有 100 个未结束的请求(含草稿),超过时返回 429 too_many_requests。
pick_item
| 字段 | 说明 |
|---|---|
vault_id | null 或省略:在本授权的全部库中挑选 |
hint | 可选,{ "url", "title", "kind" },只影响候选排序 |
{ "op": "pick_item", "vault_id": null, "hint": { "url": "https://bank.example.com", "kind": "login" }, "reason": "登录示例银行完成对账" }
{ "request_id": "a1b2c3d4-…", "state": "pending", "waiting_on": "user", "waiting_reason": "pick", "expires_at": "2026-10-23T08:40:00Z", "state_changed_at": "2026-10-23T08:30:00Z" }
有效期 10 分钟。在 trust 策略下也需要用户选择。
get_item
| 字段 | 说明 |
|---|---|
vault_id | 必填 |
item_id | 必填 |
有效期 10 分钟。ask 策略下初始为 pending,trust 策略下初始为 approved。
create_item
| 字段 | 说明 |
|---|---|
vault_id | 必填,默认保存位置 |
kind | login(缺省)、credit_card、bank_card、api_key、server、secure_note |
prefill | 可选,{ "title", "url", "username_hint", "extra_hint": { … } };也接受 v2 的字段名 site |
{ "op": "create_item", "vault_id": "8d4c…", "kind": "login", "prefill": { "title": "示例银行", "url": "https://bank.example.com/login", "username_hint": "me@example.com" }, "reason": "登录示例银行完成对账" }
有效期 30 分钟,初始恒为 pending。同一授权、同一个库中已有网址主机名相同的待处理新建请求时,返回该请求并带 "existing": true。
update_item
| 字段 | 说明 |
|---|---|
vault_id | 必填 |
item_id | 必填 |
fields | 必填,要修改的字段名(1–32 个,每个匹配 ^[a-z][a-z0-9_]*$,最长 64),例如 ["password"];服务器可见,用于卡上预告 |
创建时不得携带 patch_enc。请求以 draft 状态创建(有效期 60 秒,不出现在用户设备上),随后用 PUT …/patch 上传补丁。
{ "op": "update_item", "vault_id": "8d4c…", "item_id": "3f6b8f7e-…", "fields": ["password"], "reason": "已在网站上更换密码,同步到 MimiKey" }
{ "request_id": "a1b2c3d4-…", "state": "draft", "waiting_on": "client", "waiting_reason": "patch", "expires_at": "2026-10-23T08:31:00Z", "state_changed_at": "2026-10-23T08:30:00Z" }
list_items
| 字段 | 说明 |
|---|---|
vault_id | 必填 |
结果只含元数据(不含密码与备注)。Swift SDK 不提供此操作。
上传修改补丁
PUT /api/oauth/requests/{id}/patch
签名调用。为 draft 状态的 update_item 请求上传密封补丁,请求随即变为 pending,有效期重置为 30 分钟。
| 字段 | 说明 |
|---|---|
patch_enc | sealed box JSON 字符串,不超过 64 KiB |
补丁的构造:
- 收件人:
account_identity_public_key(或GET /api/oauth/account的identity_public_key) - AAD:
password-manager/oauth-patch/v1/{grant_id}/{request_id} - 明文:
{"patch":{"password":"新值"}},只能包含fields中声明的字段
{ "patch_enc": "{\"format\":1,\"epk\":\"PHld72bS…\",\"envelope\":{\"format\":2,\"nonce\":\"8Ovm4dzX…\",\"ciphertext\":\"9eSfVW9L…\"}}" }
响应为请求对象,state 为 pending、waiting_reason 为 review。错误:invalid_patch(格式不对或过大)、request_not_found、409 request_not_draft(不是草稿、已过期或不是 update_item)。
GET /api/oauth/requests/{id}
签名调用。查询请求;完成后携带结果。建议每 2 秒轮询一次,并在 App 回到前台时立即查询。
{
"request_id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
"state": "completed",
"op": "pick_item",
"vault_id": null,
"item_id": null,
"created_at": "2026-10-23T08:30:00Z",
"expires_at": "2026-10-23T08:40:00Z",
"state_changed_at": "2026-10-23T08:31:12Z",
"waiting_on": null,
"waiting_reason": null,
"result_enc": "{\"format\":1,\"epk\":\"FiF4WQXs…\",\"envelope\":{…}}",
"picked_item": { "id": "3f6b8f7e-…", "title": "示例银行", "url": "https://bank.example.com/login", "kind": "login" }
}
用 seal 私钥、AAD password-manager/oauth-result/v1/{grant_id}/{request_id} 打开 result_enc:
{
"item": {
"id": "3f6b8f7e-9c1d-4e2a-8b3c-4d5e6f7a8b9c",
"revision": 4,
"record": {
"title": "示例银行",
"username": "me@example.com",
"password": "correct horse battery staple",
"url": "https://bank.example.com/login",
"notes": "",
"kind": "login",
"extra": {},
"updated_at": "2026-10-23T08:00:00.000Z"
}
}
}
list_items 的结果为 { "v": 2, "items": [{ "id", "meta": { "title", "username", "url", "kind", "updated_at" } }] }。
POST /api/oauth/requests/{id}/cancel
签名调用,无请求体。取消一个未结束的请求(draft、pending 或 approved)。响应:{ "request_id": "…", "state": "cancelled" }。已结束的请求返回 409 request_not_pending。
旧模式 2 端点
以下端点仅为既有的模式 2 授权保留只读兼容,新授权无法使用:
| 端点 | 说明 |
|---|---|
GET /api/oauth/vaults | 签名调用;列出本授权的库与模式 |
GET /api/oauth/vaults/{vault_id}/items | 签名调用;返回库钥包裹与该库全部条目密文。模式 1 的库返回 403 mode_not_granted |
设备端端点
以下端点由 MimiKey App 用用户的账号会话调用,接入方不会用到:
| 端点 | 说明 |
|---|---|
GET /api/oauth/device/requests | 待本账号处理的请求(不含草稿) |
GET /api/oauth/device/requests/{id} | 单个请求(深链出卡用) |
POST /api/oauth/device/requests/{id}/decision | { "decision": "approve" 或 "deny", "reason": "conflict" };卡类请求(选条目、新建、修改)只能拒绝 |
POST /api/oauth/device/requests/{id}/result | 上传 result_enc 及回执 picked_item、created_item、updated_item 或 saved_out_of_scope |
用户授权管理
| 端点 | 说明 |
|---|---|
GET /api/oauth/grants | 当前用户的授权列表 |
GET /api/oauth/grants/{id}/history | 授权的使用历史与近 30 天统计 |
POST /api/oauth/grants/{id}/policy | { "policy": "ask" 或 "trust" },只影响之后的新请求 |
DELETE /api/oauth/grants/{id} | 撤销授权,立即生效 |
运营端:停用接入方
仅平台运营方可用,认证为独立的管理员会话(与用户会话隔离)。
| 端点 | 说明 |
|---|---|
GET /api/admin/oauth/clients?query=&page=&page_size= | 分页列出全部 client(按 ID、名称、所有者邮箱搜索),含授权数、近 30 天请求数 |
GET /api/admin/oauth/clients/{id} | 明细、近 30 天按操作与状态的计数、最近 50 个请求(不含条目内容与结果) |
POST /api/admin/oauth/clients/{id}/disable | { "reason": "…" }(必填,1–500 字符) |
POST /api/admin/oauth/clients/{id}/enable | { "reason": "…" } |
停用的效果:
- 该 client 的全部授权撤销,全部令牌失效,未完成的请求全部置为
expired; - 之后的签名调用返回
401 client_disabled; authorize/info与authorize返回403 client_disabled,MimiKey App 拒绝该 client 的深链;- 操作写入管理审计日志。
enable 只解除停用标记,不会恢复已撤销的授权;用户需要重新授权。