api-design
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chinese<objective>
Comprehensive API design skill covering RESTful conventions, error handling, pagination, versioning, and documentation. Focuses on building consistent, intuitive, and maintainable APIs.
Good API design makes the right thing easy and the wrong thing hard. This skill helps you create APIs that are a pleasure to use and maintain.
</objective>
<quick_start>
Resource naming: Nouns, plural, lowercase, hyphenated (, )
/users/blog-postsHTTP methods: GET (read), POST (create), PUT (replace), PATCH (update), DELETE (remove)
Status codes: 200 OK, 201 Created, 204 No Content, 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found, 429 Rate Limited
Pagination: Prefer cursor-based for real-time data; use
?limit=20&cursor=xxxVersioning: URL path () is clearest approach
</quick_start>
/api/v1/<success_criteria>
API design is successful when:
- Resources use plural nouns with consistent naming
- HTTP methods match semantics (GET=read, POST=create, etc.)
- Error responses follow consistent format with code, message, details, requestId
- Pagination implemented (cursor or offset based)
- Rate limiting headers included (X-RateLimit-Limit, Remaining, Reset)
- Versioning strategy defined before breaking changes needed </success_criteria>
<core_principles>
<objective>
全面的API设计技能,涵盖RESTful规范、错误处理、分页、版本控制和文档编写。专注于构建一致、直观且可维护的API。
优秀的API设计会让正确的操作变得简单,错误的操作变得困难。本技能可帮助你创建易于使用和维护的API。
</objective>
<quick_start>
资源命名: 使用名词、复数形式、小写、连字符分隔(、)
/users/blog-postsHTTP方法: GET(读取)、POST(创建)、PUT(替换)、PATCH(更新)、DELETE(删除)
状态码: 200 OK、201 Created、204 No Content、400 Bad Request、401 Unauthorized、403 Forbidden、404 Not Found、429 Rate Limited
分页: 实时数据优先使用基于游标方式;格式为
?limit=20&cursor=xxx版本控制: URL路径()是最清晰的实现方式
</quick_start>
/api/v1/<success_criteria>
API设计成功的标准:
- 资源使用复数名词且命名一致
- HTTP方法语义匹配(GET=读取、POST=创建等)
- 错误响应遵循包含代码、消息、详情、requestId的统一格式
- 已实现分页(基于游标或偏移量)
- 包含速率限制头信息(X-RateLimit-Limit、Remaining、Reset)
- 在需要进行破坏性变更前已定义版本控制策略 </success_criteria>
<core_principles>
API Design Principles
API设计原则
- Consistency - Same patterns everywhere (naming, errors, pagination)
- Predictability - Developers can guess how things work
- Simplicity - Easy cases should be easy, complex cases possible
- Backwards compatibility - Don't break existing clients
- Self-documenting - Clear naming, helpful error messages </core_principles>
<rest_basics>
- 一致性 - 所有地方使用相同模式(命名、错误处理、分页)
- 可预测性 - 开发者可以推测功能的工作方式
- 简洁性 - 简单场景应易于实现,复杂场景也能支持
- 向后兼容性 - 不要破坏现有客户端
- 自文档化 - 命名清晰,错误消息有用 </core_principles>
<rest_basics>
RESTful Conventions
RESTful规范
Resource Naming
资源命名
undefinedundefinedGOOD: Nouns, plural, lowercase, hyphenated
GOOD: Nouns, plural, lowercase, hyphenated
GET /users
GET /users/{id}
GET /users/{id}/posts
GET /blog-posts
GET /api/v1/user-preferences
GET /users
GET /users/{id}
GET /users/{id}/posts
GET /blog-posts
GET /api/v1/user-preferences
BAD: Verbs, singular, mixed case, underscores
BAD: Verbs, singular, mixed case, underscores
GET /getUser
GET /user/{id}
GET /User/{id}/getPosts
GET /blog_posts
undefinedGET /getUser
GET /user/{id}
GET /User/{id}/getPosts
GET /blog_posts
undefinedHTTP Methods
HTTP方法
| Method | Purpose | Idempotent | Safe | Request Body |
|---|---|---|---|---|
| GET | Read resource | Yes | Yes | No |
| POST | Create resource | No | No | Yes |
| PUT | Replace resource | Yes | No | Yes |
| PATCH | Partial update | No* | No | Yes |
| DELETE | Remove resource | Yes | No | No |
*PATCH is idempotent if you apply the same patch
| 方法 | 用途 | 幂等性 | 安全性 | 请求体 |
|---|---|---|---|---|
| GET | 读取资源 | 是 | 是 | 无 |
| POST | 创建资源 | 否 | 否 | 有 |
| PUT | 替换资源 | 是 | 否 | 有 |
| PATCH | 部分更新 | 否* | 否 | 有 |
| DELETE | 删除资源 | 是 | 否 | 无 |
*PATCH is idempotent if you apply the same patch
CRUD Operations
CRUD操作
undefinedundefinedCollection operations
Collection operations
GET /users # List all users
POST /users # Create a user
GET /users # List all users
POST /users # Create a user
Single resource operations
Single resource operations
GET /users/{id} # Get one user
PUT /users/{id} # Replace user
PATCH /users/{id} # Update user fields
DELETE /users/{id} # Delete user
GET /users/{id} # Get one user
PUT /users/{id} # Replace user
PATCH /users/{id} # Update user fields
DELETE /users/{id} # Delete user
Nested resources
Nested resources
GET /users/{id}/posts # User's posts
POST /users/{id}/posts # Create post for user
GET /posts/{id}/comments # Post's comments
undefinedGET /users/{id}/posts # User's posts
POST /users/{id}/posts # Create post for user
GET /posts/{id}/comments # Post's comments
undefinedActions (Non-CRUD Operations)
Actions (Non-CRUD Operations)
undefinedundefinedWhen you need actions, use verbs as sub-resources
When you need actions, use verbs as sub-resources
POST /users/{id}/activate
POST /users/{id}/deactivate
POST /orders/{id}/cancel
POST /invoices/{id}/send
POST /auth/login
POST /auth/logout
POST /auth/refresh
undefinedPOST /users/{id}/activate
POST /users/{id}/deactivate
POST /orders/{id}/cancel
POST /invoices/{id}/send
POST /auth/login
POST /auth/logout
POST /auth/refresh
undefinedStatus Codes
Status Codes
| Code | Meaning | When to Use |
|---|---|---|
| 200 | OK | Successful GET, PUT, PATCH |
| 201 | Created | Successful POST that creates |
| 204 | No Content | Successful DELETE |
| 400 | Bad Request | Invalid input, validation error |
| 401 | Unauthorized | Missing/invalid authentication |
| 403 | Forbidden | Authenticated but not authorized |
| 404 | Not Found | Resource doesn't exist |
| 409 | Conflict | Duplicate, state conflict |
| 422 | Unprocessable | Validation failed (alternative to 400) |
| 429 | Too Many Requests | Rate limited |
| 500 | Server Error | Unexpected server error |
| </rest_basics> |
<error_handling>
| 状态码 | 含义 | 使用场景 |
|---|---|---|
| 200 | OK | Successful GET, PUT, PATCH |
| 201 | Created | Successful POST that creates |
| 204 | No Content | Successful DELETE |
| 400 | Bad Request | Invalid input, validation error |
| 401 | Unauthorized | Missing/invalid authentication |
| 403 | Forbidden | Authenticated but not authorized |
| 404 | Not Found | Resource doesn't exist |
| 409 | Conflict | Duplicate, state conflict |
| 422 | Unprocessable | Validation failed (alternative to 400) |
| 429 | Too Many Requests | Rate limited |
| 500 | Server Error | Unexpected server error |
| </rest_basics> |
<error_handling>
Error Responses
错误响应
Consistent Error Format
统一错误格式
typescript
// Standard error response
interface ErrorResponse {
error: {
code: string; // Machine-readable code
message: string; // Human-readable message
details?: ErrorDetail[]; // Field-level errors
requestId?: string; // For support/debugging
};
}
interface ErrorDetail {
field: string;
message: string;
code: string;
}typescript
// Standard error response
interface ErrorResponse {
error: {
code: string; // Machine-readable code
message: string; // Human-readable message
details?: ErrorDetail[]; // Field-level errors
requestId?: string; // For support/debugging
};
}
interface ErrorDetail {
field: string;
message: string;
code: string;
}Examples
示例
json
// 400 Bad Request - Validation error
{
"error": {
"code": "validation_error",
"message": "Invalid request parameters",
"details": [
{ "field": "email", "message": "Invalid email format", "code": "invalid_format" },
{ "field": "age", "message": "Must be at least 13", "code": "min_value" }
],
"requestId": "req_abc123"
}
}
// 401 Unauthorized
{
"error": {
"code": "unauthorized",
"message": "Invalid or expired authentication token",
"requestId": "req_abc123"
}
}
// 403 Forbidden
{
"error": {
"code": "forbidden",
"message": "You don't have permission to access this resource",
"requestId": "req_abc123"
}
}
// 404 Not Found
{
"error": {
"code": "not_found",
"message": "User not found",
"requestId": "req_abc123"
}
}
// 429 Rate Limited
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Please retry after 60 seconds",
"requestId": "req_abc123"
}
}json
// 400 Bad Request - Validation error
{
"error": {
"code": "validation_error",
"message": "Invalid request parameters",
"details": [
{ "field": "email", "message": "Invalid email format", "code": "invalid_format" },
{ "field": "age", "message": "Must be at least 13", "code": "min_value" }
],
"requestId": "req_abc123"
}
}
// 401 Unauthorized
{
"error": {
"code": "unauthorized",
"message": "Invalid or expired authentication token",
"requestId": "req_abc123"
}
}
// 403 Forbidden
{
"error": {
"code": "forbidden",
"message": "You don't have permission to access this resource",
"requestId": "req_abc123"
}
}
// 404 Not Found
{
"error": {
"code": "not_found",
"message": "User not found",
"requestId": "req_abc123"
}
}
// 429 Rate Limited
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Please retry after 60 seconds",
"requestId": "req_abc123"
}
}Implementation
实现
typescript
// Error class
class ApiError extends Error {
constructor(
public statusCode: number,
public code: string,
message: string,
public details?: ErrorDetail[]
) {
super(message);
}
}
// Error handler middleware
function errorHandler(error: Error, req: Request, res: Response) {
const requestId = req.headers['x-request-id'] || crypto.randomUUID();
if (error instanceof ApiError) {
return res.status(error.statusCode).json({
error: {
code: error.code,
message: error.message,
details: error.details,
requestId,
},
});
}
// Log unexpected errors
logger.error('Unexpected error', { error, requestId });
// Don't expose internal details
return res.status(500).json({
error: {
code: 'internal_error',
message: 'An unexpected error occurred',
requestId,
},
});
}</error_handling>
<pagination>typescript
// Error class
class ApiError extends Error {
constructor(
public statusCode: number,
public code: string,
message: string,
public details?: ErrorDetail[]
) {
super(message);
}
}
// Error handler middleware
function errorHandler(error: Error, req: Request, res: Response) {
const requestId = req.headers['x-request-id'] || crypto.randomUUID();
if (error instanceof ApiError) {
return res.status(error.statusCode).json({
error: {
code: error.code,
message: error.message,
details: error.details,
requestId,
},
});
}
// Log unexpected errors
logger.error('Unexpected error', { error, requestId });
// Don't expose internal details
return res.status(500).json({
error: {
code: 'internal_error',
message: 'An unexpected error occurred',
requestId,
},
});
}</error_handling>
<pagination>Pagination
分页
Cursor-Based (Recommended)
基于游标(推荐)
typescript
// Request
GET /posts?limit=20&cursor=eyJpZCI6MTAwfQ
// Response
{
"data": [...],
"pagination": {
"hasMore": true,
"nextCursor": "eyJpZCI6MTIwfQ",
"prevCursor": "eyJpZCI6MTAwfQ"
}
}Pros: Consistent results, handles real-time data
Cons: Can't jump to page N
typescript
// Request
GET /posts?limit=20&cursor=eyJpZCI6MTAwfQ
// Response
{
"data": [...],
"pagination": {
"hasMore": true,
"nextCursor": "eyJpZCI6MTIwfQ",
"prevCursor": "eyJpZCI6MTAwfQ"
}
}优点: 结果一致,支持实时数据
缺点: 无法跳转到第N页
Offset-Based
基于偏移量
typescript
// Request
GET /posts?page=2&limit=20
// or
GET /posts?offset=20&limit=20
// Response
{
"data": [...],
"pagination": {
"page": 2,
"limit": 20,
"total": 150,
"totalPages": 8
}
}Pros: Can jump to any page
Cons: Inconsistent with real-time data, slow on large tables
typescript
// Request
GET /posts?page=2&limit=20
// or
GET /posts?offset=20&limit=20
// Response
{
"data": [...],
"pagination": {
"page": 2,
"limit": 20,
"total": 150,
"totalPages": 8
}
}优点: 可以跳转到任意页面
缺点: 实时数据下结果不一致,大数据表查询缓慢
Implementation (Cursor)
实现(基于游标)
typescript
// Encode/decode cursor
function encodeCursor(data: object): string {
return Buffer.from(JSON.stringify(data)).toString('base64url');
}
function decodeCursor(cursor: string): object {
return JSON.parse(Buffer.from(cursor, 'base64url').toString());
}
// Query with cursor
async function getPosts(limit: number, cursor?: string) {
const where: any = {};
if (cursor) {
const { id } = decodeCursor(cursor);
where.id = { lt: id };
}
const posts = await db.post.findMany({
where,
orderBy: { id: 'desc' },
take: limit + 1, // Fetch one extra to check hasMore
});
const hasMore = posts.length > limit;
const data = hasMore ? posts.slice(0, -1) : posts;
return {
data,
pagination: {
hasMore,
nextCursor: hasMore ? encodeCursor({ id: data[data.length - 1].id }) : null,
},
};
}typescript
// Encode/decode cursor
function encodeCursor(data: object): string {
return Buffer.from(JSON.stringify(data)).toString('base64url');
}
function decodeCursor(cursor: string): object {
return JSON.parse(Buffer.from(cursor, 'base64url').toString());
}
// Query with cursor
async function getPosts(limit: number, cursor?: string) {
const where: any = {};
if (cursor) {
const { id } = decodeCursor(cursor);
where.id = { lt: id };
}
const posts = await db.post.findMany({
where,
orderBy: { id: 'desc' },
take: limit + 1, // Fetch one extra to check hasMore
});
const hasMore = posts.length > limit;
const data = hasMore ? posts.slice(0, -1) : posts;
return {
data,
pagination: {
hasMore,
nextCursor: hasMore ? encodeCursor({ id: data[data.length - 1].id }) : null,
},
};
}Filtering and Sorting
过滤与排序
Use query params: , (prefix for desc), . Validate with Zod schema, build Prisma clause from parsed filters.
?status=active?sort=-created_at-?fields=id,name,emailwhereSee for full query parameter patterns and implementation.
</filtering>
<versioning>reference/filtering-sorting.md使用查询参数:、(前缀表示降序)、。使用Zod schema进行验证,从解析后的过滤器构建Prisma 子句。
?status=active?sort=-created_at-?fields=id,name,emailwhere完整的查询参数模式和实现请参考。
</filtering>
<versioning>reference/filtering-sorting.mdAPI Versioning
API版本控制
URL Path Versioning (Recommended)
URL路径版本控制(推荐)
GET /api/v1/users
GET /api/v2/usersPros: Clear, easy to understand
Cons: More maintenance
GET /api/v1/users
GET /api/v2/users优点: 清晰、易于理解
缺点: 维护成本更高
Header Versioning
请求头版本控制
GET /api/users
Accept: application/vnd.api+json; version=2Pros: Clean URLs
Cons: Hidden, harder to test
GET /api/users
Accept: application/vnd.api+json; version=2优点: URL简洁
缺点: 不直观,测试难度大
When to Version
何时进行版本控制
Create a new version when:
- Removing fields from responses
- Changing field types or formats
- Removing endpoints
- Changing authentication
Don't create a new version for:
- Adding new optional fields
- Adding new endpoints
- Adding new optional parameters
- Bug fixes </versioning>
<rate_limiting>
在以下场景创建新版本:
- 从响应中移除字段
- 更改字段类型或格式
- 移除端点
- 更改身份验证方式
以下场景无需创建新版本:
- 添加新的可选字段
- 添加新的端点
- 添加新的可选参数
- 修复bug </versioning>
<rate_limiting>
Rate Limiting
速率限制
Include headers: , , . Return with when exceeded. Use sliding window algorithm (e.g., ).
X-RateLimit-LimitX-RateLimit-RemainingX-RateLimit-Reset429Retry-After@upstash/ratelimitSee for tier definitions and implementation.
</rate_limiting>
<references>
For detailed patterns, load the appropriate reference:
reference/rate-limiting.md| Topic | Reference File | When to Load |
|---|---|---|
| REST patterns | | Endpoint design |
| Error handling | | Error responses |
| Pagination | | List endpoints |
| Filtering & sorting | | Query parameters |
| Rate limiting | | Throttling |
| Versioning | | API evolution |
| Documentation | | OpenAPI, docs |
| Checklist | | Pre-launch validation |
To load: Ask for the specific topic or check if context suggests it.
</references>
包含以下响应头:、、。当超出限制时返回状态码并附带头。使用滑动窗口算法(例如)。
X-RateLimit-LimitX-RateLimit-RemainingX-RateLimit-Reset429Retry-After@upstash/ratelimit层级定义和实现请参考。
</rate_limiting>
<references>
如需详细模式,请加载对应的参考文档:
reference/rate-limiting.md| 主题 | 参考文件 | 加载时机 |
|---|---|---|
| REST模式 | | 端点设计时 |
| 错误处理 | | 错误响应设计时 |
| 分页 | | 列表端点设计时 |
| 过滤与排序 | | 查询参数设计时 |
| 速率限制 | | 限流设计时 |
| 版本控制 | | API演进时 |
| 文档 | | OpenAPI、文档编写时 |
| 检查清单 | | 上线前验证时 |
加载方式: 请求特定主题,或根据上下文判断是否需要加载。
</references>
Emit Outcome Sidecar
输出结果侧写
As the final step, write to :
~/.claude/skill-analytics/last-outcome-api-design.jsonjson
{"ts":"[UTC ISO8601]","skill":"api-design","version":"1.0.0","variant":"default",
"status":"[success|partial|error]","runtime_ms":[estimated ms from start],
"metrics":{"endpoints_designed":[n],"patterns_applied":[n]},
"error":null,"session_id":"[YYYY-MM-DD]"}Use status "partial" if some stages failed but results were produced. Use "error" only if no output was generated.
最后一步,写入文件:
~/.claude/skill-analytics/last-outcome-api-design.jsonjson
{"ts":"[UTC ISO8601]","skill":"api-design","version":"1.0.0","variant":"default",
"status":"[success|partial|error]","runtime_ms":[estimated ms from start],
"metrics":{"endpoints_designed":[n],"patterns_applied":[n]},
"error":null,"session_id":"[YYYY-MM-DD]"}如果部分阶段失败但仍生成了结果,使用状态"partial"。只有当未生成任何输出时使用"error"。