Back to skills
SKILL.md
Api Design And Documentation
ASecurityUse for REST API design. Resources, schemas, OpenAPI, auth.
- 2 stars
- 0 votes
- 0 copies
- 3 views
- Added September 10, 2026
Works with
Security analysis
100/100npx -y skills add LoopyLuci/Skills --skill api-design-and-documentation --agent claude-codeAre you the author of Api Design And Documentation?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/loopyluci-api-design-and-documentation)---
name: api-design-and-documentation
description: "Use for REST API design. Resources, schemas, OpenAPI, auth."
version: 1.1.0
author: Hermes Agent
license: MIT
platforms: [linux, macos, windows]
metadata:
hermes:
tags: [programming, api, rest, design, documentation, openapi]
related_skills: [database-schema-design, code-review-checklist]
---
# API Design & Documentation
## Overview
A comprehensive methodology for designing RESTful APIs that are consistent, discoverable, and maintainable. Covers resource modeling, endpoint naming conventions, status codes, request/response schema design, pagination, error handling, authentication/authorization strategies, and OpenAPI/Swagger spec generation with schema validation patterns.
## When to Use
- Designing a new REST API from scratch
- Reviewing or standardizing an existing API design
- Generating OpenAPI 3.x specs for documentation or client generation
- Planning versioning, deprecation, or migration strategies
- Onboarding teams to API-first development workflows
## Workflow
### Phase 1: Resource Modeling
Identify domain entities and their relationships.
```bash
# Discover domain entities from an existing codebase
grep -rh --include='*.py' -E '^class [A-Z]\w+(Model|Schema|Entity|DTO)\b' . | head -30
grep -rh --include='*.ts' --include='*.js' -E '^(export )?interface [A-Z]\w+' . | head -30
grep -rh --include='*.{ts,js}' -E '^(export )?type [A-Z]\w+ =' . | head -30
```
**Resource naming principles:**
- Use **plural nouns** for collections: `/users`, `/orders`, `/products`
- Use **singular nouns** for singletons or single-instance: `/profile`, `/config`
- Use **nesting** for sub-resources only when the child is inseparable from the parent: `/users/{id}/addresses`
- Avoid nest > 2 levels — introduce sub-resource identifiers instead
**Resource relationships table:**
| Endpoint | Collection/Item | Example |
|----------|----------------|---------|
| `GET /users` | Collection | List users |
| `GET /users/{id}` | Item | Get one user |
| `POST /users` | Collection | Create user |
| `PUT /users/{id}` | Item | Replace user |
| `PATCH /users/{id}` | Item | Partial update |
| `DELETE /users/{id}` | Item | Remove user |
### Phase 2: Endpoint Design with Standard HTTP Verbs
Use HTTP verbs semantically:
```python
# Example: Flask resource routing
# GET /users → list_users()
# POST /users → create_user()
# GET /users/{id} → get_user(id)
# PUT /users/{id} → update_user(id) # full replacement
# PATCH /users/{id} → patch_user(id) # partial update
# DELETE /users/{id} → delete_user(id)
# Action endpoints (for non-CRUD operations):
# POST /users/{id}/activate → activate_user(id)
# POST /orders/{id}/cancel → cancel_order(id)
# POST /payments/{id}/refund → refund_payment(id)
```
**Naming conventions:**
- Use kebab-case for resource names: `/order-items`, not `/orderItems` or `/order_items`
- Use snake_case for query parameters: `?page_number=1&page_size=20`
- Use camelCase in JSON response bodies (JavaScript convention)
- Use lowercase for all URL segments
- Use hyphens (not underscores) in URLs
### Phase 3: Status Code Strategy
```python
STATUS_CODES = {
# Success
200: "GET - Success, DELETE - Success",
201: "POST - Resource created",
202: "Accepted for async processing",
204: "Success, no body (DELETE, PUT)",
# Client Errors
400: "Bad request - malformed syntax or invalid params",
401: "Unauthenticated - missing/invalid credentials",
403: "Forbidden - authenticated but not authorized",
404: "Not found - resource doesn't exist",
405: "Method not allowed",
409: "Conflict - resource state conflict (e.g. duplicate)",
422: "Unprocessable entity - validation errors",
429: "Too many requests - rate limited",
# Server Errors
500: "Internal server error",
502: "Bad gateway",
503: "Service unavailable",
504: "Gateway timeout",
}
```
### Phase 4: Request/Response Schema Design
**Pagination:**
```json
// Request
GET /users?page=2&per_page=20
GET /users?cursor=eyJpZCI6MX0&limit=20
// Response (offset-based)
{
"data": [...],
"pagination": {
"page": 2,
"per_page": 20,
"total_items": 142,
"total_pages": 8,
"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=8&per_page=20"
}
}
}
// Response (cursor-based - preferred for real-time data)
{
"data": [...],
"pagination": {
"next_cursor": "eyJpZCI6NTB9",
"has_more": true
}
}
```
**Error response envelope:**
```json
{
"error": {
"code": "VALIDATION_ERROR",
"message": "The request was invalid",
"details": [
{
"field": "email",
"message": "must be a valid email address",
"code": "INVALID_FORMAT"
}
],
"request_id": "req_abc123",
"docs_url": "https://api.example.com/docs/errors#VALIDATION_ERROR"
}
}
```
**Standard response envelope (recommended for consistency):**
```json
{
"success": true,
"data": { ... },
"meta": {
"request_id": "req_abc123",
"timestamp": "2025-07-28T12:00:00Z"
}
}
```
### Phase 5: Authentication & Authorization
```yaml
# Strategy selection
API Keys:
- Best for: Machine-to-machine, simple auth
- Transport: Header (X-API-Key) or query param
- Risk: Low entropy, easy to leak
- Pattern:
request.headers['X-API-Key'] == stored_key
JWT (Bearer Token):
- Best for: User-facing APIs, stateless auth
- Transport: Authorization: Bearer <token>
- Claims: sub, role, exp, iat, jti
- Signature: RS256 (asymmetric) preferred over HS256
- Pattern:
token = request.headers['Authorization'].split('Bearer ')[1]
payload = jwt.decode(token, public_key, algorithms=['RS256'])
OAuth2:
- Best for: Third-party app authorization
- Flows:
- Authorization Code (+ PKCE) → Web apps, mobile
- Client Credentials → Server-to-server
- Device Code → CLI tools, IoT
- Scopes: granular permission model
API Key + JWT hybrid:
- API key identifies the client application
- JWT identifies the user and carries permissions
- Both validated on every request
```
### Phase 6: OpenAPI / Swagger Spec Generation
Generate a spec from code annotations or from a skeleton:
```yaml
openapi: 3.0.3
info:
title: Users API
description: User management API
version: 1.0.0
contact:
name: API Team
url: https://api.example.com/support
servers:
- url: https://api.example.com/v1
description: Production
- url: https://staging-api.example.com/v1
description: Staging
paths:
/users:
get:
operationId: listUsers
summary: List all users
parameters:
- name: page
in: query
schema:
type: integer
default: 1
- name: per_page
in: query
schema:
type: integer
default: 20
maximum: 100
responses:
'200':
description: Paginated list of users
content:
application/json:
schema:
$ref: '#/components/schemas/UserListResponse'
post:
operationId: createUser
summary: Create a new user
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateUserRequest'
responses:
'201':
description: User created
content:
application/json:
schema:
$ref: '#/components/schemas/User'
'422':
description: Validation error
$ref: '#/components/responses/ValidationError'
components:
schemas:
User:
type: object
required: [id, email, name]
properties:
id:
type: string
format: uuid
readOnly: true
email:
type: string
format: email
name:
type: string
minLength: 1
maxLength: 100
created_at:
type: string
format: date-time
readOnly: true
CreateUserRequest:
type: object
required: [email, name]
properties:
email:
type: string
format: email
name:
type: string
password:
type: string
minLength: 8
writeOnly: true
UserListResponse:
type: object
properties:
data:
type: array
items:
$ref: '#/components/schemas/User'
pagination:
$ref: '#/components/schemas/Pagination'
Pagination:
type: object
properties:
page:
type: integer
per_page:
type: integer
total_items:
type: integer
total_pages:
type: integer
responses:
ValidationError:
description: Validation error
content:
application/json:
schema:
type: object
properties:
error:
$ref: '#/components/schemas/ErrorDetail'
ErrorDetail:
type: object
properties:
code:
type: string
message:
type: string
details:
type: array
items:
type: object
properties:
field:
type: string
message:
type: string
```
### Phase 7: API Versioning & Deprecation
```python
VERSIONING_STRATEGIES = {
"url_path": "https://api.example.com/v1/users", # Most common
"header": "Accept: application/vnd.example.v1+json", # More flexible
"query": "https://api.example.com/users?version=1", # Cache-busting issues
}
# Deprecation headers (return on every response to deprecated endpoints)
{
"Sunset": "Sat, 31 Dec 2025 23:59:59 GMT",
"Deprecation": "true",
"Link": "<https://api.example.com/docs/v2-migration>; rel=\"deprecation\""
}
```
**Recommendation:** Use URL path versioning (`/v1/`, `/v2/`) for public APIs. Use header/content-type versioning for internal/microservice APIs.
### Phase 8: Validation & Schema Design Patterns
```python
# Pattern 1: Input validation with Pydantic (Python)
from pydantic import BaseModel, EmailStr, Field, field_validator
from datetime import datetime
from uuid import UUID, uuid4
class CreateUserRequest(BaseModel):
email: EmailStr
name: str = Field(..., min_length=1, max_length=100)
password: str = Field(..., min_length=8)
role: str = Field(default="user", pattern=r"^(admin|user|viewer)$")
@field_validator('password')
@classmethod
def password_strength(cls, v: str) -> str:
if not any(c.isdigit() for c in v):
raise ValueError('password must contain a digit')
if not any(c.isupper() for c in v):
raise ValueError('password must contain uppercase')
return v
class UserResponse(BaseModel):
id: UUID
email: EmailStr
name: str
role: str
created_at: datetime
model_config = {"from_attributes": True}
# Pattern 2: TypeScript interface for frontend consumption
"""
interface CreateUserRequest {
email: string; // format: email
name: string; // 1-100 chars
password: string; // min 8 chars
role?: 'admin' | 'user' | 'viewer';
}
interface User {
id: string; // uuid
email: string;
name: string;
role: string;
created_at: string; // date-time
}
"""
```
## Common Pitfalls
- **Over-nesting resources**: Beyond 2 levels, use query params or sub-resource IDs. `/orgs/{id}/projects/{pid}/tasks/{tid}` is too deep — use `/tasks?project_id=xxx`.
- **Inconsistent error responses**: Always return the same error envelope structure. Never mix string errors with structured errors.
- **Exposing internal IDs**: Use UUIDs for public identifiers, not auto-increment integers. This prevents enumeration attacks.
- **Skipping rate limits**: Every public endpoint needs rate limiting headers (`X-RateLimit-Remaining`, `X-RateLimit-Reset`).
- **No pagination on list endpoints**: All list endpoints must paginate. Default to sane limits (20-50 per page). Never return unbounded collections.
- **PUT vs PATCH confusion**: PUT replaces the entire resource. PATCH applies partial changes. Use PATCH for single-field updates.
- **Ignoring idempotency**: PUT, DELETE should be idempotent. POST is not. For payment APIs, use Idempotency-Key headers.
- **API without documentation**: If it's not documented, it doesn't exist. Generate OpenAPI specs from code or maintain them as source of truth.
- **Breaking changes without deprecation**: Never remove or change existing fields without a deprecation period and sunset header.
- **Leaking stack traces**: Never return raw exceptions or stack traces in production API responses. Use generic error codes and log full details server-side.
## Verification Checklist
- [ ] All endpoints use standard HTTP verbs semantically (GET/POST/PUT/PATCH/DELETE)
- [ ] Resource names are plural nouns, kebab-case in URLs
- [ ] Consistent error response envelope across all endpoints
- [ ] All mutation endpoints validate input with schema validation
- [ ] Pagination implemented on all list endpoints (with sensible defaults)
- [ ] Authentication properly implemented (JWT / API Key / OAuth2)
- [ ] Authorization enforced per resource/per action
- [ ] Rate limiting configured and headers returned
- [ ] OpenAPI 3.x spec covers all endpoints with request/response schemas
- [ ] Spec renders without errors in Swagger UI / Redoc
- [ ] CORS configured for allowed origins
- [ ] Versioning strategy chosen and implemented
- [ ] Deprecation headers (Sunset, Deprecation, Link) on deprecated endpoints
- [ ] No secrets, stack traces, or internal IDs in responses
- [ ] Idempotency considered for critical POST endpoints
Attribution
Comments
Loading comments…