Error Conventions / 错误约定
中文
通用原则
当前 API 没有单独的全局 error schema package,而是由几类 handler 约定共同形成:
- 大多数业务 handler 使用
respondError,返回统一 envelope。
- 一部分 internal / agent / GORM 简单 handler 直接
c.JSON,只返回 error 或 error + message。
- 因此文档层面必须区分“稳定错误结构”和“简化错误结构”,不能假设所有接口完全一致。
标准错误 envelope
大多数认证、账户、管理员、XWorkmate、计费接口的失败响应是:
{"error":"code","message":"human readable message"}
字段含义:
| 字段 |
含义 |
error |
机器可读错误码,通常稳定,适合前端做分支。 |
message |
面向人类的错误描述。 |
扩展错误 envelope
少数接口会在标准形状上附加字段,例如:
| 场景 |
额外字段 |
| MFA 锁定 |
retryAt、mfaToken |
| Admin settings 乐观并发冲突 |
version、matrix |
| Sandbox binding / GORM 错误 |
有时直接返回底层 err.Error() 作为 message |
/api/ping |
不走错误 envelope;是纯成功读取接口 |
简化错误 envelope
以下 handler 家族经常直接返回简化结构:
{"error":"some_code"}
或
{"error":"some_code","message":"..."}
典型来源:
| 文件 |
典型接口 |
api/agent_server.go |
/api/agent-server/v1/users、/status |
api/user_agents.go |
/api/agent-server/v1/nodes、legacy /api/agent/nodes |
api/internal_sandbox_guest.go |
/api/internal/sandbox/guest |
api/admin_sandbox.go |
/api/auth/admin/sandbox/*、/api/admin/sandbox/* |
internal/auth/middleware.go |
JWT / internal-service middleware 直接中断请求时 |
错误来源分层
| 来源层 |
典型错误码 / 文本 |
说明 |
| 请求绑定与参数校验 |
invalid_request、missing_credentials、invalid_email、password_too_short |
JSON body 缺字段、query 中带敏感参数、格式不对。 |
| Session / JWT / internal token |
session_token_required、invalid_session、missing authorization header、invalid or expired token、missing service token |
认证前置失败。 |
| 角色 / 权限 / 账户状态 |
forbidden、root_only、account_suspended、read_only_account、root_email_enforced |
用户存在,但当前身份不允许执行操作。 |
| 业务状态 |
email_already_exists、subscription_not_found、mfa_not_enabled、policy_not_found |
领域对象状态不满足当前请求。 |
| 外部系统或后台依赖 |
verification_failed、xworkmate_secret_write_failed、stripe_cancel_failed、collector_status_unavailable |
SMTP、Vault、Stripe、DB、Xray render 等依赖失败。 |
高频错误码索引
认证与会话
| 错误码 |
常见状态码 |
来源 |
含义 |
credentials_in_query |
400 |
api/api.go |
登录或敏感接口不允许把凭据放在 query 中。 |
missing_credentials |
400 |
api/api.go |
登录、注册缺少关键字段。 |
user_not_found |
404 |
api/api.go、api/user_agents.go |
通过 identifier 或 session userID 找不到用户。 |
invalid_credentials |
401 |
api/api.go |
密码校验失败。 |
email_not_verified |
401 |
api/api.go |
邮箱尚未验证,禁止登录或 OAuth 登录。 |
session_token_required |
401 |
api/api.go、api/xworkmate.go、api/admin_users_metrics.go |
需要 session token。 |
invalid_session |
401 |
多个 session-based handler |
session 不存在、过期或无法匹配。 |
session_user_lookup_failed |
500 |
多个 handler |
session 存在,但无法回查用户。 |
MFA
| 错误码 |
常见状态码 |
来源 |
含义 |
mfa_ticket_required |
400 |
verifyMFALogin |
登录挑战完成接口缺少 ticket。 |
mfa_code_required |
400 |
verifyMFALogin、verifyTOTP |
缺少 TOTP 验证码。 |
invalid_mfa_ticket |
401 |
verifyMFALogin |
登录用 MFA challenge 已失效。 |
invalid_mfa_token |
401 |
provisionTOTP、verifyTOTP |
TOTP provisioning / verify 的 token 不存在或过期。 |
invalid_mfa_code |
401 / 500 |
MFA handlers |
验证码错误,或库校验过程失败。 |
mfa_challenge_locked |
429 |
verifyTOTP |
连续错误次数过多,被暂时锁定。 |
mfa_not_enabled |
400 |
verifyMFALogin、disableMFA |
用户当前没有启用 MFA。 |
注册、邮箱验证、密码重置
| 错误码 |
常见状态码 |
来源 |
含义 |
verification_required |
400 |
register |
已启用邮箱验证,但未提供验证码。 |
invalid_code |
400 |
register、verifyEmail |
验证码错误或过期。 |
verification_failed |
500 |
邮件验证流程 |
SMTP 发送或用户更新失败。 |
email_already_exists |
409 |
register、sendEmailVerification |
邮箱已存在或已验证。 |
name_already_exists |
409 |
register |
用户名冲突。 |
password_reset_failed |
500 |
password reset flows |
密码重置邮件发送、用户更新或其他内部步骤失败。 |
invalid_token |
400 |
confirmPasswordReset |
reset token 无效或过期。 |
OAuth / token exchange / refresh
| 错误码 |
常见状态码 |
来源 |
含义 |
provider_not_found |
404 |
OAuth routes |
provider 未注册。 |
code_missing |
400 |
oauthCallback |
callback query 中缺少 code。 |
oauth_exchange_failed |
500 |
oauthCallback |
与 provider 交换 token 失败。 |
fetch_profile_failed |
500 |
oauthCallback |
拉取 provider profile 失败。 |
invalid_exchange_code |
401 |
exchangeToken |
一次性 exchange code 无效或已过期。 |
token_service_unavailable |
503 |
refreshToken |
当前未启用 token service。 |
invalid_refresh_token |
401 |
refreshToken |
refresh token 无效或已过期。 |
管理员与权限
| 错误码 |
常见状态码 |
来源 |
含义 |
forbidden |
403 |
requireAdminPermission、RequireRole |
用户权限不足。 |
root_only |
403 |
sandbox bind / assume / tenant bootstrap |
只有 root 可执行。 |
root_email_enforced |
403 |
requireAdminPermission |
root role 被限制给 admin@svc.plus。 |
metrics_unavailable |
503 / 其他 |
adminUsersMetrics |
指标 provider 未配置或执行失败。 |
read_only_account |
403 |
多个写接口 |
demo/read-only 账号禁止写操作。 |
account_suspended |
403 |
session user checks |
账号被暂停。 |
XWorkmate / Vault
| 错误码 |
常见状态码 |
来源 |
含义 |
xworkmate_vault_unavailable |
503 |
ensureXWorkmateVaultService |
当前未注入 Vault backend。 |
tenant_membership_required |
403 |
xworkmate.go |
当前用户没有 tenant membership。 |
tenant_not_found |
404 |
xworkmate.go |
host 解析出的 tenant 不存在。 |
xworkmate_profile_forbidden |
403 |
updateXWorkmateProfile |
无权修改 tenant integration profile。 |
token_persistence_forbidden |
400 |
updateXWorkmateProfile |
禁止把 raw token/password/api key 直接落库。 |
xworkmate_secret_unknown_target |
400 |
secret PUT/DELETE |
:target 不在允许列表内。 |
xworkmate_secret_write_failed |
500 |
secret PUT |
写入 Vault/backend 失败。 |
xworkmate_secret_delete_failed |
500 |
secret DELETE |
删除 Vault/backend secret 失败。 |
计费、订阅、流量与调度
| 错误码 |
常见状态码 |
来源 |
含义 |
subscription_not_found |
404 |
cancelSubscription |
订阅不存在。 |
subscription_upsert_failed |
500 |
upsertSubscription |
订阅落库失败。 |
stripe_cancel_failed |
502 |
cancelSubscription |
调用 Stripe 取消失败。 |
usage_summary_unavailable |
500 |
accountUsageSummary |
读取使用量汇总失败。 |
invalid_start / invalid_end |
400 |
accountUsageBuckets |
时间范围参数不是 RFC3339。 |
policy_not_found |
404 |
accountPolicy、internalAccountPolicy |
账户策略快照不存在。 |
collector_status_unavailable |
500 |
adminCollectorStatus |
collector 读面不可用。 |
scheduler_status_unavailable |
500 |
adminSchedulerStatus |
调度决策读面不可用。 |
Agent 与内部服务
| 错误码 / 文本 |
常见状态码 |
来源 |
含义 |
missing_token |
401 |
api/agent_server.go |
agent token 缺失。 |
invalid_token |
401 |
api/agent_server.go |
agent token 无效。 |
agent_registry_unavailable |
503 |
api/agent_server.go |
未注入 agentserver.Registry。 |
list_users_failed |
500 |
internalPublicOverview、listAgentUsers、internalNetworkIdentities |
枚举用户失败。 |
store_not_configured / store_unavailable |
503 |
internal handlers |
store 未配置。 |
sandbox_missing |
404 |
sandbox guest handlers |
sandbox 用户不存在。 |
调用方建议
- 对
respondError 路由,优先按 error 做程序分支,message 仅用于展示。
- 对 agent/internal 路由,不要假设一定有
message。
- 对
429 mfa_challenge_locked,调用方应读取 retryAt 决定 UI 倒计时。
- 对
POST /api/auth/admin/settings 的 409,应读取返回的 version 与 matrix 重新加载服务端最新值。
English
Core Rule
The current API does not have a single shared error-schema package. Instead, error behavior is shaped by a few implementation conventions:
- Most business handlers use
respondError, which produces the standard envelope.
- Some internal / agent / simple GORM-backed handlers call
c.JSON directly and return only error or error + message.
- Documentation therefore has to distinguish between the stable error envelope and simplified handler-local errors.
Standard Error Envelope
Most authentication, account, admin, XWorkmate, and accounting handlers return:
{"error":"code","message":"human readable message"}
Field meaning:
| Field |
Meaning |
error |
Machine-readable error code that is usually stable enough for frontend branching. |
message |
Human-readable explanation. |
Extended Error Envelope
Some handlers add extra fields on top of the standard shape:
| Scenario |
Extra fields |
| MFA lockout |
retryAt, mfaToken |
| Admin settings optimistic conflict |
version, matrix |
| Sandbox binding / GORM errors |
Sometimes the raw err.Error() is surfaced in message |
/api/ping |
Not an error-envelope route; it is a pure success read |
Simplified Error Envelope
The following handler families commonly return:
{"error":"some_code"}
or:
{"error":"some_code","message":"..."}
Typical sources:
| File |
Example APIs |
api/agent_server.go |
/api/agent-server/v1/users, /status |
api/user_agents.go |
/api/agent-server/v1/nodes, legacy /api/agent/nodes |
api/internal_sandbox_guest.go |
/api/internal/sandbox/guest |
api/admin_sandbox.go |
/api/auth/admin/sandbox/*, /api/admin/sandbox/* |
internal/auth/middleware.go |
Direct JWT / internal-service middleware rejection responses |
Error Sources By Layer
| Source layer |
Typical codes / texts |
Meaning |
| Request binding and validation |
invalid_request, missing_credentials, invalid_email, password_too_short |
Bad JSON bodies, forbidden query-string credentials, invalid formats. |
| Session / JWT / internal token |
session_token_required, invalid_session, missing authorization header, invalid or expired token, missing service token |
Authentication precondition failed. |
| Role / permission / account-state checks |
forbidden, root_only, account_suspended, read_only_account, root_email_enforced |
The user exists but is not allowed to perform the action. |
| Business-state failures |
email_already_exists, subscription_not_found, mfa_not_enabled, policy_not_found |
Domain state does not satisfy the requested transition. |
| External or backend dependency failures |
verification_failed, xworkmate_secret_write_failed, stripe_cancel_failed, collector_status_unavailable |
SMTP, Vault, Stripe, DB, Xray rendering, and similar dependency failures. |
High-Frequency Error Index
Authentication And Sessions
| Code |
Typical status |
Source |
Meaning |
credentials_in_query |
400 |
api/api.go |
Sensitive credentials were sent through the query string. |
missing_credentials |
400 |
api/api.go |
Required login or registration fields are missing. |
user_not_found |
404 |
api/api.go, api/user_agents.go |
No user matches the identifier or session-derived user ID. |
invalid_credentials |
401 |
api/api.go |
Password verification failed. |
email_not_verified |
401 |
api/api.go |
The email is not verified, so login is blocked. |
session_token_required |
401 |
api/api.go, api/xworkmate.go, api/admin_users_metrics.go |
A session token is required. |
invalid_session |
401 |
Multiple session-based handlers |
The session does not exist, is expired, or cannot be matched. |
session_user_lookup_failed |
500 |
Multiple handlers |
The session exists but the backing user cannot be loaded. |
MFA
| Code |
Typical status |
Source |
Meaning |
mfa_ticket_required |
400 |
verifyMFALogin |
The MFA completion endpoint is missing its ticket. |
mfa_code_required |
400 |
verifyMFALogin, verifyTOTP |
No TOTP code was supplied. |
invalid_mfa_ticket |
401 |
verifyMFALogin |
The login MFA challenge has expired or is invalid. |
invalid_mfa_token |
401 |
provisionTOTP, verifyTOTP |
The provisioning / verification token does not exist or has expired. |
invalid_mfa_code |
401 / 500 |
MFA handlers |
The code is wrong, or the verification library errored. |
mfa_challenge_locked |
429 |
verifyTOTP |
Too many invalid attempts caused a temporary lockout. |
mfa_not_enabled |
400 |
verifyMFALogin, disableMFA |
The user does not currently have MFA enabled. |
Registration, Email Verification, And Password Reset
| Code |
Typical status |
Source |
Meaning |
verification_required |
400 |
register |
Email verification is enabled but the code was not provided. |
invalid_code |
400 |
register, verifyEmail |
The verification code is wrong or expired. |
verification_failed |
500 |
Email-verification flows |
SMTP delivery or user-update work failed. |
email_already_exists |
409 |
register, sendEmailVerification |
The email already exists or is already verified. |
name_already_exists |
409 |
register |
Username conflict. |
password_reset_failed |
500 |
Password-reset flows |
Email delivery, user update, or other internal reset steps failed. |
invalid_token |
400 |
confirmPasswordReset |
The reset token is invalid or expired. |
OAuth, Token Exchange, And Refresh
| Code |
Typical status |
Source |
Meaning |
provider_not_found |
404 |
OAuth routes |
The provider is not registered. |
code_missing |
400 |
oauthCallback |
The callback query does not contain code. |
oauth_exchange_failed |
500 |
oauthCallback |
Exchanging the provider code failed. |
fetch_profile_failed |
500 |
oauthCallback |
Fetching the provider profile failed. |
invalid_exchange_code |
401 |
exchangeToken |
The one-time exchange code is invalid or expired. |
token_service_unavailable |
503 |
refreshToken |
The token service is not enabled. |
invalid_refresh_token |
401 |
refreshToken |
The refresh token is invalid or expired. |
Admin And Permission Errors
| Code |
Typical status |
Source |
Meaning |
forbidden |
403 |
requireAdminPermission, RequireRole |
The caller lacks the required permission. |
root_only |
403 |
Sandbox bind / assume / tenant bootstrap flows |
Only the root user may perform the action. |
root_email_enforced |
403 |
requireAdminPermission |
The root role is restricted to admin@svc.plus. |
metrics_unavailable |
503 and others |
adminUsersMetrics |
The metrics provider is missing or failed. |
read_only_account |
403 |
Multiple write handlers |
Demo/read-only accounts are blocked from writes. |
account_suspended |
403 |
Session user checks |
The account has been suspended. |
XWorkmate And Vault
| Code |
Typical status |
Source |
Meaning |
xworkmate_vault_unavailable |
503 |
ensureXWorkmateVaultService |
No Vault backend has been injected. |
tenant_membership_required |
403 |
xworkmate.go |
The caller has no tenant membership. |
tenant_not_found |
404 |
xworkmate.go |
The host-resolved tenant does not exist. |
xworkmate_profile_forbidden |
403 |
updateXWorkmateProfile |
The caller may not update the tenant integration profile. |
token_persistence_forbidden |
400 |
updateXWorkmateProfile |
Raw token/password/api-key values may not be persisted directly. |
xworkmate_secret_unknown_target |
400 |
Secret PUT/DELETE handlers |
The :target path value is outside the allowed set. |
xworkmate_secret_write_failed |
500 |
Secret PUT |
Persisting the Vault/backend secret failed. |
xworkmate_secret_delete_failed |
500 |
Secret DELETE |
Deleting the Vault/backend secret failed. |
Billing, Subscription, Traffic, And Scheduler Reads
| Code |
Typical status |
Source |
Meaning |
subscription_not_found |
404 |
cancelSubscription |
The subscription does not exist. |
subscription_upsert_failed |
500 |
upsertSubscription |
Persisting the subscription failed. |
stripe_cancel_failed |
502 |
cancelSubscription |
Stripe cancellation failed. |
usage_summary_unavailable |
500 |
accountUsageSummary |
Usage summary reads failed. |
invalid_start / invalid_end |
400 |
accountUsageBuckets |
The time-range query parameters are not valid RFC3339 timestamps. |
policy_not_found |
404 |
accountPolicy, internalAccountPolicy |
No account policy snapshot is available. |
collector_status_unavailable |
500 |
adminCollectorStatus |
Collector read models are unavailable. |
scheduler_status_unavailable |
500 |
adminSchedulerStatus |
Scheduler decision reads are unavailable. |
Agent And Internal-Service Errors
| Code / text |
Typical status |
Source |
Meaning |
missing_token |
401 |
api/agent_server.go |
The agent token is missing. |
invalid_token |
401 |
api/agent_server.go |
The agent token is invalid. |
agent_registry_unavailable |
503 |
api/agent_server.go |
No agentserver.Registry has been injected. |
list_users_failed |
500 |
internalPublicOverview, listAgentUsers, internalNetworkIdentities |
Enumerating users failed. |
store_not_configured / store_unavailable |
503 |
Internal handlers |
The store has not been configured. |
sandbox_missing |
404 |
Sandbox guest handlers |
The sandbox user does not exist. |
Client Guidance
- For
respondError-based routes, branch on error first and treat message as display text.
- For agent/internal routes, do not assume
message is always present.
- For
429 mfa_challenge_locked, read retryAt and drive the UI countdown from it.
- For
409 from POST /api/auth/admin/settings, reload the server-returned version and matrix before retrying.