RESTful API 设计规范速查表 - REST API 接口设计大全
接口退化的原因很少是功能缺失,而是形状不统一:同一种资源在不同接口里换着名字,同一类错误一半返 200 带 errCode 一半返 500,前端每接一个都要重新判断一次。本表把 REST 接口的每一条决策点拆开:URL 如何表达资源层级与集合、用名词复数而非动词、过滤分页排序放查询参数还是路径、方法如何映射到幂等语义、状态码在什么场景取哪个、请求头承担哪些横切信息、响应体和错误体固定成什么形状、认证选哪一种。动手顺序也有讲究——先把资源模型和 URL 定下来,因为它决定权限与缓存怎么设计;再定错误结构,因为它决定前端兜底逻辑;响应包装最后定,因为改它成本最低。与方法表的分工是:那张表讲单条动词怎么选,这张表讲一整套接口保持形状稳定。
典型使用场景
面向设计、评审或实现 HTTP 接口的开发者与后端工程师。表内把 REST 接口的约定一次理顺:URL 用名词复数表示集合、用 ID 定位单个资源,查询参数负责过滤、分页与排序;HTTP 方法各司其职,GET 安全且幂等、PUT 与 DELETE 幂等、POST 不幂等,PATCH 用于部分更新,HEAD 只取响应头,OPTIONS 承担 CORS 预检。状态码部分强调语义边界:401 是没认证、403 是认证了但没权限,400 是格式问题、422 是字段语义校验失败,409 用于重复创建冲突,429 表示触发限流。请求头与认证一节覆盖 JSON 协商、Bearer Token / JWT、API Key 与 OAuth 2.0 授权码模式,另有 If-None-Match 配合 ETag 做条件请求。响应结构推荐用 data + meta 的统一包裹,错误统一为 error.code 与 error.message,分页既可用 body 里的 page/limit/total,也可用 Link 头;批量操作走 /users/batch 这类子资源端点,配合 201 + Location、RateLimit-Limit / Retry-After 等响应头,让客户端能可靠处理重试与配额。
URL 设计规范 6
/api/v1/users/api/v1/users/123/api/v1/users/123/orders/api/v1/users?role=admin/api/v1/users?page=2&limit=20/api/v1/users?sort=-created_atHTTP 方法 8
GET /usersGET /users/123POST /usersPUT /users/123PATCH /users/123DELETE /users/123HEAD /users/123OPTIONS /users常用状态码 12
200 OK201 Created204 No Content301 Moved Permanently400 Bad Request401 Unauthorized403 Forbidden404 Not Found409 Conflict422 Unprocessable Entity429 Too Many Requests500 Internal Server Error请求头 5
Content-Type: application/jsonAccept: application/jsonAuthorization: Bearer <token>If-None-Match: "etag"X-API-Key: abc123响应格式 5
{ "data": [...], "meta": {...} }{ "data": {...} }{ "error": { "code": "...", "message": "..." } }{ "page": 2, "limit": 20, "total": 100 }Link: <url>; rel="next"认证方式 5
Authorization: Basic base64(user:pass)Authorization: Bearer <jwt>X-API-Key: <key>?api_key=<key>OAuth 2.0最佳实践 5
幂等性设计POST 创建返回 201 + Location批量操作: POST /users/batchRateLimit-Limit / Retry-AfterHATEOAS参数矩阵
| 参数 | 作用 | 示例 |
|---|---|---|
/api/v1/users | 资源集合路径,名词用复数 | /api/v1/users |
/api/v1/users/123 | 用 ID 定位单个资源 | /api/v1/users/123 |
/api/v1/users/123/orders | 子资源嵌套路径 | /api/v1/users/123/orders |
?role=admin | 查询参数过滤资源 | /api/v1/users?role=admin |
?page=2&limit=20 | 分页参数 | /api/v1/users?page=2&limit=20 |
?sort=-created_at | 排序参数,前缀 - 表示降序 | /api/v1/users?sort=-created_at |
GET /users | 获取资源列表,安全且幂等 | GET /users |
POST /users | 创建资源,非幂等 | POST /users |
PUT /users/123 | 完整更新资源,幂等 | PUT /users/123 |
PATCH /users/123 | 部分更新资源 | PATCH /users/123 |
DELETE /users/123 | 删除资源,幂等 | DELETE /users/123 |
HEAD /users/123 | 只获取响应头,不返回 body | HEAD /users/123 |
OPTIONS /users | 查询支持的方法,用于 CORS 预检 | OPTIONS /users |
200 OK | 请求成功 | GET /users/123 → 200 |
201 Created | 资源创建成功,配合 Location 头 | POST /users → 201 + Location |
204 No Content | 成功但无返回内容,如删除 | DELETE /users/123 → 204 |
301 Moved Permanently | 资源永久重定向 | GET /old → 301 |
400 Bad Request | 请求参数错误或格式不合法 | POST /users → 400 |
401 Unauthorized | 未认证,缺少或无效凭证 | 无 Authorization 头 → 401 |
403 Forbidden | 已认证但无权限访问 | 普通用户访问管理接口 → 403 |
404 Not Found | 资源不存在 | GET /users/999 → 404 |
409 Conflict | 资源冲突,如重复创建 | 重复注册同名账号 → 409 |
422 Unprocessable Entity | 字段校验失败的语义错误 | 邮箱格式非法 → 422 |
429 Too Many Requests | 请求频率超限,触发限流 | 高频调用 → 429 |
500 Internal Server Error | 服务器内部错误 | 未捕获异常 → 500 |
Content-Type: application/json | 声明请求体为 JSON | Content-Type: application/json |
Accept: application/json | 期望响应为 JSON | Accept: application/json |
Authorization: Bearer *** | Bearer Token / JWT 认证 | Authorization: Bearer <token> |
If-None-Match: "etag" | 条件请求,用 ETag 校验缓存 | If-None-Match: "v1" |
X-API-Key: *** | 自定义头携带 API Key 认证 | X-API-Key: <key> |
{ "data": [...], "meta": {...} } | 标准列表响应结构 | { "data": [], "meta": {} } |
{ "data": {...} } | 单资源响应结构 | { "data": { "id": 1 } } |
{ "error": { "code", "message" } } | 统一错误响应结构 | { "error": { "code": "INVALID_ARG" } } |
{ "page", "limit", "total" } | 分页元信息 | { "page": 2, "limit": 20, "total": 100 } |
Link: <url>; rel="next" | 分页链接头,便于客户端翻页 | Link: </users?page=3>; rel="next" |
Authorization: Basic *** | HTTP 基本认证 | Authorization: Basic <base64> |
?api_key=<key> | 查询参数携带 API Key | /users?api_key=<key> |
OAuth 2.0 授权码模式 | 第三方授权的标准流程 | authorization code flow |
POST /users/batch | 批量创建/更新使用子资源端点 | POST /users/batch |
RateLimit-Limit / Retry-After | 限流响应头,告知配额与重试时机 | Retry-After: 30 |
HATEOAS | 响应中包含相关资源链接 | { "links": { "orders": "..." } } |
易错点与避坑指南
现象客户端收到 401 后不断重试,永远进不去
原因接口把「凭证无效」也返回成了 401,客户端分不清该重新登录还是该重试
处置未认证返回 401,已认证但无权限返回 403,让客户端能走不同的处理分支
现象前端把 400 当成字段校验失败来展示,实际是请求体格式就不对
原因格式错误与语义错误混用了同一个状态码
处置格式或语法错误用 400,字段级业务校验失败用 422,并在 error.code 中区分
现象重复提交同一个创建请求,数据库里出现两条相同记录
原因POST 本身不幂等,客户端超时重试时服务端无法识别重复
处置用 409 Conflict 表达重复创建,并要求客户端带幂等键或先查询再创建
现象PUT 更新后客户端没传的字段被清空了
原因PUT 被当作完整替换处理,缺失字段按默认值写回
处置语义上只需要改部分字段时改用 PATCH,只接受实际提交的字段
现象改用 PATCH 后客户端仍按完整对象提交,服务端行为与 PUT 一样
原因接口实现忽略部分更新语义,全量覆盖了记录
处置在 PATCH 处理里只读取请求体中出现的键做更新,其余字段保持不变
现象集合路径写成 /user 而不是 /users,客户端按同一规则解析时全错
原因URL 命名未统一,资源集合与单个资源没有区分
处置统一用名词复数表示集合、ID 定位单个资源,并把版本号固定在 /v1/ 前缀里
现象触发限流后客户端没有退避,接口雪崩式被压垮
原因429 响应里没有告诉客户端该等多久,客户端只能密集重试
处置在 429 响应中带上 Retry-After 与 RateLimit-Limit,让客户端按提示退避重试
排障路径
1确认某个端点到底支持哪些 HTTP 方法
OPTIONS /users预检请求会返回允许的方法列表,也是排查跨域与方法限制的第一个入口。
2检查当前客户端手里的缓存副本是否还有效
If-None-Match: "etag"带上上次的 ETag 请求,内容没变时服务端返回 304,省掉整个响应体传输。
3定位分页到底从哪一页开始、总量是多少
/api/v1/users?page=2&limit=20响应里的 { "page", "limit", "total" } 可核对服务端与客户端的页码约定是否一致。
4按创建时间倒序核对列表默认排序
/api/v1/users?sort=-created_at前面的 - 表示降序;去掉 - 则是升序,可确认排序方向是否符合接口约定。
5确认创建成功后资源的新地址
POST /api/v1/users成功应返回 201,响应头里的 Location 给出新建资源的 URI,可直接用它跟进一次 GET。
6验证鉴权失败到底属于哪一类
GET /api/v1/users/123无凭证得到 401、带凭证仍被拒得到 403,据此判断该修客户端凭证还是服务端授权。
7确认批量操作端点的行为
POST /users/batch批量走独立子资源端点而非在集合上叠加方法,响应里应逐条给出成功与失败明细。
命令示例
列表接口:查询参数 + 分页 + 排序一次表达清楚
GET /api/v1/users?role=admin&page=2&limit=20&sort=-created_at
Accept: application/json
Authorization: Bearer <token>集合用复数名词、ID 定位用子路径,过滤/分页/排序全部走查询参数(sort 前加 - 表示降序)。响应体统一包在 data 里,分页信息放 meta,客户端只需认一套结构。
输出
HTTP/1.1 200 OK
Content-Type: application/json
{
"data": [ { "id": 123, "role": "admin", "created_at": "2026-07-19T10:30:00Z" } ],
"meta": { "page": 2, "limit": 20, "total": 100 }
}创建资源返回 201 + Location
POST /api/v1/users
Content-Type: application/json
{ "name": "tom" }POST 创建成功必须回 201 并用 Location 头指明新资源位置,客户端不必再解析 body 拼 URL。用 200 返回创建结果会让缓存和监控都无法区分「创建」与「查询」。
输出
HTTP/1.1 201 Created
Location: /api/v1/users/456
{ "data": { "id": 456, "name": "tom" } }用 ETag 做条件请求省掉整包传输
GET /api/v1/users/123
If-None-Match: "a1b2c3d4"客户端把上次拿到的 ETag 原样回传,内容没变时服务端回 304 且不带 body。对频繁轮询的移动端接口,流量能降到零,省带宽也省服务端序列化开销。
输出
HTTP/1.1 304 Not Modified ETag: "a1b2c3d4"
错误响应用固定结构,区分 401 与 403
GET /api/v1/users/999
Authorization: Bearer <expired-token>401 表示凭证缺失或失效(客户端该去登录/刷新 token),403 表示凭证有效但权限不足(重试无用)。把两者混成一个 401 会让客户端陷入无限重试。
输出
HTTP/1.1 403 Forbidden
{
"error": {
"code": "FORBIDDEN",
"message": "token lacks scope users:read"
}
}限流后按 Retry-After 退避
GET /api/v1/users
# 触发限流时:
# HTTP/1.1 429 Too Many Requests
# RateLimit-Limit: 100
# Retry-After: 30429 必须带 Retry-After,客户端按它退避重试;否则客户端只能硬编码一个猜测值,在限流期间把压力越叠越大。写接口的客户端还要把 429 计入重试预算而不是当失败放弃。
常见坑与注意事项
- 分页游标(cursor / after / since)依赖排序字段的稳定性:一旦有新记录插入或同值记录,next 游标会失效或漏数据,导致客户端卡在同一页或丢行;优先用「(created_at, id) 复合游标」并给一致性快照。
- POST 创建若没有幂等键(Idempotency-Key)或唯一约束,客户端超时重试会重复下单、重复扣款;支付类写接口必须保证重试安全,否则一次网络抖动就是真金白银的损失。
- 用本地时间存时间戳会丢时区与精度:跨时区客户端会把 2026-07-19T10:30:00Z 读成本地时间,跨日订单对不上账;统一用 UTC RFC 3339 带 Z 或偏移量,金额与数量一律用整数最小单位。
- 在 URL 查询参数里传 API Key(?api_key=)会被浏览器历史、代理日志和 Referer 头记录下来,等同于把凭证公开;请改用 X-API-Key 或 Authorization 头。
- PUT 被定义为幂等的整体替换,只传部分字段会把未传字段清空;客户端想局部更新必须用 PATCH,否则会静默抹掉别的字段。
- DELETE 声称幂等,但对同一资源先删后重建会让第二次 DELETE 返回 404,客户端若把 404 当失败就会误报;确认删除结果需回查时用 204 或带回执字段。
提示
- URL 使用名词复数表示资源集合,如 /users 而不是 /user。
- GET 请求应该是安全的,不改变服务器状态;PUT/DELETE 应是幂等的。
- PUT 是完整替换,PATCH 是部分更新,根据场景选择。
- API 版本放在 URL 路径中(/v1/)或请求头中(Accept-Version)。
- 错误响应应包含错误码、消息和可选的详情字段,方便客户端处理。
- 分页统一一种风格并写进文档:offset 分页用 limit/offset,cursor 分页用 cursor/limit 且游标不透明,大数据量表优先 cursor 避免深翻页拖垮数据库。
- 错误体把字段错误放到 details 里做字段级映射(如 {"email": "格式非法"}),前端才能定位到输入框,而不是只弹一句"参数错误"。
- 幂等键用客户端生成的 UUID 而非请求体字段,且键要绑定到操作类型:同一键复用到不同端点应被拒绝,否则去重逻辑会误伤。
常见问题
401、403、404 到底怎么选?
按「鉴权还是授权还是找不到」分:401 Unauthorized 是没通过认证——缺少凭证、token 无效或已过期,客户端的解法是带上或刷新凭证。403 Forbidden 是通过了认证但权限不足,凭证没问题,是这个主体不允许碰这个资源,重试无用。404 Not Found 是资源不存在,包括刻意隐藏存在性时也用它,避免泄露「资源存在但你无权」的信号。三者的重试语义完全不同:401 可重试(换 token)、403 不可重试、404 需要换 URL 或 ID。POST 创建重复数据则不是 401/403/404,而是 409 Conflict。
PUT 和 PATCH 都能改数据,接口设计时该怎么选?
看替换语义:PUT /users/123 是完整更新,必须提交该资源的完整表示,调用两次结果一致(幂等);PATCH /users/123 是部分更新,只传要改的字段,其余保持不变。同样属于幂等设计的还有 DELETE /users/123——重复删除结果一致。另外 GET 必须安全,不能改变服务器状态,POST /users 用来创建,属于非幂等。工程上选 PATCH 更省事,客户端只发改动的字段;选 PUT 则更利于缓存与校验,但一旦有字段被漏传就会丢数据,所以很多团队在 schema 里区分「全量必填」和「增量可空」两套表示。
参数不合法到底该返回 400 还是 422?
按错误发生的位置分层:400 Bad Request 用于请求参数错误或格式不合法——JSON 解析不了、Content-Type 不对、语法层面就坏了,服务器连「内容」都没读进去。422 Unprocessable Entity 用于语义错误,字段校验失败:结构解析得进去,但 email 格式不对、age 越界、状态取值非法。这类区分对客户端有意义,因为 400 意味着「别再原样重发」,422 意味着「把报错字段改了再发」。二者都应配合统一的错误结构 { "error": { "code": "...", "message": "..." } } 返回,方便客户端按 code 分支处理。
创建接口返回什么?批量写入和限流怎么处理?
创建成功应返回 201 Created 而不是 200,并带上 Location 头指向新资源 URI;后续取该资源用 HEAD /users/123(只取响应头)或 GET 取 body。成功但无内容时用 204 No Content,删除场景最典型。批量创建/更新不要硬塞进普通 POST,用子资源端点 POST /users/batch。触发限流时返回 429 Too Many Requests,并用 RateLimit-Limit 告知配额、Retry-After 告知何时重试,客户端据此退避。响应里带上相关资源链接(HATEOAS)能让客户端跟着链接发现下一步动作,而不是把 URL 硬编码。
几种认证方式怎么选?
按客户端类型和信任度分。Authorization: Bearer <token> 适合前后端分离的 SPA,token 可以是 JWT,权限信息自包含,Authorization: Basic <base64> 只是 base64 编码的账号密码,安全性低,适合内网或临时调试。X-API-Key: <key> 走自定义头,服务端到服务端调用最常见;把 key 放进 ?api_key=<key> 查询参数虽然方便但会进访问日志和 Referer,生产环境应避免。需要第三方用户授权的用 OAuth 2.0 授权码模式。配套请求头有 Content-Type: application/json、Accept: application/json,以及 If-None-Match: "etag" 做条件请求,靠 ETag 命中缓存省带宽。
由 巧匠 维护
公开更新于 2026年7月21日,内容持续校对官方文档。