Loading...
Loading...
REST and API design principles — resource naming, HTTP methods, status codes, pagination, versioning, and error responses. Reference when designing or reviewing APIs.
npx skill4agent add claude-code-community-ireland/claude-code-resources api-design| Rule | Good | Bad |
|---|---|---|
| Use plural nouns | | |
| Use nouns, not verbs | | |
| Nest for relationships | | |
| Use kebab-case | | |
| Keep URLs shallow (max 3) | | |
| Use query params for filters | | |
| Collection + resource IDs | | |
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 collectionPOST /users/123/activate # State transition
POST /orders/456/refund # Domain action
POST /reports/export # Process trigger| Method | Purpose | Idempotent | Safe | Request Body | Success Code |
|---|---|---|---|---|---|
| Retrieve resource(s) | Yes | Yes | No | 200 |
| Create a resource | No | No | Yes | 201 |
| Full replacement | Yes | No | Yes | 200 |
| Partial update | No* | No | Yes | 200 |
| Remove a resource | Yes | No | No | 204 |
PATCHGETPOSTPUTPATCHDELETEGET /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| Code | Name | When to Use |
|---|---|---|
| OK | Successful GET, PUT, PATCH, or DELETE with body |
| Created | Successful POST that created a resource |
| No Content | Successful DELETE or PUT with no response body |
| Code | Name | When to Use |
|---|---|---|
| Bad Request | Malformed JSON, invalid syntax |
| Unauthorized | Missing or invalid authentication credentials |
| Forbidden | Authenticated but not authorized for this action |
| Not Found | Resource does not exist at this URL |
| Conflict | Resource state conflict (duplicate, version mismatch) |
| Unprocessable Entity | Valid JSON but fails business validation |
| Too Many Requests | Rate limit exceeded |
| Code | Name | When to Use |
|---|---|---|
| Internal Server Error | Unexpected server failure |
| Service Unavailable | Server is down for maintenance or overloaded |
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{
"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"
}
}codemessagedetailsrequest_id| Error Code | HTTP Status | Description |
|---|---|---|
| 422 | One or more fields are invalid |
| 404 | Requested resource does not exist |
| 401 | No valid credentials provided |
| 403 | Insufficient permissions |
| 409 | Resource state conflict |
| 429 | Too many requests |
| 500 | Unexpected server error |
| 503 | Dependency or server is down |
GET /api/v1/orders?limit=20&cursor=eyJpZCI6MTAwfQ{
"data": [ ... ],
"pagination": {
"next_cursor": "eyJpZCI6MTIwfQ",
"has_more": true
}
}has_moreGET /api/v1/products?page=3&per_page=25{
"data": [ ... ],
"pagination": {
"page": 3,
"per_page": 25,
"total_count": 342,
"total_pages": 14
}
}| Approach | Use When |
|---|---|
| Cursor-based | Real-time data, infinite scroll, large datasets |
| Offset-based | Admin panels, reports where total count is needed |
limitper_pagedata| Strategy | Example | Pros | Cons |
|---|---|---|---|
| URL path | | Explicit, easy to route | URL changes on version bump |
| Header | | Clean URLs | Hidden, harder to test |
| Query param | | Easy to add | Not standard, clutters query |
/api/v1/users # Version 1
/api/v2/users # Version 2 (breaking changes)Sunset| Header | Value |
|---|---|
| Maximum requests per window |
| Requests remaining in current window |
| Unix timestamp when the window resets |
| Seconds to wait (on 429 responses only) |
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"
}
}| Pattern | Use Case | Header |
|---|---|---|
| Bearer Token | User authentication (JWT, OAuth2) | |
| API Key | Service-to-service, third-party integrations | |
| OAuth2 | Third-party user authorization | |
| Basic Auth | Simple internal services (over HTTPS only) | |
WWW-Authenticate{
"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" }
]
}
}GET /api/v1/orders?status=pending&created_after=2025-01-01
GET /api/v1/products?category=electronics&min_price=100&max_price=500GET /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 fieldsGET /api/v1/users?fields=id,name,email
GET /api/v1/orders?fields=id,total,status