错误码
第三方授权 API 返回的全部错误码、对应的 HTTP 状态、含义与建议处理,以及 Swift SDK 的错误映射。
错误格式
所有业务错误都以 JSON 返回:
{ "error": "signature_invalid" }
错误码是稳定的机器可读字符串,请按错误码而不是 HTTP 状态或文案做判断。
注意 少数错误不是 JSON:请求体超过 400,000 字节返回
413;对POST /api/oauth/clients、POST /api/oauth/authorize、POST /api/oauth/token这类按固定结构解析的端点,JSON 语法错误、缺少必填字段或缺少Content-Type: application/json时,返回框架默认的400、415或422纯文本响应。签名端点(/api/oauth/requests…、/api/oauth/revoke)的请求体解析失败统一返回400 invalid_input。
签名与令牌
适用于全部签名调用(/api/oauth/account、/api/oauth/requests…、/api/oauth/vaults…)。
| HTTP | 错误码 | 含义 | 建议处理 |
|---|---|---|---|
| 401 | invalid_token | 缺少 Authorization,或 access token 不存在 | 用 refresh token 续期;仍失败则重新授权 |
| 401 | token_expired | access token 已过期(有效期 3600 秒) | 续期后重试一次 |
| 401 | grant_revoked | 授权已被用户撤销、被接入方撤销,或因 refresh token 复用整个令牌家族已失效 | 停止重试,清除本地凭据,引导用户重新授权 |
| 401 | client_disabled | client 已被平台运营方停用 | 停止调用;联系平台 |
| 403 | client_disabled | client 已被开发者停用 | 停止调用 |
| 401 | signature_required | 缺少 x-pm-timestamp、x-pm-nonce 或 x-pm-signature | 检查签名实现 |
| 401 | signature_invalid | 时间戳不是整数、nonce 长度不在 8–128、或签名验证失败 | 检查规范串:方法、不含查询串的路径、请求体原始字节的 SHA-256;签名后不要再改动请求体 |
| 401 | timestamp_out_of_window | 时间戳与服务器相差超过 300 秒 | 校准设备时钟后重试 |
| 401 | nonce_replayed | 同一授权内 nonce 被重复使用 | 每次请求生成新的随机 nonce |
登记 client
POST /api/oauth/clients、DELETE /api/oauth/clients/{id}。
| HTTP | 错误码 | 含义 | 建议处理 |
|---|---|---|---|
| 400 | invalid_input | name 为空或超过 100 个字符 | 修正名称 |
| 400 | invalid_client_type | client_type 不是 public 或 confidential | 修正类型 |
| 400 | invalid_redirect_uris | 回调地址数量不在 1–10,或某个地址不符合该类型的规则 | 见回调地址规则 |
| 400 | invalid_public_key | seal_public_key 不是 32 字节 base64url | 修正公钥 |
| 400 | invalid_platforms | platforms 不是数组、超过 10 项或含非法字符 | 使用小写字母、数字、_、- |
| 400 | invalid_brand | brand 不是对象,或颜色、图标地址、主页格式不对 | 颜色用 #RRGGBB,图标用 https |
| 400 | mode_retired | 提交了非空的 service_public_key(模式 2 已停用) | 去掉该字段 |
| 404 | client_not_found | 要停用的 client 不存在、不属于你或已停用 | — |
授权与同意
GET /api/oauth/authorize/info、POST /api/oauth/authorize(由 MimiKey App 调用)。在 MimiKey 中出现这些错误时,接入方通常收到回调 error=invalid_request。
| HTTP | 错误码 | 含义 | 建议处理 |
|---|---|---|---|
| 404 | client_not_found | client_id 不存在 | 核对 client_id |
| 403 | client_disabled | client 已被开发者或平台停用 | — |
| 400 | redirect_uri_mismatch | 回调地址不在登记列表中 | 使回调地址与登记值逐字一致 |
| 400 | invalid_approval_key | 审批公钥不是 32 字节 | 检查深链参数 approval_key |
| 400 | invalid_public_key | seal 公钥不是 32 字节 | 检查深链参数 seal_key |
| 400 | seal_key_required | public client 未提供 seal 公钥 | 提供 seal_key |
| 400 | code_challenge_required | public client 未提供 PKCE challenge | 提供 code_challenge |
| 400 | invalid_code_challenge | 方法不是 S256,或 challenge 不是 43–128 个 base64url 字符 | 修正 PKCE |
| 400 | invalid_policy | policy 不是 ask 或 trust | — |
| 400 | invalid_selections | 没有选择库、超过 32 个、重复或模式非法 | — |
| 400 | mode_retired | 选择了模式 2 | 只使用模式 1 |
| 403 | vault_not_owned | 选择了不归该用户所有的库 | — |
令牌
POST /api/oauth/token。
| HTTP | 错误码 | 含义 | 建议处理 |
|---|---|---|---|
| 400 | unsupported_grant_type | grant_type 不是 authorization_code 或 refresh_token | — |
| 400 | invalid_client | public client 携带了 client_secret | 去掉 client_secret |
| 401 | invalid_client_secret | client_id 不存在,或 confidential client 的 secret 不对 | 核对 client_id 与 secret |
| 403 | client_disabled | client 已停用 | — |
| 400 | invalid_code | 授权码不存在或已被使用 | 重新授权 |
| 400 | code_expired | 授权码超过 10 分钟 | 重新授权 |
| 400 | code_client_mismatch | 授权码不属于该 client | — |
| 400 | redirect_uri_mismatch | redirect_uri 与授权时不一致 | — |
| 400 | invalid_grant | PKCE 校验失败或缺少 code_verifier | 使用生成 challenge 时的同一个 verifier;授权码已作废,需重新授权 |
| 401 | invalid_refresh_token | refresh token 缺失、不存在或不属于该 client | 重新授权 |
| 401 | refresh_expired | refresh token 超过 30 天未使用 | 重新授权 |
| 401 | refresh_token_reused | 已轮换作废的 refresh token 被再次提交;该授权的全部令牌已被吊销 | 立即停止自动重试,清除本地凭据,引导用户重新授权;检查是否有并发续期或令牌泄露 |
| 401 | grant_revoked | 授权已撤销 | 重新授权 |
撤销
POST /api/oauth/revoke。未知令牌返回 { "ok": true },不报错。
| HTTP | 错误码 | 含义 | 建议处理 |
|---|---|---|---|
| 400 | invalid_input | 请求体不是合法 JSON 或缺少字段 | — |
| 400 | invalid_client | public client 携带了 client_secret | 去掉 client_secret,改用签名 |
| 401 | invalid_client_secret | client_id 不存在,或 confidential client 的 secret 不对 | — |
| 401 | signature_required 等 | public client 撤销有效令牌时缺少或带错签名 | 用该授权的审批私钥签名 |
请求
POST /api/oauth/requests、PUT /api/oauth/requests/{id}/patch、GET /api/oauth/requests/{id}、POST /api/oauth/requests/{id}/cancel。
| HTTP | 错误码 | 含义 | 建议处理 |
|---|---|---|---|
| 400 | invalid_input | 请求体不是合法 JSON、hint 不是对象,或 update_item 创建时携带了 patch_enc | 修正请求体 |
| 400 | invalid_op | op 不是支持的操作 | — |
| 400 | item_id_required | get_item 或 update_item 缺少 item_id | — |
| 400 | unsupported_kind | create_item 的 kind 不在六种类型之内 | — |
| 400 | fields_required | update_item 缺少 fields | — |
| 400 | invalid_fields | fields 为空、超过 32 个或字段名不合法 | 字段名用小写字母、数字、_,以字母开头 |
| 400 | invalid_patch | patch_enc 缺失、超过 64 KiB 或不是合法的 sealed box | 检查密封实现 |
| 403 | vault_not_granted | 目标库不在本授权内(pick_item 跨库时本授权没有任何库) | 只使用授权返回的库 |
| 403 | mode_not_granted | 目标库在授权内,但不是该操作需要的模式 | — |
| 404 | request_not_found | 请求不存在或不属于本授权 | — |
| 409 | request_not_draft | 上传补丁时请求已不是草稿:草稿超过 60 秒已过期、补丁已上传,或该请求不是 update_item | 重新创建 update_item,在 60 秒内上传补丁 |
| 409 | request_not_pending | 取消时请求已结束 | 以 GET 查询最终状态 |
| 429 | too_many_requests | 本授权未结束的请求(含草稿)已达 100 个 | 取消不再需要的请求,或等待过期 |
设备端与用户端
由 MimiKey App 使用,接入方一般不会遇到。
| HTTP | 错误码 | 含义 |
|---|---|---|
| 401 | locked | 账号会话不存在或已过期 |
| 401 | device_revoked_or_rotated | 会话所属设备已被撤销或换密 |
| 403 | untrusted_origin | 请求的 Origin 与服务地址不符 |
| 404 | grant_not_found | 授权不存在或不属于当前用户 |
| 409 | request_not_pending | 处理时请求已结束;或对选条目、新建、修改请求提交了 approve |
| 409 | request_not_approved | 上传读取结果时请求尚未批准 |
运营端
| HTTP | 错误码 | 含义 |
|---|---|---|
| 401 | admin_locked | 缺少或无效的管理员会话 |
| 400 | reason_required | 停用或启用时没有填写原因(1–500 字符) |
| 404 | client_not_found | client 不存在 |
服务器错误
| HTTP | 错误码 | 含义 | 建议处理 |
|---|---|---|---|
| 500 | database_error | 服务端存储暂时不可用 | 指数退避后重试 |
已停用的错误码
以下错误码属于模式 2,在 v3 中由于 mode_retired 先行拦截,正常情况下不会再出现:service_key_missing、wrap_missing。
回调错误
MimiKey 跳回你的回调地址时可能带有 error 参数:
| 错误 | 场景 | 建议处理 |
|---|---|---|
access_denied | 用户在同意卡上点了「拒绝」 | 尊重用户的决定 |
not_signed_in | MimiKey 尚未登录账号 | 提示用户先登录 MimiKey |
invalid_request | 深链参数不合法、client 不存在或已停用、回调地址未登记 | 核对 client_id 与回调地址 |
request_not_found | 请求不存在或不属于当前 MimiKey 账号 | 检查用户是否登录了授权时的同一个账号 |
请求类回调还会带 status:completed、denied、cancelled、expired,它们是请求的结果而不是错误,见回调状态汇总。
SDK 错误映射
Swift SDK 把服务器错误码、请求状态与回调统一映射为 MimiKeyError:
MimiKeyError | 来源 |
|---|---|
.notInstalled(downloadURL:) | 无法打开 mimikey://(未安装,或 iOS 未在 LSApplicationQueriesSchemes 中声明 mimikey) |
.notAuthorized | 本机钥匙串中没有授权 |
.cancelled | 调用了 cancelPending() 或取消了 Task;请求状态为 cancelled |
.denied | 回调 error=access_denied;请求状态为 denied |
.conflict | 请求状态为 denied 且 deny_reason 为 conflict |
.expired | 请求状态为 expired;超过 expires_at 15 秒仍无结果;授权等待超过 authorizeTimeout;连续 150 次轮询网络失败 |
.notSignedIn | 回调 error=not_signed_in |
.revoked | grant_revoked、client_disabled、refresh_token_reused、invalid_refresh_token、refresh_expired、invalid_client、invalid_client_secret,以及续期后仍为 invalid_token。SDK 会清空本机凭据 |
.network(Error) | URLSession 失败(等待期间的轮询失败会自动重试) |
.protocol(String) | 其他服务器错误码(如 vault_not_granted、invalid_grant)、其他回调错误(如 invalid_request、request_not_found),以及下表的 SDK 内部错误 |
token_expired 与 invalid_token 不会直接抛出:SDK 先用 refresh token 续期并重试一次。
SDK 内部的 .protocol 错误码:
| 错误码 | 含义 |
|---|---|
nothing_pending | 调用 retryOpen() 时没有正在等待的流程 |
invalid_callback | 授权回调中没有授权码 |
no_granted_vault | createItem 未指定库,且本授权没有可用的库 |
empty_patch | updateItem 的 patch 为空 |
updated_item_missing | 修改完成但服务器没有返回 updated_item |
result_missing | 请求完成但没有 result_enc |
result_open_failed | 无法用本机 seal 私钥打开结果(密钥或 AAD 不匹配) |
result_malformed | 结果明文不是预期的结构 |
malformed_response | 服务器响应不是预期的 JSON |
http_<状态码> | 非 2xx 响应且没有 JSON 错误体 |