关于 design-api
一项将需求推进到用户故事、OpenAPI 生成及可选 lint 的 API 设计技能,面向使用 LLM 设计 API 的人员。它区分必需的输入文档与可选故事,并在生成规范前提供审阅环节。
主要功能
- 01从需求到规范的工作流
- 02生成故事并设置审阅环节
- 03生成 OpenAPI 3.1.0
- 04验证架构与引用
- 05可选自动 lint
适用场景
- 01将 API 需求整理为待审阅的故事
- 02根据已批准故事生成规范
- 03检查 OpenAPI 文档的结构问题
- 04在具备 Node.js 时本地预览规范
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ paraskakis/ skills --skill design-api安装后在 Claude Code 中输入/design-api确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/examples/API-standards.mdconference-domain.mdconference-research.mdOpenAPI-best-practices.md详细介绍
设计 API
端到端设计 API:从需求到用户故事、OpenAPI 规范、lint 检查和迭代。这是 Emmanuel Paraskakis 使用大模型设计 API 的方法。
四个基础文件(需求、领域、标准、OpenAPI 最佳实践)始终必需。用户故事是可选的——如果提供,它们代表人的设计决策,技能会直接进入规范生成阶段。如果未提供,技能会先生成用户故事,然后暂停以供审核。
范围与路由
触发条件由 frontmatter 描述定义。范围内:从需求到 lint 检查后的规范的完整流程。范围外:用户只需要一种产物时——改用对应的单项技能(design-api-stories、design-api-spec 或 design-api-review)。
在当前流程中执行各阶段。 下文各阶段概述了相关的同级技能(design-api-stories、design-api-spec)。不要将这些技能作为单独的技能调用重新执行——本技能自身的信息收集会取代它们。如果已安装同级技能的 SKILL.md 文件,而某阶段的概要不足以完成任务,请查阅这些文件;否则,此处内容足以执行。
可选依赖项
核心技能(用户故事 + 规范生成)无需额外工具。若要获得完整体验:
- RateMyOpenAPI API 密钥 — 支持自动 lint 检查和评分。可在 api.ratemyopenapi.com/docs 免费注册。将其设为
RMOA_API_KEY环境变量。 - Node.js — 可通过
npx http-server在本地预览 Swagger UI。 - curl + python3 — 用于调用 RMOA API。几乎总是已预装。
如果没有这些依赖项,技能仍会生成完整的 OpenAPI 规范——只是会跳过 lint 检查和本地预览。
输入
用户必须提供(或指明)以下文件。文件可以使用任意名称——根据 # 标题 标题或文件内容识别各文件,而不是根据文件名。
始终必需:
- 需求 — ICP、需求、痛点、用例、收益。“为什么”以及“是什么”。查找类似
# Requirements、# Research的标题。 - 领域 — 数据对象、属性、枚举、对象关系。作为 schema 的事实来源。查找类似
# Domain、# Domain Model的标题。 - API 标准 — 风格指南、命名、路径、安全性、错误处理。作为约定的事实来源。查找类似
# API Standards、# Style Guide的标题。 - OpenAPI 最佳实践 — 格式规则、DRY 原则、SDK 兼容性、组件结构。作为 OpenAPI 文档质量的事实来源。查找类似
# OpenAPI Best Practices、# OAS Best Practices的标题。
可选(决定入口):
5. 用户故事 — 包含方法、资源和参数的 API 用户故事。它们体现人的设计决策,包括资源嵌套、端点分组和参数选择。查找类似 # Stories、# User Stories 的标题。如果提供 → 跳到阶段 2(规范)。如果未提供 → 阶段 1 会生成用户故事供审核。
如果缺少任何必需文件,请先请用户提供。
如果用户没有自己的文件,请使用 references/examples/ 中附带的会议示例。不要另行编造领域。 用户没有表达偏好,并不意味着可以自行编造——请端到端运行会议排期 API,让用户看到这套方法如何应用于一组完整且已验证的输入。如果用户明确指定某个领域,则可自行构造该领域。
附带示例
references/examples/ 中包含一套完整的示例输入:
conference-research.md— 需求conference-domain.md— 领域模型API-standards.md— API 标准OpenAPI-best-practices.md— OpenAPI 最佳实践
阶段 1:用户故事(如果已提供用户故事,则跳过)
步骤 1:读取输入
完整阅读需求、领域和标准。了解:
- API 的使用者是谁(来自需求)
- 存在哪些实体及其关系(来自领域)
- 应遵循哪些约定(来自标准)
步骤 2:确定主题
将需求中的用例归类为逻辑主题。每个主题对应一组相关的 API 操作。此处不要停下——立即继续执行步骤 3。
例外:如果用户明确要求分步审查,则先展示主题并等待。
步骤 3:生成故事(默认 = 简单)
默认情况下,生成简单故事——内容仅需足以审查 API 结构并验证设计方向。
简单故事格式(默认)
对于每个故事,仅输出:
#### Story [Theme].[Number]: [Name]
**As a** [actor from requirements],
**I want** to [action],
**So that** [business value].
* **Method:** `GET` / `POST` / `PUT` / `PATCH` / `DELETE`
* **Resource:** `/path/{id}/subresource`
* **Parameters:**
* `paramName` (type, required/optional): Brief description.
简单故事规则:
- 方法和资源必须遵循 API 标准(命名、嵌套、复数形式)
- 参数包括路径参数、查询参数和请求正文中的字段
- 将每个参数标记为必填或可选
- 严格使用领域对象名称和属性名称
- 不要包含响应架构、错误代码、标头或实现细节
- 除非标准规定了特殊要求,否则不要包含身份验证详情
详细故事格式(按需)
如果用户要求提供详细故事(例如 --detailed),则添加:
- 成功响应摘要(状态代码 + 返回内容)
- 错误响应(状态代码 + 发生条件)
- 标头(速率限制、分页游标)
- 分页方式说明
步骤 4:审查检查点
一次性展示所有故事,然后询问:
“以下是故事。在我生成规范之前,需要修改吗?还是可以继续?”
- 如果用户提出修改 → 纳入修改,重新展示并再次询问。
- 如果用户表示可以继续 → 立即将最终故事保存到文件(在单独的回复中,并在进行任何规范生成思考之前),然后进入第 2 阶段。
- 如果用户传入
--no-pause→ 跳过检查点并直接继续。
重要:始终先将最终审查通过的故事保存到文件,再生成规范。保存的故事是第 2 阶段的事实来源——规范必须体现用户在审查期间提出的任何修改。
先保存故事,再生成规范——分开回复。 用户批准故事后,立即通过独立的工具调用将其写入文件。不要将保存故事与生成规范合并处理——规范需要大量推理时间,合并处理会不必要地延迟故事的保存。先保存故事能向用户展示进度,并在规范生成失败时为其提供检查点。
将故事保存到用户指定的文件中;如果用户未指定,则询问他们希望保存到哪里。
直接写入——不要先检查或读取现有文件。 如果写入工具因名称冲突而失败,则在文件名后追加数字并重试。
第 2 阶段:规范
步骤 5:读取所有输入
完整读取故事、领域、标准和 OpenAPI 最佳实践。建立以下内容的整体理解:
- 故事中的每个端点(方法 + 资源 + 参数)
- 领域中的每个数据对象和枚举
- 标准中的每项约定
- 最佳实践中的每项格式规则
步骤 6:生成 OpenAPI 文档
输出一个有效的 JSON 文件。除非用户指定其他版本,否则默认使用 OpenAPI 3.1.0。
结构
按以下顺序排列文档内容:
openapi— "3.1.0"(或用户指定的版本)info— 标题、描述、版本、联系信息、许可证(遵循最佳实践)servers— 至少包含一个带有描述的服务security— 如果标准要求身份验证,则添加全局安全方案tags— 每个主题对应一个标签paths— 故事中的所有端点components— schemas、parameters、responses、headers、securitySchemes
Schema 生成规则
- 每个领域对象都要成为一个组件架构。 使用领域文件中的确切属性名称。
- 每个枚举都要成为一个组件架构。 不要内联枚举。
- 集合响应需要有自己的架构。 例如,
InstrumentCollection包含data数组和分页字段。 - 错误响应使用 RFC 9457(Problem Details), 除非标准另有规定。
- 不要使用内联架构。 将所有内容提取到
components/schemas,并通过$ref引用。 - 可复用参数放在
components/parameters中。 分页参数(cursor、limit)、常用筛选条件等应定义一次,并在各处引用。 - 可复用响应对象放在
components/responses中。 错误响应(400、401、404、429、500)应按不同结构各定义一次。 - 可复用标头放在
components/headers中。 例如速率限制标头等。
内容规则
- 每个操作、参数、架构、属性、响应和标头都必须有非空的
description。 - 每个架构和参数都必须有
example值。使用领域文件中的真实示例。 - 每个操作都必须有
summary和operationId。 - 错误示例必须与错误类型相符(429 示例应描述速率限制,而不是“错误请求”)。
- 积极使用
$ref。文档应遵循 DRY 原则。
命名规则
- operationId:使用与故事中的操作匹配的 camelCase 动词(例如,
searchInstruments、getInstrumentByFigi) - 标签:与故事中的主题名称匹配
- 架构:使用与领域对象名称匹配的 PascalCase
- 参数:使用与领域属性名称匹配的 camelCase
不要添加的内容
- 不要添加故事中没有的端点
- 不要添加领域中没有的属性
- 不要添加标准中没有的安全方案
- 如果故事很简单(未列出错误代码),仍需添加标准错误响应(集合端点带筛选条件时添加 400,单资源端点添加 404,要求身份验证时添加 401,添加 429 速率限制,以及 500 服务器错误)
第 7 步:验证 JSON
写入文件之前:
- 确保输出是有效的 JSON(没有尾随逗号或重复键)
- 确保每个
$ref都指向一个存在的组件 - 确保每个组件至少被引用一次(没有孤立组件)
- 确保不使用
nullable关键字(按照 3.1.0 的规定,改用"type": ["string", "null"])
第 8 步:保存规范
将规范保存为 JSON,保存到用户指定的位置;或者询问用户希望保存到哪里。无需遵循特定的命名约定。
直接写入——不要先检查或读取现有文件。 如果 Write 工具因名称冲突而失败,则在名称后追加一个数字并重试。
第 9 步:使用 RateMyOpenAPI 进行 Lint 检查(可选)
此步骤需要 RateMyOpenAPI 的 API 密钥。在 api.ratemyopenapi.com/docs 注册免费账户——登录后,API 密钥会显示在 Authentication 部分。将其设为环境变量 RMOA_API_KEY(也可以使用其他名称——只需相应更新下面的 curl 命令)。
如果未设置 RMOA_API_KEY,请先尝试加载用户的 shell 配置文件(source ~/.zshrc、source ~/.bashrc 或同等文件)——密钥可能已在其中定义,只是尚未载入当前会话。如果仍未设置,请告知用户:“未找到 RMOA API 密钥。跳过自动 Lint 检查。你可以在 ratemyopenapi.com 获取免费密钥,并设置 RMOA_API_KEY 以启用此功能。”然后跳到第 10 步。
如果已设置 RMOA_API_KEY,请上传规范并获取评分和完整的问题列表。
先将响应写入文件(不要将 curl 的输出通过管道传给 python3 -c)。RMOA 响应可能在 JSON 字符串中包含原始控制字符,Python 严格的 JSON 解析器从标准输入读取时会拒绝此类字符。先保存到文件,再使用 strict=False 解析,可避免此问题以及 shell 对字节的篡改。
curl -s -X POST \
-H "Authorization: Bearer $RMOA_API_KEY" \
-F "apiFile=@[/absolute/path/to/spec.json]" \
-o /tmp/rmoa-response.json \
-w "HTTP:%{http_code} SIZE:%{size_download}\n" \
"https://api.ratemyopenapi.com/sync-report"
解析前,请检查 curl 调用是否返回 HTTP:200 且响应大小非零。如果不是,则问题出在 RMOA 本身——请重试或跳过 Lint 检查。
然后解析已保存的响应:
python3 -c "
import json
data = json.loads(open('/tmp/rmoa-response.json').read(), strict=False)
r = data['results']['simpleReport']
issues = data['results']['fullReport']['issues']
print(f'Score: {r[\"score\"]} | Docs: {r[\"docsScore\"]} | Completeness: {r[\"completenessScore\"]} | SDK: {r[\"sdkGenerationScore\"]} | Security: {r[\"securityScore\"]}')
print(f'Report: https://ratemyopenapi.com/report/{r.get(\"reportId\", \"\")}')
print(f'Total issues: {len(issues)}')
for i, issue in enumerate(issues, 1):
path = ' > '.join(str(p) for p in issue.get('path', []))
print(f'{i}. [{\"ERROR\" if issue[\"severity\"]==0 else \"WARN\"}] {issue[\"code\"]}')
print(f' {issue[\"message\"]}')
print(f' Path: {path}')
print()
"
根据问题列表完成以下所有修复。
解读结果
对四个类别评分(每项 0-100):
- 文档 — 描述、摘要、示例
- 完整性 — 必填字段、响应代码、架构
- SDK 生成 — operationId、无内联架构、正确的引用
- 安全性 — 身份验证方案、401/403 响应
目标:所有类别均达到 100/100。
处理问题
-
自动修复明显属于格式或完整性的问题(缺少描述、缺少示例、缺少错误响应)。
-
就涉及设计决策的问题询问用户。常见示例:
- 无身份验证/缺少 401:如果 API 有意不使用身份验证(例如训练练习),请告知用户 RMOA 已标记此问题,并询问他们是想忽略问题还是添加身份验证。
- 缺少错误代码:如果 RMOA 要求 401/403,但相关故事中不包含身份验证,请询问用户。
- 命名约定:如果 RMOA 标记的命名模式与标准文件冲突,以标准文件为准。请告知用户。
-
修复后重新运行。持续迭代,直到满足以下条件之一:
- 评分达到 100/100,或
- 剩余问题属于用户已批准忽略的有意设计选择
常见问题
- 无身份验证的 API 在安全性方面的得分约为 65。 RMOA 会针对缺少 401/403 标记 8 个以上警告。如果 API 有意不使用身份验证,这些警告是预期结果——请与用户确认后继续。
nullable关键字。 OpenAPI 3.1.0 已弃用nullable。请改用"type": ["string", "null"]。RMOA 会对此发出标记。- 缺少描述/示例。 这是最常见的扣分原因。每个操作、参数、架构属性和响应都需要同时提供描述和示例。也不要遗漏标头。
- 孤立组件。 RMOA 会标记在
components中定义但从未被$ref引用的架构。请将其删除或接入引用。 - 内联架构。 直接在路径中定义的任何架构(而非通过
$ref引用)都会拉低 SDK 生成评分。请将其提取到components/schemas。
第 10 步:报告
向用户显示:
## OpenAPI Spec Complete
**File:** [path]
**Score:** [Overall] / 100
- Docs: [score]
- Completeness: [score]
- SDK Generation: [score]
- Security: [score]
**Report:** [RMOA report URL]
**Issues ignored (by design):**
- [list any intentional deviations the user approved]
然后询问:“想在 Swagger UI 中预览吗?”
需要 Node.js(用于运行 npx http-server)和互联网连接(Swagger UI 从 unpkg.com CDN 加载)。如果未安装 Node,请告知用户:“Swagger UI 预览需要 Node.js。你可以将规范粘贴到 https://editor.swagger.io,在那里预览。”然后跳过预览。
如果用户回答是且 Node 可用,请在与规范相同的目录中创建 swagger-preview.html 文件:
<!DOCTYPE html>
<html>
<head>
<title>[API Name] — Swagger UI</title>
<link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css">
</head>
<body>
<div id="swagger-ui"></div>
<script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js"></script>
<script>
fetch('./[spec-filename].json')
.then(r => r.json())
.then(spec => {
SwaggerUIBundle({
spec: spec,
dom_id: '#swagger-ui',
deepLinking: true,
presets: [SwaggerUIBundle.presets.apis],
layout: "BaseLayout"
});
});
</script>
</body>
</html>
然后在本地提供服务并在浏览器中打开:
cd [spec-directory] && npx -y http-server . -p 8090 -c-1 &
sleep 2 && open http://localhost:8090/swagger-preview.html
默认使用端口 8090。如果端口已被占用,请尝试 8091、8092 等。
用户完成审阅后,询问:“预览完了吗?我将清理服务和预览文件。”
如果用户回答是:
- 终止 http-server 进程:
lsof -ti:8090 | xargs kill 2>/dev/null - 删除预览 HTML 文件:从规范目录中移除
swagger-preview.html - 确认:“服务器已停止,预览文件已移除。”
核心原则
-
故事是供人审阅的,不是工程规范。 保持易于浏览。产品经理应能查看方法、资源和参数,然后判断“对,这就是正确的 API”或“不对,我们缺少 X”。
-
严格遵循标准。 如果标准规定使用 camelCase,每个参数都必须使用 camelCase。如果标准规定资源名称使用复数,每个资源都必须使用复数。故事是标准落实的第一道关卡。
-
准确使用领域语言。 如果领域中使用
exchangeMic,参数就应为exchangeMic,而不是exchange_mic或exchangeId。领域定义是命名的唯一依据。 -
根据领域关系嵌套资源。 如果 Quote 属于 Instrument,资源应为
/instruments/{id}/quotes,而不是/quotes?instrumentId=X。遵循标准中的嵌套深度限制。 -
不要臆造需求。 如果需求中未提到删除或更新操作,就不要添加。如果你认为有所遗漏,应将其标记为问题,而不是擅自添加。
-
分页遵循标准。 如果标准指定基于游标的分页,则每个集合端点都应包含
cursor和limit参数。不要混用分页方式。 -
领域定义是架构的唯一依据。 属性名称、类型、枚举、关系等都必须与领域文件中的内容完全一致。
-
最佳实践决定 OpenAPI 的质量。 DRY、描述、示例、组件提取等方面,应以最佳实践文件为准,而不是为了图省事走捷径。
-
Lint 是验证手段,不是绝对准则。 RMOA 能发现实际问题,但也会标记有意为之的设计选择。由用户决定修复哪些问题、忽略哪些问题。
-
仅输出 JSON。 OpenAPI 使用 JSON,而非 YAML。这样既符合 RMOA 预期的输入格式,也可避免 LLM 输出的 YAML 格式问题。