一键导入
api-design
Design and implement REST APIs with consistent conventions, versioning, error contracts, and security.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Design and implement REST APIs with consistent conventions, versioning, error contracts, and security.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Design and implement async task queues, message consumers, and background job patterns.
Design relational schemas, write efficient queries, plan indexes, and implement safe migrations.
Core development tools used across any project — git, docker, make, CI/CD, linting, formatting, pre-commit hooks.
Implement structured logging, distributed tracing, and metrics for production-ready backend services.
Systematic backend debugging — reproduce, isolate root cause, implement fix with regression test.
| name | api-design |
| type | skill |
| description | Design and implement REST APIs with consistent conventions, versioning, error contracts, and security. |
| related-rules | ["architecture.md","security.md"] |
| allowed-tools | Read, Write, Edit, Bash |
| agentic | {"generated_by":"agentic","source":"areas/software/backend/skills/api-design/SKILL.md","repository":"https://github.com/sawrus/agent-guides","created_by":"v0.4.0","updated_by":"v0.5.1"} |
Expertise: REST API design, HTTP semantics, versioning strategies, error contracts, OpenAPI, pagination, idempotency.
| Operation | Method | Success | Error cases |
|---|---|---|---|
| Create resource | POST | 201 Created | 400 Validation, 409 Conflict |
| Read resource | GET | 200 OK | 404 Not Found, 403 Forbidden |
| Full update | PUT | 200 OK | 400, 404, 409 |
| Partial update | PATCH | 200 OK | 400, 404 |
| Delete | DELETE | 204 No Content | 404, 409 (has dependents) |
| Async action | POST | 202 Accepted | 400 |
✅ Nouns, plural, lowercase, kebab-case
GET /users/{id}
POST /orders
GET /product-categories
❌ Verbs in path
POST /createOrder
GET /getUser?id=123
✅ Nesting only for true ownership (max 2 levels)
GET /orders/{order_id}/items ✅ items belong to order
GET /orders/{order_id}/items/{item_id}/tags ❌ too deep
✅ Actions as sub-resources
POST /orders/{id}/cancel
POST /invoices/{id}/resend
All error responses must follow the same shape:
{
"error": {
"code": "ORDER_NOT_FOUND", // machine-readable, stable
"message": "Order ord_123 not found", // human-readable
"details": [ // optional: per-field errors
{ "field": "items[0].quantity", "issue": "must be > 0" }
],
"request_id": "req_abc123" // traceable
}
}
# FastAPI implementation
from fastapi import HTTPException
from pydantic import BaseModel
from typing import Optional, List
class ErrorDetail(BaseModel):
field: str
issue: str
class ErrorResponse(BaseModel):
code: str
message: str
details: Optional[List[ErrorDetail]] = None
request_id: Optional[str] = None
# Usage
raise HTTPException(
status_code=404,
detail=ErrorResponse(
code="ORDER_NOT_FOUND",
message=f"Order {order_id} not found",
request_id=request.state.request_id
).model_dump()
)
class PaginatedResponse(BaseModel, Generic[T]):
items: List[T]
next_cursor: Optional[str] = None # opaque, base64-encoded
total_count: Optional[int] = None # only if cheap to compute
# Encode cursor: hide implementation detail from client
import base64, json
def encode_cursor(last_id: int, last_created_at: datetime) -> str:
payload = {"id": last_id, "ts": last_created_at.isoformat()}
return base64.b64encode(json.dumps(payload).encode()).decode()
def decode_cursor(cursor: str) -> dict:
return json.loads(base64.b64decode(cursor).decode())
# Only when total count is cheap and dataset doesn't change under pagination
@router.get("/products")
async def list_products(page: int = 1, page_size: int = Query(20, le=100)):
offset = (page - 1) * page_size
# ...
URL versioning (recommended for major breaking changes):
/api/v1/orders
/api/v2/orders ← new schema, old still supported
Header versioning (for minor variations):
Accept: application/vnd.myapi.v2+json
Rules:
- v1 stays alive for minimum 6 months after v2 launch
- Deprecated endpoints return: Deprecation: true, Sunset: <date> headers
- Never remove a field from a response without a major version bump
# Idempotency key pattern for POST mutations
@router.post("/orders", status_code=201)
async def create_order(
body: CreateOrderRequest,
idempotency_key: Optional[str] = Header(None, alias="X-Idempotency-Key"),
db: AsyncSession = Depends(get_db),
redis: Redis = Depends(get_redis),
):
if idempotency_key:
cached = await redis.get(f"idempotency:{idempotency_key}")
if cached:
return JSONResponse(json.loads(cached), status_code=200) # 200 = already done
order = await order_service.create(db, body)
response = OrderResponse.model_validate(order)
if idempotency_key:
await redis.setex(
f"idempotency:{idempotency_key}",
86400, # 24h TTL
response.model_dump_json()
)
return response
if order.user_id != current_user.id: raise 403allow_origins=["*"] in production