Loading...
Loading...
RESTful API design, error handling, versioning, and best practices. Use when designing APIs, reviewing endpoints, implementing error responses, or setting up API structure. Triggers on "design API", "review API", "REST best practices", or "API patterns".
npx skill4agent add asyrafhussin/agent-skills api-design-patterns| Priority | Category | Impact | Prefix |
|---|---|---|---|
| 1 | Resource Design | CRITICAL | |
| 2 | Error Handling | CRITICAL | |
| 3 | Security | CRITICAL | |
| 4 | Pagination & Filtering | HIGH | |
| 5 | Versioning | HIGH | |
| 6 | Response Format | MEDIUM | |
| 7 | Documentation | MEDIUM | |
rest-nouns-not-verbsrest-plural-resourcesrest-http-methodsrest-nested-resourcesrest-status-codesrest-idempotencyrest-hateoaserror-consistent-formaterror-meaningful-messageserror-validation-detailserror-codeserror-stack-tracessec-authenticationsec-authorizationsec-rate-limitingsec-input-validationsec-corssec-sensitive-datapage-cursor-basedpage-offset-basedpage-consistent-paramspage-metadatafilter-query-paramssort-flexiblever-url-pathver-header-basedver-backward-compatiblever-deprecationresp-consistent-structureresp-json-conventionsresp-partial-responsesresp-compressiondoc-openapidoc-examplesdoc-changelog# ❌ Verbs in URLs - RPC style
GET /getUsers
POST /createUser
PUT /updateUser/123
DELETE /deleteUser/123
# ✅ Nouns with HTTP methods - REST style
GET /users # List users
POST /users # Create user
GET /users/123 # Get user
PUT /users/123 # Update user (full)
PATCH /users/123 # Update user (partial)
DELETE /users/123 # Delete user# ✅ Logical nesting (max 2 levels)
GET /users/123/orders # User's orders
GET /users/123/orders/456 # Specific order
POST /users/123/orders # Create order for user
# ❌ Too deeply nested
GET /users/123/orders/456/items/789/reviews
# ✅ Flatten when appropriate
GET /order-items/789/reviews # Direct access# GET - Retrieve resource(s)
GET /users → 200 OK + array
GET /users/123 → 200 OK + object
GET /users/999 → 404 Not Found
# POST - Create resource
POST /users → 201 Created + object + Location header
POST /users (invalid) → 400 Bad Request + errors
# PUT - Full update (replace)
PUT /users/123 → 200 OK + updated object
PUT /users/999 → 404 Not Found
# PATCH - Partial update
PATCH /users/123 → 200 OK + updated object
# DELETE - Remove resource
DELETE /users/123 → 204 No Content
DELETE /users/999 → 404 Not Found
# Other common status codes
401 Unauthorized # Not authenticated
403 Forbidden # Authenticated but not authorized
409 Conflict # Resource state conflict
422 Unprocessable # Validation failed
429 Too Many Requests # Rate limited
500 Internal Error # Server error
503 Service Unavailable # Maintenance/overload// ❌ Inconsistent error formats
{ "error": "Not found" }
{ "message": "Invalid email" }
{ "errors": ["Error 1", "Error 2"] }
// ✅ Consistent error envelope
{
"error": {
"code": "VALIDATION_ERROR",
"message": "The request contains invalid data",
"details": [
{
"field": "email",
"code": "INVALID_FORMAT",
"message": "Please provide a valid email address"
},
{
"field": "password",
"code": "TOO_SHORT",
"message": "Password must be at least 8 characters"
}
],
"request_id": "req_abc123"
}
}
// ✅ Not found error
{
"error": {
"code": "NOT_FOUND",
"message": "User with ID 123 not found",
"request_id": "req_def456"
}
}
// ✅ Server error (no sensitive details)
{
"error": {
"code": "INTERNAL_ERROR",
"message": "An unexpected error occurred. Please try again.",
"request_id": "req_ghi789"
}
}// ✅ Offset-based pagination
GET /users?page=2&per_page=20
{
"data": [...],
"meta": {
"current_page": 2,
"per_page": 20,
"total_pages": 10,
"total_count": 195
},
"links": {
"first": "/users?page=1&per_page=20",
"prev": "/users?page=1&per_page=20",
"next": "/users?page=3&per_page=20",
"last": "/users?page=10&per_page=20"
}
}
// ✅ Cursor-based pagination (for large datasets)
GET /users?cursor=eyJpZCI6MTIzfQ&limit=20
{
"data": [...],
"meta": {
"has_more": true,
"next_cursor": "eyJpZCI6MTQzfQ"
},
"links": {
"next": "/users?cursor=eyJpZCI6MTQzfQ&limit=20"
}
}# ✅ Query parameter filtering
GET /users?status=active
GET /users?role=admin&status=active
GET /users?created_after=2024-01-01
# ✅ Sorting
GET /users?sort=created_at # Ascending (default)
GET /users?sort=-created_at # Descending (prefix with -)
GET /users?sort=last_name,-created_at # Multiple fields
# ✅ Field selection (sparse fieldsets)
GET /users?fields=id,name,email
GET /users/123?fields=id,name,orders
# ✅ Search
GET /users?q=john
GET /users?search=john@example// ✅ Single resource
GET /users/123
{
"data": {
"id": "123",
"type": "user",
"attributes": {
"name": "John Doe",
"email": "john@example.com",
"created_at": "2024-01-15T10:30:00Z"
},
"relationships": {
"orders": {
"links": {
"related": "/users/123/orders"
}
}
}
}
}
// ✅ Collection
GET /users
{
"data": [
{ "id": "123", "type": "user", ... },
{ "id": "124", "type": "user", ... }
],
"meta": {
"total_count": 100
},
"links": {
"self": "/users?page=1",
"next": "/users?page=2"
}
}
// ✅ Simpler envelope (also acceptable)
{
"user": {
"id": "123",
"name": "John Doe"
}
}
{
"users": [...],
"pagination": {...}
}# ✅ URL path versioning (recommended - explicit)
GET /api/v1/users
GET /api/v2/users
# ✅ Header versioning
GET /api/users
Accept: application/vnd.myapi.v2+json
# ✅ Query parameter (simple, but less clean)
GET /api/users?version=2# ✅ Include rate limit headers
HTTP/1.1 200 OK
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 998
X-RateLimit-Reset: 1640995200
# ✅ Rate limited response
HTTP/1.1 429 Too Many Requests
Retry-After: 60
{
"error": {
"code": "RATE_LIMITED",
"message": "Too many requests. Please retry after 60 seconds.",
"retry_after": 60
}
}# For non-CRUD actions, use sub-resources or actions
# ✅ Sub-resource style
POST /users/123/activate
POST /orders/456/cancel
POST /posts/789/publish
# ✅ Or controller-style for complex operations
POST /auth/login
POST /auth/logout
POST /auth/refresh
POST /password/reset
POST /password/reset/confirm// ✅ Validate and return all errors at once
POST /users
{
"email": "invalid",
"password": "short"
}
// Response: 422 Unprocessable Entity
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Validation failed",
"details": [
{
"field": "email",
"code": "INVALID_FORMAT",
"message": "Must be a valid email address"
},
{
"field": "password",
"code": "TOO_SHORT",
"message": "Must be at least 8 characters",
"meta": {
"min_length": 8,
"actual_length": 5
}
}
]
}
}// ✅ Include links for discoverability
{
"data": {
"id": "123",
"status": "pending",
"total": 99.99
},
"links": {
"self": "/orders/123",
"cancel": "/orders/123/cancel",
"pay": "/orders/123/pay",
"items": "/orders/123/items"
},
"actions": {
"cancel": {
"method": "POST",
"href": "/orders/123/cancel"
},
"pay": {
"method": "POST",
"href": "/orders/123/pay",
"fields": [
{ "name": "payment_method", "type": "string", "required": true }
]
}
}
}// ✅ Bulk create
POST /users/bulk
{
"users": [
{ "name": "User 1", "email": "user1@example.com" },
{ "name": "User 2", "email": "user2@example.com" }
]
}
// Response with partial success
{
"data": {
"succeeded": [
{ "id": "123", "name": "User 1" }
],
"failed": [
{
"index": 1,
"error": {
"code": "DUPLICATE_EMAIL",
"message": "Email already exists"
}
}
]
},
"meta": {
"total": 2,
"succeeded": 1,
"failed": 1
}
}endpoint - [category] Description of issueGET /getUsers - [rest] Use noun '/users' instead of verb '/getUsers'
POST /users - [error] Missing validation error details in 400 response
GET /users - [page] Missing pagination metadata in list responserules/rest-http-methods.md
rules/error-consistent-format.md
rules/page-cursor-based.md