关于 rest-api-design
rest-api-design 是一项用于设计 REST/HTTP API 资源、行为和契约的技能,面向需要建立一致、可演进 API 的工程师。它提供状态码、错误格式、分页、版本与安全等设计规范,并将设计映射为 OpenAPI 3.1 契约。
主要功能
- 01按资源名词设计一致的 URL
- 02选择 HTTP 方法、状态码与幂等策略
- 03采用 RFC 9457 错误模型与统一分页
- 04制定版本、认证和限流方案
- 05将设计映射到 OpenAPI 3.1
适用场景
- 01从领域实体规划新的 REST API
- 02统一已有 API 的错误和分页格式
- 03确定请求结果对应的状态码
- 04把已定 API 设计整理成 OpenAPI 契约
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ bm629/ agent-skills --skill rest-api-design安装后在 Claude Code 中输入/rest-api-design确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/contract-patterns.mderror-model.mdopenapi-mapping.mdsources.md详细介绍
rest-api-design — SKILL.md
变体: standard · 使用时机: 调用此技能时,它会将以下设计决策应用于资源界面,并返回一份一致的契约;随后控制权交还给调用方。
概述
此技能是 REST/HTTP API 及其契约的设计规范,位于任何 Web 框架之上。它包含资深工程师在将一组领域名词转化为一致且可演进的 HTTP 界面时采用的明确默认做法:如何命名资源、每种操作采用哪种方法和状态码、统一的机器可读错误格式(RFC 9457 problem+json)、结构化的成功响应和分页封装、版本管理策略、身份验证和速率限制的处理方式,以及如何将这一切呈现为 OpenAPI 3.1 契约。示例代码采用 Python/FastAPI + Pydantic v2 风格,但所有决策都与语言无关。框架机制(路由器、依赖注入、response_model)和 Schema 建模属于 fastapi 和 pydantic-v2 技能的范畴——本技能会引用它们,但不会重复说明。
何时启用
- ✅ 根据一组领域实体设计新的 API 界面——确定端点、方法和契约。
- ✅ 为某个结果选择正确的状态码,或决定使用 400 还是 422、401 还是 403、PUT 还是 PATCH。
- ✅ 定义或修正 API 的错误模型、成功响应封装或分页方案。
- ✅ 决定 API 的版本管理、身份验证或速率限制策略。
- ✅ 将达成一致的 REST 设计转化为 OpenAPI 3.1 契约。
不要在以下情况启用:
- 为已设计的端点编写 FastAPI 处理器/路由器/依赖注入代码 → 使用
fastapi技能。 - 深入建模请求/响应数据类 → 使用
pydantic-v2技能。 - 编写或生成大型 OpenAPI 文档、检查其规范,或运行 SDK 代码生成 → 这些属于专门的 OpenAPI 编写/代码生成工作(参见
references/openapi-mapping.md,了解设计到契约的映射及交接方式)。 - 设计 GraphQL 或 gRPC 界面 → 不在适用范围内;本技能仅适用于 REST/HTTP。
工作流
按顺序执行以下步骤。每个步骤都说明了一项明确的默认做法;只有给出理由时才偏离。
步骤 1:建模资源(用名词,而非动词)
- 资源是名词;HTTP 方法才是动词。使用
POST /orders,绝不使用POST /createOrder。 - 集合名称统一使用复数:
/users、/users/{id}、/users/{id}/orders。绝不混用/user和/users。 - 嵌套最多 2 层。
/users/{id}/orders没问题;/users/{id}/orders/{oid}/items/{iid}则不行——将深层子资源提升为顶层资源(/order-items/{iid}),并建立关联。 - 选择 URL 前,先根据领域名词及其关系识别资源。子资源表示包含/所有权关系;横切实体则应成为独立的顶层资源。
- URL 路径段使用小写字母和连字符(
/order-items),不要使用camelCase或snake_case。 - 不要泄露会暴露行数或可枚举信息的内部/主键 ID——公开契约应优先使用 UUID 或不透明 ID。
第 2 步:分配方法并遵守幂等性
根据 RFC 9110 HTTP 语义:
| 方法 | 用途 | 安全 | 幂等 |
|---|---|---|---|
| GET | 读取资源/集合 | 是 | 是 |
| POST | 在集合中创建资源;执行非幂等操作 | 否 | 否 |
| PUT | 在已知 URL 上完整替换资源 | 否 | 是 |
| PATCH | 部分更新(仅更新发送的字段) | 否 | 否 |
| DELETE | 删除资源 | 否 | 是 |
- PUT 用于替换,PATCH 用于合并。 PUT 请求正文是完整的新表示;未提供的字段会被清除。PATCH 请求正文只包含要更改的字段。部分更新不要使用 PUT。
- POST 和 PATCH 不具备幂等性:重试 POST 会创建重复项。对于客户端可能重试的创建/扣款操作,应接受
Idempotency-Key请求头(Stripe 采用的约定,现已成为 IETF 草案)——存储密钥和首次响应,并在收到重复请求时重放该响应,这样网络重试就是安全的。 - GET 不得产生副作用。如果某项操作会修改状态,就不属于 GET。
第 3 步:选择与结果相符的状态码
| 代码 | 含义/适用情况 |
|---|---|
| 200 OK | GET/PATCH/PUT 成功,或 DELETE 成功且返回正文 |
| 201 Created | 创建成功——设置 Location 响应头,指向新资源的 URL |
| 204 No Content | 操作成功且无正文(通常用于 DELETE,或不返回内容的 PUT/PATCH) |
| 400 Bad Request | 请求格式错误——JSON 无法解析、结构不正确、查询编码错误 |
| 401 Unauthorized | 未通过身份验证——缺少或凭据无效 |
| 403 Forbidden | 已通过身份验证,但无权执行此操作 |
| 404 Not Found | 资源不存在(或对当前调用方隐藏) |
| 409 Conflict | 请求与当前状态冲突——唯一字段重复、版本冲突 |
| 422 Unprocessable Content | 语法有效,但语义无效——业务/字段验证失败 |
| 429 Too Many Requests | 超出速率限制——包含 Retry-After |
| 5xx | 服务器故障——500 表示一般错误,503 表示服务不可用;绝不要归咎于客户端 |
- 400 与 422: 400 表示服务器根本无法解析/理解请求;422 表示服务器成功解析请求,但其中的值未通过验证。新 API 默认使用 422 表示验证失败,400 表示解析失败。
- 401 与 403: 401 表示“你是谁”(需要重新进行身份验证);403 表示“我知道你是谁,但你仍无权执行此操作”。
- 绝不要在错误正文中返回 200。 状态码是首要的机器可读信号——将失败包装在 200 响应中会破坏所有客户端、代理和监控系统。
第 4 步:定义一种错误模型——RFC 9457 problem+json(默认)
每个错误响应都使用 application/problem+json(RFC 9457,取代了 RFC 7807),并包含以下成员:
type— 用于标识问题类型的 URI(稳定且有文档说明;如果问题仅由状态码表示,则默认为"about:blank")。title— 对问题类型的简短说明(对于about:blank,使用 HTTP 状态短语,例如"Not Found")。status— HTTP 状态码,在正文中重复提供。detail— 对此次问题的可读说明。instance— 用于标识此次问题的 URI(例如请求路径或跟踪 ID)。- 扩展成员 — 其他任何领域字段;客户端 MUST 忽略不认识的成员。字段级验证错误放在
errors数组中(RFC 认可的扩展)。
{
"type": "https://api.example.com/problems/validation-error",
"title": "Your request parameters did not validate.",
"status": 422,
"detail": "The request body has 2 invalid fields.",
"instance": "/orders",
"errors": [
{ "detail": "must be a positive integer", "pointer": "#/quantity" },
{ "detail": "must be a valid email", "pointer": "#/customer/email" }
]
}
- 为整个 API 选择一种模型,绝不要混用。RFC 9457 是默认选择,因为它是一种标准、可机器解析且自描述的格式。
- 替代方案(仅在项目规范强制要求时使用): 封装格式
{"error": {"code": "...", "message": "...", "details": [...]}}。这种格式可用,但不是标准;新 API 应优先使用 problem+json。不要同时使用两种格式。 - FastAPI 用法:注册一个返回
application/problem+json的异常处理器;将RequestValidationError映射为包含errors数组的 422 问题响应。参见references/error-model.md。
步骤 5:将成功响应和分页整合为一个信封
- 单个资源:返回该资源的表示形式。如果对所有地方都采用轻量且一致的包装形式(
{"data": {...}}),也完全可以——一致性胜过巧思。 - 集合始终分页——绝不返回无界列表。默认
limit为 20,硬上限为 100;超过上限则返回 422。 - 所有列表端点都使用同一种信封结构:
data(项目)+pagination/meta(游标/计数)+ 可选的links(导航 URL)。 - 对大型、不断变化或类似信息流的数据,默认使用游标(键集)分页:插入数据时保持稳定,深分页耗时固定,且无需昂贵的总数统计。只有对于需要随机跳转页面或总数的小型稳定数据集,才使用偏移量/页码分页——并发写入时它会发生偏移(漏项/重复),深分页也会变得非常慢。
- 两种信封结构以及游标与偏移量的选择,请参阅
references/contract-patterns.md。
步骤 6:决定版本控制、身份验证和速率限制
- 版本控制——默认使用 URL 路径:
/v1/users。清晰可见、易于调试、便于缓存,并允许多个版本并行运行。若整洁的 URL 比易于发现更重要,可选择基于请求头/媒体类型的版本控制。 - 仅在发生破坏性变更时才提升主版本号(删除/重命名字段、更改类型、添加必填字段,或更改同一场景下的状态码)。增量变更(新增端点、新增可选字段)绝不提升主版本号——应将客户端设计为容忍型读取器,忽略未知字段。
- 使用
Deprecation和Sunset请求头标示弃用(RFC 8594);日落后返回410 Gone。 - **身份验证属于契约的一部分,而非事后补充:**面向用户的 API 默认使用
Authorization: Bearer <token>(JWT/OAuth);服务间调用使用 API 密钥请求头(例如X-API-Key)。**始终使用 HTTPS。**在 OpenAPI 中,这些对应于securitySchemes和security要求(可按操作配置,也可全局配置)。 - **速率限制:**返回
429+Retry-After(稳定且符合 RFC-9110 的默认方案)。使用广泛部署的X-RateLimit-Limit/-Remaining/-Reset约定公布配额;IETF 的RateLimit/RateLimit-Policy字段是正在形成的标准,但尚未成为 RFC——429+Retry-After是所有客户端都能理解的安全基线。
步骤 7:用 OpenAPI 3.1 契约表达设计
交付内容是一份可直接映射设计的 OpenAPI 3.1 文档:资源→paths,方法→操作,信封/错误/实体结构→components/schemas,复用的响应/参数通过 $ref 对应到 components/responses+components/parameters,身份验证→components/securitySchemes。使用 3.1(完整支持 JSON Schema 2020-12:可空值使用 type: ["string","null"],而非旧版的 nullable)。每项设计提供一份完整示例契约,到此为止——深入编写 OpenAPI、校验、模拟服务和 SDK 代码生成属于其他范畴。完整的设计→契约映射及一份完整示例契约见 references/openapi-mapping.md。
规则
硬性规则(绝不违反):
- 整个 API 采用一种错误模型;默认使用 RFC 9457 problem+json,以
application/problem+json提供。绝不混用两种错误格式。 - 绝不在响应正文包含错误时返回
200(或任何 2xx)。状态码就是契约。 - 资源使用名词;路径中绝不使用动词。集合名称使用复数,并保持一致。
- 每个集合端点都必须分页,并强制执行最大页大小限制。
- 始终使用 HTTPS;在契约中声明身份验证(
securitySchemes+security),绝不隐式处理。 - PUT 用于完整替换,PATCH 用于部分更新;绝不混淆两者。
- 契约使用 OpenAPI 3.1(JSON Schema 2020-12);通过
"null"类型成员表示可空性,不使用已移除的 3.0nullable关键字。
偏好(可调整):
- 默认采用游标/键集分页;仅对小型、稳定且支持随机访问的数据集使用偏移分页。
- 默认采用 URL 路径版本控制;仅使用主版本号,向后兼容的新增内容不提升版本。
- 用户 API 使用 Bearer/JWT,服务间调用使用 API 密钥。
- 以
429+Retry-After作为速率限制基线;添加X-RateLimit-*提示性标头。 - 资源嵌套最多 2 层;将更深层的子资源提升为顶层资源。
易错点
- 将 PATCH 当作完整替换会悄无声息地清除客户端未发送的字段。PATCH 必须只合并提供的字段;如果需要替换语义,请使用 PUT。
- 正文包含错误但状态码为 200在浏览器中看似正常,却会破坏基于状态码的重试逻辑、缓存和监控。务必设置真实状态码。
- **偏移分页漂移:**并发插入/删除时,
?offset=会导致不同页面之间漏掉或重复行;偏移量越大,速度也越慢(数据库会扫描并丢弃所有跳过的行)。游标/键集分页可以避免这两个问题。 - **混淆 422 和 400:**对于已成功解析但未通过业务验证的值返回 400,会丢失客户端用于分支处理的信息。将 400 留给“完全无法解析”的情况。
- **OpenAPI 3.1 架构中的
nullable: true**会被悄无声息地忽略——这是 3.0 的关键字。请使用type: ["string", "null"]。验证器不一定会发出警告。 - **假设 POST 具有幂等性:**客户端(和代理)可能会重试超时的 POST,从而创建重复项。如果预期会发生重试,请要求提供
Idempotency-Key,并在服务端进行去重。 - 泄露连续的内部 ID(
/users/1001)会暴露行数并使枚举成为可能。请在公开接口中使用不透明 ID/UUID。
反模式
- “我们就返回 200,然后在正文里放个
ok: false”——不行。状态码是机器可读的契约。 - “动词更清楚,所以
/getUser没问题”——不行。方法是动词;路径是名词。 - “每个子系统使用一种错误格式”——选择一种(problem+json),并在所有地方统一使用。
- “跳过分页吧,这个集合很小”——集合会不断增长;无界列表是潜在的故障隐患。
- “为每个端点单独做版本控制/在 URL 中使用次版本号”——对整个 API 进行版本控制,且仅使用主版本号。
- “在这里重新讲解 FastAPI 路由/Pydantic 建模”——请参阅
fastapi和pydantic-v2技能;本技能负责设计,而不是框架代码。
输出
此技能会生成一份REST API 设计及其 OpenAPI 3.1 契约:资源/URL 映射、每项操作对应的方法和状态码决策、统一的错误模型(problem+json)和成功/分页封套、版本控制/身份验证/速率限制方案,以及一份完整示例 OpenAPI 3.1 文档来表达这些内容。抽象的使用者是负责实现处理程序的工程师(或编码 Agent)——使用 fastapi 和 pydantic-v2 技能——以及依据已发布契约进行集成的下游客户端。
相关内容
fastapi— 框架机制(路由器、依赖注入、response_model、异常处理器)。本技能负责确定契约;fastapi负责实现。pydantic-v2— 请求/响应模式建模。本技能负责命名数据结构;pydantic-v2负责对其建模。- OpenAPI-authoring / SDK-codegen 工具 — 更深入的契约编写、 lint、模拟服务和客户端生成(属于本技能交由其他工具处理的独立事项)。
references/error-model.md、references/contract-patterns.md、references/openapi-mapping.md— 加载条件见下文。
渐进式披露
大量内容位于 references/ 中,仅按需加载:
references/error-model.md— 定义或修复错误模型时加载:RFC 9457 逐成员说明、errors验证数组、about:blank默认值、404 和 422 的 problem+json 示例、FastAPI 异常处理器模式,以及用于对比的非标准{error:{...}}替代方案。references/contract-patterns.md— 设计响应或分页时加载:成功响应封装、包含两种响应结构的游标分页与偏移分页决策表、版本生命周期(Deprecation/Sunset/410)、身份验证方案和速率限制标头。references/openapi-mapping.md— 将设计表述为契约时加载:设计→OpenAPI-3.1 元素映射、3.1 与 3.0 的说明(JSON Schema 2020-12、null 类型、webhook),以及一个包含 problem+json 响应和安全方案的完整契约示例。references/sources.md— 研究来源 / 引用。
正文预算
description≤ 1,024 个字符(agentskills.io 上限)。- 正文 ≤ 约 500 行 / 5,000 个 token — 每轮都保留在上下文中。
- 每个参考文件:超过 10k 个 token 时警告,超过 25k 个 token 时报错。参考文件总量:超过 25k 个 token 时警告,超过 50k 个 token 时报错。