AgentSkillsCN

api-design

REST 与 API 设计原则——资源命名、HTTP 方法、状态码、分页、版本控制以及错误响应。在设计或评审 API 时可作为参考。

SKILL.md
--- frontmatter
name: api-design
description: REST and API design principles — resource naming, HTTP methods, status codes, pagination, versioning, and error responses. Reference when designing or reviewing APIs.

API Design

RESTful Resource Naming

Conventions

RuleGoodBad
Use plural nouns/users/user, /getUsers
Use nouns, not verbsPOST /ordersPOST /createOrder
Nest for relationships/users/123/orders/getUserOrders?userId=123
Use kebab-case/user-profiles/userProfiles, /user_profiles
Keep URLs shallow (max 3)/users/123/orders/users/123/orders/456/items/789
Use query params for filters/orders?status=pending/orders/pending
Collection + resource IDs/users/123/user?id=123

URL Structure

code
https://api.example.com/v1/users                    # Collection
https://api.example.com/v1/users/123                # Single resource
https://api.example.com/v1/users/123/orders          # Nested collection
https://api.example.com/v1/users/123/orders/456      # Nested resource
https://api.example.com/v1/orders?status=pending     # Filtered collection

Actions That Do Not Map to CRUD

For operations that are not simple CRUD, use a sub-resource or action noun:

code
POST /users/123/activate          # State transition
POST /orders/456/refund           # Domain action
POST /reports/export              # Process trigger

HTTP Method Semantics

MethodPurposeIdempotentSafeRequest BodySuccess Code
GETRetrieve resource(s)YesYesNo200
POSTCreate a resourceNoNoYes201
PUTFull replacementYesNoYes200
PATCHPartial updateNo*NoYes200
DELETERemove a resourceYesNoNo204

*PATCH can be made idempotent with proper implementation but is not guaranteed by the spec.

Method Usage Rules

  • GET must never modify server state
  • POST is the only method for creating new resources
  • PUT sends the complete resource; omitted fields are set to defaults or null
  • PATCH sends only the fields to change; omitted fields remain unchanged
  • DELETE returns 204 on success, and is a no-op if the resource already does not exist

Examples

http
GET /api/v1/users/123
Accept: application/json

---

POST /api/v1/users
Content-Type: application/json

{
  "name": "Jane Doe",
  "email": "jane@example.com"
}

---

PUT /api/v1/users/123
Content-Type: application/json

{
  "name": "Jane Doe",
  "email": "jane@newdomain.com",
  "role": "admin"
}

---

PATCH /api/v1/users/123
Content-Type: application/json

{
  "role": "admin"
}

---

DELETE /api/v1/users/123

Status Code Guide

Success Codes

CodeNameWhen to Use
200OKSuccessful GET, PUT, PATCH, or DELETE with body
201CreatedSuccessful POST that created a resource
204No ContentSuccessful DELETE or PUT with no response body

Client Error Codes

CodeNameWhen to Use
400Bad RequestMalformed JSON, invalid syntax
401UnauthorizedMissing or invalid authentication credentials
403ForbiddenAuthenticated but not authorized for this action
404Not FoundResource does not exist at this URL
409ConflictResource state conflict (duplicate, version mismatch)
422Unprocessable EntityValid JSON but fails business validation
429Too Many RequestsRate limit exceeded

Server Error Codes

CodeNameWhen to Use
500Internal Server ErrorUnexpected server failure
503Service UnavailableServer is down for maintenance or overloaded

Decision Tree

code
Is the request well-formed?
  No  --> 400 Bad Request
  Yes --> Is the client authenticated?
    No  --> 401 Unauthorized
    Yes --> Is the client authorized?
      No  --> 403 Forbidden
      Yes --> Does the resource exist?
        No  --> 404 Not Found
        Yes --> Does the request pass validation?
          No  --> 422 Unprocessable Entity
          Yes --> Is there a conflict?
            No  --> 2xx Success
            Yes --> 409 Conflict

Structured Error Response Format

All error responses must follow this format:

json
{
  "error": {
    "code": "VALIDATION_FAILED",
    "message": "The request could not be processed due to validation errors.",
    "details": [
      {
        "field": "email",
        "message": "Must be a valid email address.",
        "code": "INVALID_FORMAT"
      },
      {
        "field": "age",
        "message": "Must be at least 18.",
        "code": "OUT_OF_RANGE"
      }
    ],
    "request_id": "req_abc123def456"
  }
}

Error Response Rules

  • Always include a machine-readable code (uppercase snake_case)
  • Always include a human-readable message
  • Include details array for field-level validation errors
  • Include request_id for traceability
  • Never expose stack traces, internal paths, or database details
  • Use consistent error codes across the entire API

Standard Error Codes

Error CodeHTTP StatusDescription
VALIDATION_FAILED422One or more fields are invalid
RESOURCE_NOT_FOUND404Requested resource does not exist
AUTHENTICATION_REQUIRED401No valid credentials provided
PERMISSION_DENIED403Insufficient permissions
CONFLICT409Resource state conflict
RATE_LIMIT_EXCEEDED429Too many requests
INTERNAL_ERROR500Unexpected server error
SERVICE_UNAVAILABLE503Dependency or server is down

Pagination

Cursor-Based Pagination (Preferred)

Best for real-time data, large datasets, and consistent results.

Request:

http
GET /api/v1/orders?limit=20&cursor=eyJpZCI6MTAwfQ

Response:

json
{
  "data": [ ... ],
  "pagination": {
    "next_cursor": "eyJpZCI6MTIwfQ",
    "has_more": true
  }
}

Implementation notes:

  • Encode cursor as base64 of the last item's sort key
  • Cursor is opaque to the client; never expose internal IDs directly
  • Always include has_more boolean

Offset-Based Pagination

Simpler but has consistency issues on changing data.

Request:

http
GET /api/v1/products?page=3&per_page=25

Response:

json
{
  "data": [ ... ],
  "pagination": {
    "page": 3,
    "per_page": 25,
    "total_count": 342,
    "total_pages": 14
  }
}

When to Use Each

ApproachUse When
Cursor-basedReal-time data, infinite scroll, large datasets
Offset-basedAdmin panels, reports where total count is needed

Pagination Rules

  • Default limit or per_page to a sensible value (e.g., 20)
  • Enforce a maximum limit (e.g., 100) to prevent abuse
  • Always include pagination metadata in the response
  • Return an empty data array (not null) when no results match

Versioning Strategies

StrategyExampleProsCons
URL path/api/v1/usersExplicit, easy to routeURL changes on version bump
HeaderAccept: application/vnd.api+json; version=1Clean URLsHidden, harder to test
Query param/api/users?version=1Easy to addNot standard, clutters query

Recommended: URL Path Versioning

code
/api/v1/users    # Version 1
/api/v2/users    # Version 2 (breaking changes)

Versioning Rules

  • Only increment the major version for breaking changes
  • Support at least N-1 version in production
  • Document the deprecation timeline (minimum 6 months notice)
  • Use Sunset header to communicate deprecation date
  • Non-breaking changes (new fields, new endpoints) do not require a version bump

Rate Limiting

Headers

Include these headers in every response:

HeaderValue
X-RateLimit-LimitMaximum requests per window
X-RateLimit-RemainingRequests remaining in current window
X-RateLimit-ResetUnix timestamp when the window resets
Retry-AfterSeconds to wait (on 429 responses only)

Example 429 Response

http
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1700000000
Retry-After: 30
Content-Type: application/json

{
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "You have exceeded the rate limit. Please retry after 30 seconds.",
    "request_id": "req_xyz789"
  }
}

Authentication Patterns

PatternUse CaseHeader
Bearer TokenUser authentication (JWT, OAuth2)Authorization: Bearer <token>
API KeyService-to-service, third-party integrationsX-API-Key: <key> or query param
OAuth2Third-party user authorizationAuthorization: Bearer <access_token>
Basic AuthSimple internal services (over HTTPS only)Authorization: Basic <base64>

Authentication Rules

  • Always use HTTPS in production
  • Never pass tokens or keys in URL query params for GET requests (they appear in logs)
  • API keys should be rotatable without downtime
  • Return 401 for missing/invalid credentials, 403 for insufficient permissions
  • Include WWW-Authenticate header in 401 responses

Input Validation

Validation Order

  1. Type validation - Is the JSON well-formed? Are types correct?
  2. Presence validation - Are all required fields present?
  3. Format validation - Do values match expected patterns (email, URL, UUID)?
  4. Range validation - Are numbers within bounds? Are strings within length limits?
  5. Business validation - Does the data make sense in context?

Return All Errors at Once

json
{
  "error": {
    "code": "VALIDATION_FAILED",
    "message": "Multiple validation errors occurred.",
    "details": [
      { "field": "email", "message": "Required field.", "code": "REQUIRED" },
      { "field": "age", "message": "Must be between 0 and 150.", "code": "OUT_OF_RANGE" },
      { "field": "name", "message": "Must be 1-100 characters.", "code": "INVALID_LENGTH" }
    ]
  }
}

Never return one error at a time forcing clients to resubmit repeatedly.

Filtering, Sorting, and Field Selection

Filtering

http
GET /api/v1/orders?status=pending&created_after=2025-01-01
GET /api/v1/products?category=electronics&min_price=100&max_price=500

Sorting

http
GET /api/v1/users?sort=created_at       # Ascending (default)
GET /api/v1/users?sort=-created_at      # Descending (prefix with -)
GET /api/v1/users?sort=name,-created_at  # Multiple fields

Field Selection (Sparse Fieldsets)

http
GET /api/v1/users?fields=id,name,email
GET /api/v1/orders?fields=id,total,status

Query Parameter Rules

  • Use consistent naming across all endpoints
  • Document every supported filter, sort field, and selectable field
  • Ignore unknown query parameters (do not error)
  • Apply sensible defaults when parameters are omitted
  • Validate and sanitize all query parameter values