plugins/vibeworks-library/skills/api-design/SKILL.md
REST and API design principles — resource naming, HTTP methods, status codes, pagination, versioning, and error responses. Reference when designing or reviewing APIs.
npx skillsauth add claude-code-community-ireland/claude-code-resources api-designInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
| Rule | Good | Bad |
|-----------------------------|-----------------------------------|--------------------------------------|
| Use plural nouns | /users | /user, /getUsers |
| Use nouns, not verbs | POST /orders | POST /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 |
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
For operations that are not simple CRUD, use a sub-resource or action noun:
POST /users/123/activate # State transition
POST /orders/456/refund # Domain action
POST /reports/export # Process trigger
| Method | Purpose | Idempotent | Safe | Request Body | Success Code |
|----------|----------------------|------------|------|--------------|--------------|
| GET | Retrieve resource(s) | Yes | Yes | No | 200 |
| POST | Create a resource | No | No | Yes | 201 |
| PUT | Full replacement | Yes | No | Yes | 200 |
| PATCH | Partial update | No* | No | Yes | 200 |
| DELETE | Remove a resource | Yes | No | No | 204 |
*PATCH can be made idempotent with proper implementation but is not guaranteed by the spec.
GET must never modify server statePOST is the only method for creating new resourcesPUT sends the complete resource; omitted fields are set to defaults or nullPATCH sends only the fields to change; omitted fields remain unchangedDELETE returns 204 on success, and is a no-op if the resource already does not existGET /api/v1/users/123
Accept: application/json
---
POST /api/v1/users
Content-Type: application/json
{
"name": "Jane Doe",
"email": "[email protected]"
}
---
PUT /api/v1/users/123
Content-Type: application/json
{
"name": "Jane Doe",
"email": "[email protected]",
"role": "admin"
}
---
PATCH /api/v1/users/123
Content-Type: application/json
{
"role": "admin"
}
---
DELETE /api/v1/users/123
| Code | Name | When to Use |
|-------|--------------|------------------------------------------------------|
| 200 | OK | Successful GET, PUT, PATCH, or DELETE with body |
| 201 | Created | Successful POST that created a resource |
| 204 | No Content | Successful DELETE or PUT with no response body |
| Code | Name | When to Use |
|-------|---------------------|----------------------------------------------------|
| 400 | Bad Request | Malformed JSON, invalid syntax |
| 401 | Unauthorized | Missing or invalid authentication credentials |
| 403 | Forbidden | Authenticated but not authorized for this action |
| 404 | Not Found | Resource does not exist at this URL |
| 409 | Conflict | Resource state conflict (duplicate, version mismatch)|
| 422 | Unprocessable Entity| Valid JSON but fails business validation |
| 429 | Too Many Requests | Rate limit exceeded |
| Code | Name | When to Use |
|-------|----------------------|---------------------------------------------------|
| 500 | Internal Server Error| Unexpected server failure |
| 503 | 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
All error responses must follow this format:
{
"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"
}
}
code (uppercase snake_case)messagedetails array for field-level validation errorsrequest_id for traceability| Error Code | HTTP Status | Description |
|-------------------------|-------------|------------------------------------|
| VALIDATION_FAILED | 422 | One or more fields are invalid |
| RESOURCE_NOT_FOUND | 404 | Requested resource does not exist |
| AUTHENTICATION_REQUIRED| 401 | No valid credentials provided |
| PERMISSION_DENIED | 403 | Insufficient permissions |
| CONFLICT | 409 | Resource state conflict |
| RATE_LIMIT_EXCEEDED | 429 | Too many requests |
| INTERNAL_ERROR | 500 | Unexpected server error |
| SERVICE_UNAVAILABLE | 503 | Dependency or server is down |
Best for real-time data, large datasets, and consistent results.
Request:
GET /api/v1/orders?limit=20&cursor=eyJpZCI6MTAwfQ
Response:
{
"data": [ ... ],
"pagination": {
"next_cursor": "eyJpZCI6MTIwfQ",
"has_more": true
}
}
Implementation notes:
has_more booleanSimpler but has consistency issues on changing data.
Request:
GET /api/v1/products?page=3&per_page=25
Response:
{
"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 |
limit or per_page to a sensible value (e.g., 20)data array (not null) when no results match| Strategy | Example | Pros | Cons |
|----------------|----------------------------------|--------------------------------------|------------------------------------|
| URL path | /api/v1/users | Explicit, easy to route | URL changes on version bump |
| Header | Accept: application/vnd.api+json; version=1 | Clean URLs | Hidden, harder to test |
| Query param | /api/users?version=1 | Easy to add | Not standard, clutters query |
/api/v1/users # Version 1
/api/v2/users # Version 2 (breaking changes)
Sunset header to communicate deprecation dateInclude these headers in every response:
| Header | Value |
|-------------------------|------------------------------------------|
| X-RateLimit-Limit | Maximum requests per window |
| X-RateLimit-Remaining| Requests remaining in current window |
| X-RateLimit-Reset | Unix timestamp when the window resets |
| Retry-After | 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) | Authorization: Bearer <token> |
| API Key | Service-to-service, third-party integrations| X-API-Key: <key> or query param |
| OAuth2 | Third-party user authorization | Authorization: Bearer <access_token>|
| Basic Auth | Simple internal services (over HTTPS only) | Authorization: Basic <base64> |
WWW-Authenticate header in 401 responses{
"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.
GET /api/v1/orders?status=pending&created_after=2025-01-01
GET /api/v1/products?category=electronics&min_price=100&max_price=500
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
GET /api/v1/users?fields=id,name,email
GET /api/v1/orders?fields=id,total,status
tools
Automate changelog generation from commits, PRs, and releases following Keep a Changelog format. Use when setting up release workflows, generating release notes, or standardizing commit conventions.
documentation
Project Guidelines Skill (Example)
development
Development skill from everything-claude-code
documentation
Create beautiful visual art in .png and .pdf documents using design philosophy. You should use this skill when the user asks to create a poster, piece of art, design, or other static piece. Create original visual designs, never copying existing artists' work to avoid copyright violations.