打开任何一个存在超过两年的代码库,你都会发现这些历史遗留痕迹: /getUser, /user_list, /Users/fetchAll, 三种不同的分页方案,以及一个 customerID 字段旁边紧挨着 order_id 位于同一个响应中。它们都不会导致任何功能故障,却都会拖慢所有人的工作速度。
命名是你能做出的成本最低的 API 设计决策,也是最难以逆转的决策。一旦客户端依赖于 /getOrders, 一旦这样做,你可能要维护它很多年。本指南为 REST API 强制你作出的每个命名决策都给出一条明确规则,并为每条规则提供一个正例和一个反例。它遵循我们更广泛的 面向开发者的 REST API 指南,但会聚焦于团队最常争论的部分:如何为事物命名。
如果你更愿意通过工具而不是代码审查意见来落实这些规则, Apifox 让你可以在任何人编写代码之前,基于共享 schema 以可视化方式定义每个 endpoint。文末会详细介绍这一点。
集合应使用复数名词
URL 命名的是资源,而不是操作。集合是一组事物,因此应使用复数名词为其命名。
推荐:
GET /v1/products
GET /v1/products/89
GET /v1/orders
不要:
GET /v1/getProducts
GET /v1/product
GET /v1/productList
复数形式在这两个层级都适用。 /products 读作“产品集合”,而 /products/89 读作“集合中的产品 89”。单数命名会迫使 URL 变得别扭,例如 /product/89 表示一个项目,但 /product 表示多个项目,读起来不对。这份 Microsoft REST API 指南 最终采用复数名词,正是出于这个原因;大多数公共 API(Stripe、GitHub、Shopify)也采用了同样的做法。
例外情况是:单例资源。如果用户恰好只有一个购物车, /users/42/cart 就没问题。不要将基数为 1 的内容复数化。
路径中不要使用动词
HTTP method 就是动词。在路径中再放入另一个动词会重复信息,并破坏资源模型。
推荐:
GET /v1/orders/42 (read it)
DELETE /v1/orders/42 (delete it)
PATCH /v1/orders/42 (update it)
不要:
GET /v1/fetchOrder/42
POST /v1/deleteOrder/42
POST /v1/updateOrderStatus
基于动词的路径还会扩大你的接口暴露面。一个资源配有四个方法,就会变成四个需要分别编写文档、测试和缓存的端点。缓存失效也会变得更糟:CDN 可以缓存 GET /v1/orders/42 并在 DELETE /v1/orders/42 时使缓存失效,因为二者都指向同一个 URL。它无法将 /fetchOrder/42 与 /deleteOrder/42。
在 URL 路径中使用 kebab-case
多词路径段需要一个分隔符,而连字符正是合适的选择。
建议:
/v1/gift-cards
/v1/shipping-addresses
不要:
/v1/giftCards
/v1/gift_cards
/v1/GiftCards
三个原因。Google 在编制索引时会将连字符视为单词分隔符,因此,使用 kebab-case 的公共 API 文档排名更高。URL 在电子邮件或文档中加下划线后,下划线会消失。而 URL 中的 camelCase 容易引发大小写敏感性错误: /giftCards 和 /giftcards 在大多数服务器上是不同的 URL,而有人会把其中一个输错。 Zalando RESTful API 指南 将 kebab-case 设为 MUST 规则,并且他们已在数百个内部服务中推行过这套实践。
选择一种 JSON 大小写风格并记录下来
REST API 命名约定:实用风格指南
对于请求体和响应体中的字段名,实话是:camelCase 和 snake_case 都可以。不可取的是混用它们。
{ "orderId": 42, "createdAt": "2026-08-30T09:15:00Z", "totalAmount": 4999 }
{ "order_id": 42, "created_at": "2026-08-30T09:15:00Z", "total_amount": 4999 }
应(任选其一,并保持一致):
{ "orderId": 42, "created_at": "2026-08-30T09:15:00Z", "TotalAmount": 4999 }
不要:
camelCase 可与 JavaScript 和 Java 客户端自然映射。snake_case 更易于扫读,也与 Ruby、Python 以及大多数 SQL 列名一致;Stripe 到处都使用它。请根据主要使用你们 API 的人群来选择,然后将这一选择写入样式指南,这样争论只需进行一次,而不是每个 pull request 都要争论一次。混合大小写是实际 API 中最常见的不一致,因为不同团队会发布不同的端点。这是治理失败,而不是品味问题。
将嵌套限制为两层/users/42/orders嵌套表达了归属关系:
表示“属于用户 42 的订单”。这很有用。超过两层后,它就不再有用了。
GET /v1/users/42/orders
GET /v1/orders/1337/refunds
应:
GET /v1/users/42/orders/1337/refunds/7/status
不要:/refunds/7深层嵌套迫使客户端携带每个祖先 ID 才能访问叶资源,即使叶资源本身拥有全局唯一的 ID。如果某个退款的 ID 是 7,就将其暴露在 /orders/1337/refunds/7 或 /orders/1337 并到此为止。一个很好的判断标准是:如果 URL 包含三个或更多 ID,就将其扁平化。一旦订单存在,路径中就不需要包含其所属用户;
将过滤、排序和分页放入查询参数中
路径用于标识资源。查询参数用于修改资源的查看方式。永远不要将过滤条件编码到路径中。
应该:
GET /v1/orders?status=active&sort=-created_at&limit=50&cursor=eyJpZCI6NDJ9
GET /v1/products?category=electronics&min_price=1000
不要:
GET /v1/orders/active
GET /v1/orders/sorted-by-date-desc
GET /v1/getOrdersByStatusAndDate
该 sort=-created_at 模式(降序时去掉前缀)源自 JSON:API 规范,并省去了第二个 order=desc 筛选路径,例如 /orders/active 看似无害,直到你需要组合筛选条件;那时你会为每种组合都创建一个新端点。分页参数名称也应遵循同样的规范:选择 limit/cursor 或 page/per_page 确定一次,并在每个集合中重复使用。我们的 API 分页指南 深入介绍了游标与偏移量之间的权衡;这里的命名规则很简单:保持一致即可。
路径中的版本
主要有两种选择:路径片段(/v1/products)或请求头(Accept: application/vnd.myapi.v1+json). 请求头版本控制更符合“纯粹”的 REST,因为 URL 在不同版本中始终为同一资源命名,而 Google API 设计指南 指出这两种方式在实际应用中都存在。但从运维角度看,路径版本控制更胜一筹:它会显示在每一行日志中,可以通过浏览器进行测试,可以缓存,而无需 Vary 体操,并且客户端不可能忘记。每个调试过因缺少版本 header 而导致的“在 curl 中正常、在 prod 中失败”问题的开发者,都知道另一种做法的代价。使用 /v1/ 仅使用主版本号,不使用 /v1.2/;次要变更应采用增量方式,且不得破坏兼容性。有关包括内容协商在内的完整决策树,请参阅我们对 API 版本控制策略.
将资源 ID 视为不透明值,不要不加考虑地泄露连续整数
/orders/41, /orders/42, /orders/43: 连续整数 ID 会让任何查看者准确知道你处理了多少订单,还会引发枚举攻击,攻击者会遍历 ID 空间,探测授权缺口。这类漏洞,即对象级别授权失效,位列OWASP API Security Top 10.
应该:
GET /v1/orders/ord_9f8e2a71b3
GET /v1/users/550e8400-e29b-41d4-a716-446655440000
不要(需要防止枚举时):
GET /v1/orders/42
GET /v1/invoices/10883
带前缀的随机 ID,例如 Stripe 的 ord_9f8e2a71b3 是最稳妥的模式:不可猜测、在日志中具有自描述性,并且可以安全地对外暴露。无论采用哪种方式,授权检查仍然是必需的。不透明 ID 可以缩小遗漏检查所造成的影响范围,但不能替代检查。在内部,你仍可以保留整数主键;这条规则针对的是你在 URL 中对外暴露的内容。
将非 CRUD 操作建模为控制器资源
迟早你会需要一个无法与 CRUD 清晰对应的操作:取消订单、重试付款、重新发送电子邮件。不要通过状态字段上的 PATCH 来实现它,也不要在顶层放置动词。
应该:
POST /v1/orders/42/cancel
POST /v1/payments/pay_88a1/retry
不要:
PATCH /v1/orders/42 { "status": "cancelled" }
POST /v1/cancelOrder { "orderId": 42 }
这就是控制器模式,也是“不使用动词”规则唯一获准的例外:动词位于路径末尾,并限定在其所作用的资源之下。使用 PATCH 的方式看似符合 RESTful 风格,却把状态机隐藏在字段更新中。取消订单会触发退款、释放库存并发送通知;假装它只是一次字段写入,会迫使服务器通过比较请求载荷差异来检测意图。一个 /cancel 端点会表明意图,为该操作提供独立的权限和审计跟踪,并为取消原因等特定于操作的输入留出空间。
保持请求头和查询参数的大小写一致
两个更小的表面遵循同样的规范。自定义请求头使用 Hyphenated-Pascal-Case,符合 HTTP 惯例: Idempotency-Key, Request-Id。跳过旧的 X- 前缀;它已于 2012 年被 RFC 6648 弃用。请求头名称在传输过程中不区分大小写,但文档和 SDK 仍应统一采用一种拼写方式。
查询参数应与 JSON 请求体使用相同的大小写方式。如果请求体使用 snake_case,请写 ?min_price=1000&created_after=2026-01-01,而不是 ?minPrice=1000。开发者在响应中看到 created_at 并且必须在查询中输入 createdAfter 就会在第一次尝试时弄错,之后的每个人也一样。
完整规则集速览
| # | 规则 | 应该 | 不要 |
|---|---|---|---|
| 1 | 集合使用复数名词 | /products, /products/89 |
/getProducts, /productList |
| 2 | 路径中不使用动词 | DELETE /orders/42 |
POST /deleteOrder/42 |
| 3 | kebab-case 路径片段 | /gift-cards |
/giftCards, /gift_cards |
| 4 | 统一的 JSON 大小写格式,并在文档中说明 | order_id 处处 |
orderId,且order_id 混用 |
| 5 | 最多两层嵌套 | /orders/1337/refunds |
/users/42/orders/1337/refunds/7 |
| 6 | 在查询参数中进行过滤和分页 | ?status=active&sort=-created_at |
/orders/active |
| 7 | 在路径中包含主版本号 | /v1/products |
/v1.2/products, 版本请求头 |
| 8 | 不透明的资源 IDs | /orders/ord_9f8e2a71b3 |
/orders/42 (公开、可枚举) |
| 9 | 用于操作的控制器模式 | POST /orders/42/cancel |
PATCH 与 {"status":"cancelled"} |
| 10 | 请求头和参数的大小写保持一致 | Idempotency-Key, ?min_price= |
X-IDEMPOTENCY_KEY, ?minPrice= 混杂其中 |
在规模化场景下强制执行规范
将样式指南放在 wiki 中并不会改变任何事情。那些 API 始终保持一致的团队都有一个共同习惯:他们先进行设计,并在代码出现之前强制执行这些规范,这正是 API 治理 在实践中的核心。
常见问题
REST URL 应使用复数还是单数?
复数,适用于包含多个实例的任何资源:/products, /orders, /users。复数形式对于集合(/orders)和单个成员(/orders/42)。真正的单例才使用单数名称,例如 /users/42/cart。如果你想深入了解资源建模背后的深层原理,请参阅我们关于 什么是 REST API 的指南,它从第一性原理出发对此进行了讲解。
对于 JSON 字段名,camelCase 还是 snake_case 更好?
单论优劣,两者都没有绝对优势。camelCase 适合以 JavaScript 为主的使用者;snake_case 更易读,也与 Python、Ruby 以及 Stripe 的公共 API 保持一致。真正有约束力的规则是:选定一种,将其写入风格指南,并在模式审查中强制执行。在各个端点之间混用大小写,造成的问题比选择任一种写法都更大。
我应该把 API 版本放在 URL 中,还是放在请求头中?
使用路径(/v1/orders)除非你有很强的超媒体需求。路径版本会在日志、缓存和浏览器测试中直接显现,无需客户端做任何额外工作。请求头版本控制能让不同版本使用的 URL 保持稳定,但客户端忘记请求头时会静默失败。仅使用主版本;次要变更应作为增量且不破坏兼容性的更新发布。
在 REST API 路径中,动词是否可以接受?
可以,但仅限一个地方:用于非 CRUD 操作的控制器端点,例如 POST /orders/42/cancel 或 POST /payments/pay_88a1/retry. 动词位于路径末尾,并限定在其资源范围内,且方法始终为 POST。其他所有情况下,HTTP method 承载动词,而路径始终只包含名词。
开发必备:API 全流程管理神器 Apifox
介绍完上文的内容,我想额外介绍一个对开发者同样重要的效率工具 —— Apifox。作为一个集 API 文档、调试、设计、测试、Mock、自动化测试于一体的工具,Apifox 是目前提升研发效率的首选。
如果你正在开发项目,不妨试试其极其友好的界面设计,它完全兼容 Postman 和 Swagger 数据格式,导入数据非常方便,,即使是新手也能很快上手,点击这里即可注册使用。

值得一提的是,除了个人和常规团队使用,针对有高安全合规要求、或需要在内网环境协作的企业,Apifox 还提供了深度定制的私有化部署方案。
获取专属报价与部署方案
详细的私有化部署系统架构与安全白皮书
针对您公司规模的专属报价单
免费的 1v1 专属产品演示 (Demo) 机会