未启用付费 REST API 功能时应返回什么 HTTP 状态码?
结论
用户已通过身份认证,但当前账号没有启用相应的付费功能,建议返回 403 Forbidden。
403 表示服务器理解该请求,也知道请求者是谁,但不允许其访问当前资源或执行相应操作。未购买套餐、功能尚未启用、缺少产品授权,都属于权限不足或 entitlement 条件未满足。
通常不建议使用 402 Payment Required。这个状态码长期处于保留状态,通用语义、客户端处理方式和生态支持都不明确。只有当 API 将 402 明确定义为计费协议的一部分,并且所有调用方都了解这一约定时,才适合使用。其他情况下,403 更合适。
不同情况应该如何选择
已登录,但没有购买或启用功能:403 Forbidden
这正是问题中的主要场景:
HTTP/1.1 403 Forbidden
Content-Type: application/problem+json
{
"type": "https://api.example.com/problems/feature-not-enabled",
"title": "Feature not enabled",
"status": 403,
"detail": "This endpoint requires the analytics_pro feature.",
"code": "FEATURE_NOT_ENABLED",
"required_feature": "analytics_pro",
"activation_url": "https://example.com/settings/billing"
}
HTTP 状态码负责说明请求遭到拒绝,具体原因则应通过稳定的业务错误码表达。客户端可以根据 FEATURE_NOT_ENABLED 显示升级入口,无需解析自然语言。
尚未认证:401 Unauthorized
如果服务器无法确认调用者身份,例如 token 缺失、无效或已经过期,应返回 401:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer
Content-Type: application/problem+json
{
"title": "Authentication required",
"status": 401,
"code": "INVALID_ACCESS_TOKEN"
}
虽然状态码名称中有 “Unauthorized”,但 401 实际表示认证失败。调用者已经通过认证,只是权限不足时,才使用 403。
为避免泄露资源存在性:可考虑 404 Not Found
如果需要向无权限用户隐藏付费 endpoint 或资源是否存在,可以返回 404。这是一种安全策略,并非常规的”未购买”提示。
例如,多租户系统可能不希望调用者根据响应差异判断某项资源是否存在:
HTTP/1.1 404 Not Found
Content-Type: application/problem+json
{
"title": "Resource not found",
"status": 404,
"code": "RESOURCE_NOT_FOUND"
}
如果付费 API 已经写在公开文档中,返回 404 通常没有明显好处,还可能让调用者误以为 URL 写错了。
推荐的处理流程
服务端可以依次进行以下判断:
- 检查凭证是否存在且有效,失败时返回
401。 - 检查账号是否有权访问目标资源,失败时返回
403;如果需要隐藏资源,则返回404。 - 检查账号是否具备 endpoint 要求的付费功能,未启用时返回
403,并附上业务错误码。 - 检查请求参数和业务状态,根据具体问题返回相应的
4xx错误。 - 权限检查通过后,执行请求并返回正常结果。
Express 示例:
app.get(
"/v1/advanced-analytics",
authenticate,
async (req, res) => {
const enabled = await hasFeature(
req.user.accountId,
"analytics_pro"
);
if (!enabled) {
return res.status(403).type("application/problem+json").json({
type: "https://api.example.com/problems/feature-not-enabled",
title: "Feature not enabled",
status: 403,
detail: "This endpoint requires the analytics_pro feature.",
code: "FEATURE_NOT_ENABLED",
required_feature: "analytics_pro",
activation_url: "https://example.com/settings/billing"
});
}
const result = await generateAdvancedAnalytics(req.user.accountId);
return res.status(200).json(result);
}
);
为什么其他状态码不合适
402 Payment Required:可以用于私有 API 约定,但没有统一、可互操作的标准语义,不能只因为名称看起来合适就使用。404 Not Found:适合隐藏资源,不适合普通的套餐权限提示。409 Conflict:表示请求与资源当前状态发生冲突,不表示访问授权失败。422 Unprocessable Content:表示请求内容语法正确,但服务器无法处理,不适用于套餐权限判断。429 Too Many Requests:用于限流或配额消耗过快,不能表示功能尚未购买。
如果用户已经购买该功能,只是当月额度耗尽,可以根据产品规则返回 429 Too Many Requests,再通过响应体或相关 header 说明配额及恢复时间。这与功能从未启用是两种不同的情况。
注意事项
错误响应应当稳定,并且可由程序识别。不要只返回 "Please upgrade" 这样的字符串,最好同时包含:
- HTTP 状态码
403 - 稳定的业务错误码,例如
FEATURE_NOT_ENABLED - 所需功能的标识
- 面向用户的说明
- 可选的启用或升级地址
错误信息不应泄露内部价格、账单状态或账号权限细节,除非调用者确实有权查看这些信息。API 文档也应明确说明付费 endpoint 可能返回 403,并列出对应的业务错误码。
默认规则可以概括为:未认证返回 401;已认证但未启用付费功能返回 403;需要隐藏资源是否存在时,再考虑返回 404。
备注:内容仅供参考。
