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-posts
)
HTTP 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=xxx
Versioning: URL path (
/api/v1/
) is clearest approach </quick_start>
<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-posts
HTTP方法: 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路径(
/api/v1/
)是最清晰的实现方式 </quick_start>
<success_criteria> API设计成功的标准:
  • 资源使用复数名词且命名一致
  • HTTP方法语义匹配(GET=读取、POST=创建等)
  • 错误响应遵循包含代码、消息、详情、requestId的统一格式
  • 已实现分页(基于游标或偏移量)
  • 包含速率限制头信息(X-RateLimit-Limit、Remaining、Reset)
  • 在需要进行破坏性变更前已定义版本控制策略 </success_criteria>
<core_principles>

API Design Principles

API设计原则

  1. Consistency - Same patterns everywhere (naming, errors, pagination)
  2. Predictability - Developers can guess how things work
  3. Simplicity - Easy cases should be easy, complex cases possible
  4. Backwards compatibility - Don't break existing clients
  5. Self-documenting - Clear naming, helpful error messages </core_principles>
<rest_basics>
  1. 一致性 - 所有地方使用相同模式(命名、错误处理、分页)
  2. 可预测性 - 开发者可以推测功能的工作方式
  3. 简洁性 - 简单场景应易于实现,复杂场景也能支持
  4. 向后兼容性 - 不要破坏现有客户端
  5. 自文档化 - 命名清晰,错误消息有用 </core_principles>
<rest_basics>

RESTful Conventions

RESTful规范

Resource Naming

资源命名

undefined
undefined

GOOD: 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
undefined
GET /getUser GET /user/{id} GET /User/{id}/getPosts GET /blog_posts
undefined

HTTP Methods

HTTP方法

MethodPurposeIdempotentSafeRequest Body
GETRead resourceYesYesNo
POSTCreate resourceNoNoYes
PUTReplace resourceYesNoYes
PATCHPartial updateNo*NoYes
DELETERemove resourceYesNoNo
*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操作

undefined
undefined

Collection 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
undefined
GET /users/{id}/posts # User's posts POST /users/{id}/posts # Create post for user GET /posts/{id}/comments # Post's comments
undefined

Actions (Non-CRUD Operations)

Actions (Non-CRUD Operations)

undefined
undefined

When 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
undefined
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
undefined

Status Codes

Status Codes

CodeMeaningWhen to Use
200OKSuccessful GET, PUT, PATCH
201CreatedSuccessful POST that creates
204No ContentSuccessful DELETE
400Bad RequestInvalid input, validation error
401UnauthorizedMissing/invalid authentication
403ForbiddenAuthenticated but not authorized
404Not FoundResource doesn't exist
409ConflictDuplicate, state conflict
422UnprocessableValidation failed (alternative to 400)
429Too Many RequestsRate limited
500Server ErrorUnexpected server error
</rest_basics>
<error_handling>
状态码含义使用场景
200OKSuccessful GET, PUT, PATCH
201CreatedSuccessful POST that creates
204No ContentSuccessful DELETE
400Bad RequestInvalid input, validation error
401UnauthorizedMissing/invalid authentication
403ForbiddenAuthenticated but not authorized
404Not FoundResource doesn't exist
409ConflictDuplicate, state conflict
422UnprocessableValidation failed (alternative to 400)
429Too Many RequestsRate limited
500Server ErrorUnexpected 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,
    },
  };
}
</pagination> <filtering>
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,
    },
  };
}
</pagination> <filtering>

Filtering and Sorting

过滤与排序

Use query params:
?status=active
,
?sort=-created_at
(prefix
-
for desc),
?fields=id,name,email
. Validate with Zod schema, build Prisma
where
clause from parsed filters.
See
reference/filtering-sorting.md
for full query parameter patterns and implementation. </filtering>
<versioning>
使用查询参数:
?status=active
?sort=-created_at
(前缀
-
表示降序)、
?fields=id,name,email
。使用Zod schema进行验证,从解析后的过滤器构建Prisma
where
子句。
完整的查询参数模式和实现请参考
reference/filtering-sorting.md
</filtering>
<versioning>

API Versioning

API版本控制

URL Path Versioning (Recommended)

URL路径版本控制(推荐)

GET /api/v1/users
GET /api/v2/users
Pros: 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=2
Pros: 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:
X-RateLimit-Limit
,
X-RateLimit-Remaining
,
X-RateLimit-Reset
. Return
429
with
Retry-After
when exceeded. Use sliding window algorithm (e.g.,
@upstash/ratelimit
).
See
reference/rate-limiting.md
for tier definitions and implementation. </rate_limiting>
<references> For detailed patterns, load the appropriate reference:
TopicReference FileWhen to Load
REST patterns
reference/rest-patterns.md
Endpoint design
Error handling
reference/error-handling.md
Error responses
Pagination
reference/pagination.md
List endpoints
Filtering & sorting
reference/filtering-sorting.md
Query parameters
Rate limiting
reference/rate-limiting.md
Throttling
Versioning
reference/versioning.md
API evolution
Documentation
reference/documentation.md
OpenAPI, docs
Checklist
reference/checklist.md
Pre-launch validation
To load: Ask for the specific topic or check if context suggests it. </references>
包含以下响应头:
X-RateLimit-Limit
X-RateLimit-Remaining
X-RateLimit-Reset
。当超出限制时返回
429
状态码并附带
Retry-After
头。使用滑动窗口算法(例如
@upstash/ratelimit
)。
层级定义和实现请参考
reference/rate-limiting.md
。 </rate_limiting>
<references> 如需详细模式,请加载对应的参考文档:
主题参考文件加载时机
REST模式
reference/rest-patterns.md
端点设计时
错误处理
reference/error-handling.md
错误响应设计时
分页
reference/pagination.md
列表端点设计时
过滤与排序
reference/filtering-sorting.md
查询参数设计时
速率限制
reference/rate-limiting.md
限流设计时
版本控制
reference/versioning.md
API演进时
文档
reference/documentation.md
OpenAPI、文档编写时
检查清单
reference/checklist.md
上线前验证时
加载方式: 请求特定主题,或根据上下文判断是否需要加载。 </references>

Emit Outcome Sidecar

输出结果侧写

As the final step, write to
~/.claude/skill-analytics/last-outcome-api-design.json
:
json
{"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.json
json
{"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"。