with one click
api-documentation
// API Documentation skill - write comprehensive API documentation from scratch
// API Documentation skill - write comprehensive API documentation from scratch
| name | api-documentation |
| description | API Documentation skill - write comprehensive API documentation from scratch |
api-documentation mode: DOCUMENT EVERY ENDPOINT
when this skill is active, you follow API documentation best practices. this is a comprehensive guide to writing world-class API documentation.
PHASE 0: PREREQUISITE DISCOVERY
before writing ANY API documentation, discover what exists.
check for existing API specifications
find . -name "openapi.yaml" -o -name "openapi.yml" -o -name "swagger.json" 2>/dev/null find . -name ".spec.yaml" -o -name ".spec.yml" 2>/dev/null
if OpenAPI spec exists: openapi.yaml analyze current completeness identify missing sections
if no spec exists: create one from scratch this becomes the source of truth
check for existing documentation
find . -type d -name "docs" 2>/dev/null ls -la docs/ 2>/dev/null || echo "no docs directory" find docs -name "api" -o -name "endpoint" 2>/dev/null
read existing docs to understand:
check for API source code
find . -name "routes.py" -o -name "api.py" -o -name "controller.py" 2>/dev/null | head -20 find . -name "routes.js" -o -name "api.js" -o -name "controller.js" 2>/dev/null | head -20 find . -name "handlers.go" 2>/dev/null | head -10
for each route file found: path/to/routes.py extract endpoint definitions note request/response patterns identify authentication requirements
check for authentication setup
grep -r "jwt|oauth|api.key|bearer|auth" --include=".py" --include="*.js" . 2>/dev/null | head -20 find . -name "auth.py" -o -name "auth.js" 2>/dev/null | head -10
understand:
check for testing files
find . -name "testapi*.py" -o -name "testroute*.py" 2>/dev/null | head -10 find . -name "testapi*.js" 2>/dev/null | head -10
test files contain:
these are GOLD for documentation examples.
PHASE 1: UNDERSTANDING YOUR API
before documenting, understand what the API does.
inventory all endpoints
create a complete endpoint inventory:
method path description
GET /api/users list all users POST /api/users create new user GET /api/users/{id} get user by ID PUT /api/users/{id} update user DELETE /api/users/{id} delete user GET /api/users/{id}/posts get user's posts
use this command to find routes: grep -r "@app|@router|@bp.route|.get|.post|.put|.delete" --include=".py" . 2>/dev/null grep -r "router.|app." --include=".js" . 2>/dev/null | grep -E "get|post|put|delete|patch" | head -30
group endpoints by resource
organize into logical resource groups:
users: GET /api/users POST /api/users GET /api/users/{id} PUT /api/users/{id} DELETE /api/users/{id}
posts: GET /api/posts POST /api/posts GET /api/posts/{id} PUT /api/posts/{id} DELETE /api/posts/{id} GET /api/posts/{id}/comments
comments: POST /api/posts/{id}/comments DELETE /api/comments/{id}
documentation should follow this grouping.
identify common patterns
look for patterns across endpoints:
[ ] pagination (page, limit, offset) [ ] filtering (filter[field]=value) [ ] sorting (sort=field, order=asc|desc) [ ] search (q=query) [ ] field selection (fields=id,name) [ ] includes (include=related) [ ] versioning (/v1/, /v2/) [ ] rate limiting headers [ ] standard response format
document patterns ONCE, then reference them.
PHASE 2: OPENAPI SPECIFICATION STRUCTURE
the OpenAPI specification is your foundation.
create the base spec
openapi.yaml openapi: 3.0.3 info: title: My API description: | Detailed description of what this API does.## Authentication
All endpoints require authentication using API keys or JWT tokens.
## Rate Limiting
Rate limits are enforced per API key.
version: 1.0.0 contact: name: API Support email: support@example.com license: name: MIT url: https://opensource.org/licenses/MIT
servers:
tags:
paths:
components: securitySchemes: bearerAuth: type: http scheme: bearer bearerFormat: JWT apiKeyAuth: type: apiKey in: header name: X-API-Key
schemas: Error: type: object required: - code - message properties: code: type: string example: "validation_error" message: type: string example: "Validation failed" details: type: object additionalProperties: true
document a single endpoint fully
paths: /users: get: summary: List all users description: | Returns a paginated list of users. By default, returns 20 users per page.
The response includes user profiles with basic information.
tags:
- users
security:
- bearerAuth: []
parameters:
- name: page
in: query
description: Page number for pagination
required: false
schema:
type: integer
default: 1
minimum: 1
- name: limit
in: query
description: Number of items per page
required: false
schema:
type: integer
default: 20
minimum: 1
maximum: 100
- name: sort
in: query
description: Sort field and order
required: false
schema:
type: string
enum: [name_asc, name_desc, created_asc, created_desc]
default: created_desc
- name: status
in: query
description: Filter by user status
required: false
schema:
type: string
enum: [active, inactive, suspended]
responses:
'200':
description: Successful response
content:
application/json:
schema:
type: object
required:
- data
- pagination
properties:
data:
type: array
items:
$ref: '#/components/schemas/User'
pagination:
$ref: '#/components/schemas/Pagination'
examples:
success:
summary: Successful response
value:
data:
- id: "1"
name: "Alice Johnson"
email: "alice@example.com"
status: "active"
created_at: "2024-01-15T10:30:00Z"
pagination:
page: 1
limit: 20
total: 145
pages: 8
'400':
description: Bad request - invalid parameters
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
example:
code: "validation_error"
message: "Invalid sort parameter"
details:
field: "sort"
errors: ["Must be one of: name_asc, name_desc"]
'401':
description: Unauthorized - missing or invalid token
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
example:
code: "unauthorized"
message: "Authentication required"
'429':
description: Too many requests
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
example:
code: "rate_limit_exceeded"
message: "Rate limit exceeded. Try again in 1 minute."
headers:
X-RateLimit-Limit:
schema:
type: integer
description: Request limit per time window
X-RateLimit-Remaining:
schema:
type: integer
description: Requests remaining in window
X-RateLimit-Reset:
schema:
type: integer
description: Unix timestamp when limit resets
PHASE 3: REQUEST DOCUMENTATION
every possible input must be documented.
path parameters
parameters: - name: user_id in: path description: | The unique identifier of the user.
This can be either:
- The numeric user ID
- The string "me" for the authenticated user
Example: `/users/123` or `/users/me`
required: true
schema:
type: string
pattern: '^[a-z0-9_-]+$'
example: "123"
always document: [ ] what the parameter represents [ ] valid values or patterns [ ] if it's optional (should not be for path params) [ ] example values
query parameters
parameters: - name: include in: query description: | Related resources to include in the response.
Multiple values can be comma-separated.
Available includes:
- `profile` - user profile information
- `settings` - user preferences
- `stats` - user statistics
Example: `?include=profile,stats`
required: false
schema:
type: string
example: "profile,stats"
- name: fields
in: query
description: |
Comma-separated list of fields to return.
Use this to reduce response size by requesting only needed fields.
Example: `?fields=id,name,email`
required: false
schema:
type: string
example: "id,name,email"
- name: filter
in: query
description: |
Filter results by field values.
Syntax: `filter[field]=value`
Multiple filters can be combined.
Supported operators:
- `filter[name]=Alice` - exact match
- `filter[name][contains]=Ali` - contains
- `filter[name][starts]=Al` - starts with
- `filter[age][gte]=18` - greater than or equal
- `filter[age][lte]=65` - less than or equal
Example: `?filter[status]=active&filter[age][gte]=18`
required: false
schema:
type: object
additionalProperties: true
style: deepObject
explode: true
request headers
document all relevant headers:
parameters: - name: Accept in: header description: | Response content format.
Supported values:
- `application/json` (default)
- `application/vnd.api+json` (JSON:API format)
required: false
schema:
type: string
enum: [application/json, application/vnd.api+json]
default: application/json
- name: Accept-Language
in: header
description: |
Preferred language for response messages.
Supported: `en`, `es`, `fr`, `de`
required: false
schema:
type: string
example: "en"
- name: Idempotency-Key
in: header
description: |
A unique key to ensure idempotent requests.
Use this for POST/PUT operations that should only execute once.
The key should be a UUID or unique identifier.
The key expires after 24 hours.
required: false
schema:
type: string
format: uuid
example: "550e8400-e29b-41d4-a716-446655440000"
request body
requestBodies: createUser: description: User object to create required: true content: application/json: schema: $ref: '#/components/schemas/CreateUserRequest' examples: minimal: summary: Minimal user creation value: email: "user@example.com" password: "SecurePassword123!" full: summary: Complete user creation with all fields value: email: "alice@example.com" password: "SecurePassword123!" name: "Alice Johnson" phone: "+1-555-0123" timezone: "America/New_York" locale: "en" withProfile: summary: User with profile information value: email: "bob@example.com" password: "SecurePassword123!" name: "Bob Smith" profile: bio: "Software developer" location: "San Francisco, CA" website: "https://bob.example.com" application/x-www-form-urlencoded: schema: type: object required: - email - password properties: email: type: string format: email password: type: string minLength: 8 name: type: string encoding: profile: style: form explode: false
components: schemas: CreateUserRequest: type: object required: - email - password properties: email: type: string format: email description: | User's email address. Must be unique across all users.
A confirmation email will be sent to this address.
example: "user@example.com"
password:
type: string
minLength: 8
maxLength: 128
description: |
User's password. Must be at least 8 characters.
Requirements:
- At least 8 characters
- At least one uppercase letter
- At least one number
- At least one special character
example: "SecurePass123!"
name:
type: string
maxLength: 100
description: User's display name
example: "Alice Johnson"
phone:
type: string
pattern: '^\+?[1-9]\d{1,14}$'
description: |
Phone number in E.164 format.
Include country code prefixed with +.
example: "+1-555-0123"
timezone:
type: string
description: User's timezone for display purposes
example: "America/New_York"
locale:
type: string
description: Preferred language/locale
example: "en"
PHASE 4: RESPONSE DOCUMENTATION
document all possible responses.
success response structure
responses: '200': description: Successful operation content: application/json: schema: allOf: - $ref: '#/components/schemas/SuccessResponse' - type: object properties: data: $ref: '#/components/schemas/User'
components: schemas: SuccessResponse: type: object required: - data - meta properties: data: description: The primary response data meta: type: object description: Metadata about the response properties: id: type: string description: Unique request ID for tracing timestamp: type: string format: date-time description: When the response was generated
pagination in responses
components: schemas: PaginatedResponse: type: object required: - data - pagination properties: data: type: array description: Array of items pagination: type: object required: - page - limit - total - pages properties: page: type: integer minimum: 1 description: Current page number example: 1 limit: type: integer minimum: 1 maximum: 100 description: Items per page example: 20 total: type: integer minimum: 0 description: Total number of items example: 145 pages: type: integer minimum: 1 description: Total number of pages example: 8 has_prev: type: boolean description: Whether there is a previous page has_next: type: boolean description: Whether there is a next page
resource schemas
components: schemas: User: type: object required: - id - email - created_at properties: id: type: string description: Unique user identifier example: "usr_abc123xyz" email: type: string format: email description: User's email address example: "alice@example.com" name: type: string nullable: true description: User's display name example: "Alice Johnson" avatar_url: type: string format: uri nullable: true description: URL to user's avatar image example: "https://cdn.example.com/avatars/usr_abc123xyz.jpg" status: type: string enum: [active, inactive, suspended, deleted] description: Current user status example: "active" role: type: string enum: [user, admin, moderator] description: User's role in the system example: "user" created_at: type: string format: date-time description: When the user account was created example: "2024-01-15T10:30:00Z" updated_at: type: string format: date-time nullable: true description: When the user was last updated example: "2024-01-20T14:22:00Z" last_login_at: type: string format: date-time nullable: true description: Last time user logged in example: "2024-01-25T09:15:00Z"
error responses
create standard error response types:
components: schemas: Error: type: object required: - error properties: error: type: object required: - code - message properties: code: type: string description: Machine-readable error code example: "validation_error" message: type: string description: Human-readable error message example: "The request failed validation" details: oneOf: - type: object additionalProperties: true description: Additional error details - type: array items: type: object properties: field: type: string description: Field name with error message: type: string description: Error message for this field code: type: string description: Error code for this field stacktrace: type: string description: Stack trace (development only) deprecated: true request_id: type: string description: Request ID for support example: "req_abc123xyz"
common error codes to document:
400 Bad Request - validation_error: Invalid input data - invalid_json: Malformed JSON - missing_required_field: Required field missing - invalid_format: Field format invalid
401 Unauthorized - unauthorized: No authentication provided - invalid_token: Token is invalid or expired - insufficient_permissions: Lacks required scope/role
403 Forbidden - access_denied: Access to resource denied - account_suspended: Account is suspended - quota_exceeded: API quota exceeded
404 Not Found - not_found: Resource does not exist
409 Conflict - duplicate: Resource already exists - conflict: State conflict
422 Unprocessable Entity - semantic_error: Request is valid but cannot be processed
429 Too Many Requests - rate_limit_exceeded: Rate limit exceeded - temporarily_unavailable: Service temporarily unavailable
500 Internal Server Error - internal_error: Unexpected server error
503 Service Unavailable - service_unavailable: Service is down for maintenance
PHASE 5: AUTHENTICATION DOCUMENTATION
clearly explain how to authenticate.
authentication overview section
create a dedicated authentication guide:
All API requests require authentication. We support two methods:
Most common method. Include your JWT token in the Authorization header.
First, authenticate to receive a token:
curl -X POST https://api.example.com/v1/auth/token \
-H "Content-Type: application/json" \
-d '{
"client_id": "your_client_id",
"client_secret": "your_client_secret",
"grant_type": "client_credentials"
}'
Response:
{
"access_token": "<access-token>",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "<refresh-token>",
"scope": "read write"
}
Include the token in subsequent requests:
curl https://api.example.com/v1/users \
-H "Authorization: Bearer <access-token>"
Access tokens expire after 1 hour. Use the refresh token to get a new access token:
curl -X POST https://api.example.com/v1/auth/refresh \
-H "Content-Type: application/json" \
-d '{
"refresh_token": "<refresh-token>"
}'
For simple integrations, use an API key.
Generate an API key from your dashboard:
Include the key in the X-API-Key header:
curl https://api.example.com/v1/users \
-H "X-API-Key: ${API_KEY}"
Or as a query parameter (not recommended for production):
curl "https://api.example.com/v1/users?api_key=${API_KEY}"
document scopes and permissions
Access tokens can include scopes for granular permissions:
| Scope | Description | Example Endpoints |
|---|---|---|
users:read | Read user information | GET /users, GET /users/{id} |
users:write | Create and modify users | POST /users, PUT /users/{id} |
users:delete | Delete users | DELETE /users/{id} |
posts:read | Read posts | GET /posts |
posts:write | Create and modify posts | POST /posts |
admin | Full administrative access | All endpoints |
Include scopes in your authentication request:
curl -X POST https://api.example.com/v1/auth/token \
-H "Content-Type: application/json" \
-d '{
"client_id": "your_client_id",
"client_secret": "your_client_secret",
"grant_type": "client_credentials",
"scope": "users:read posts:write"
}'
PHASE 6: INTERACTIVE DOCUMENTATION
make your API documentation interactive.
swagger UI setup
add Swagger UI to display interactive docs:
docs/swagger.html body { margin: 0; padding: 0; } #swagger-ui { max-width: 1460px; margin: 0 auto; }redoc setup
for an alternative clean documentation style:
docs/redoc.html API Reference body { margin: 0; padding: 0; }PHASE 7: CODE EXAMPLES
provide working examples in multiple languages.
curl examples
# List all users
curl "https://api.example.com/v1/users?page=1&limit=20" \
-H "Authorization: Bearer ${API_TOKEN}" \
-H "Accept: application/json"
# Create a new user
curl -X POST "https://api.example.com/v1/users" \
-H "Authorization: Bearer ${API_TOKEN}" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"email": "newuser@example.com",
"password": "SecurePassword123!",
"name": "New User"
}'
# Update a user
curl -X PUT "https://api.example.com/v1/users/usr_abc123" \
-H "Authorization: Bearer ${API_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"name": "Updated Name"
}'
# Delete a user
curl -X DELETE "https://api.example.com/v1/users/usr_abc123" \
-H "Authorization: Bearer ${API_TOKEN}"
python examples
import requests
import json
# Configure
base_url = "https://api.example.com/v1"
token = "${API_TOKEN}"
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json"
}
# List users
response = requests.get(
f"{base_url}/users",
headers=headers,
params={"page": 1, "limit": 20}
)
response.raise_for_status()
users = response.json()["data"]
print(f"Found {len(users)} users")
# Create user
new_user = {
"email": "newuser@example.com",
"password": "SecurePassword123!",
"name": "New User"
}
response = requests.post(
f"{base_url}/users",
headers=headers,
json=new_user
)
response.raise_for_status()
user = response.json()["data"]
print(f"Created user: {user['id']}")
# Upload file
with open("avatar.jpg", "rb") as f:
response = requests.put(
f"{base_url}/users/{user['id']}/avatar",
headers={"Authorization": f"Bearer {token}"},
files={"file": f}
)
response.raise_for_status()
javascript examples
// Using fetch API
const baseUrl = "https://api.example.com/v1";
const token = "${API_TOKEN}";
const headers = {
"Authorization": `Bearer ${token}`,
"Content-Type": "application/json",
"Accept": "application/json"
};
// List users
async function listUsers(page = 1) {
const response = await fetch(
`${baseUrl}/users?page=${page}&limit=20`,
{ headers }
);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data.data;
}
// Create user
async function createUser(userData) {
const response = await fetch(`${baseUrl}/users`, {
method: "POST",
headers: {
...headers,
"Idempotency-Key": crypto.randomUUID()
},
body: JSON.stringify(userData)
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.error.message);
}
const data = await response.json();
return data.data;
}
// Usage
try {
const users = await listUsers();
console.log(`Found ${users.length} users`);
const newUser = await createUser({
email: "newuser@example.com",
password: "SecurePassword123!",
name: "New User"
});
console.log(`Created: ${newUser.id}`);
} catch (error) {
console.error("Error:", error.message);
}
node.js with axios
const axios = require('axios');
// Configure client
const api = axios.create({
baseURL: 'https://api.example.com/v1',
headers: {
'Authorization': `Bearer ${process.env.API_TOKEN}`,
'Content-Type': 'application/json'
}
});
// List users
async function listUsers() {
try {
const response = await api.get('/users', {
params: { page: 1, limit: 20 }
});
return response.data.data;
} catch (error) {
if (error.response) {
throw new Error(error.response.data.error.message);
}
throw error;
}
}
// Create user with automatic retry
async function createUser(userData, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const response = await api.post('/users', userData, {
headers: {
'Idempotency-Key': require('crypto').randomUUID()
}
});
return response.data.data;
} catch (error) {
if (error.response?.status === 429 && attempt < maxRetries - 1) {
// Rate limited - wait and retry
const retryAfter = error.response.headers['retry-after'];
await new Promise(r => setTimeout(r, (retryAfter || 1) * 1000));
continue;
}
throw error;
}
}
}
go examples
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
const (
BaseURL = "https://api.example.com/v1"
Token = "${API_TOKEN}"
)
type Client struct {
HTTPClient *http.Client
BaseURL string
Token string
}
func NewClient(token string) *Client {
return &Client{
HTTPClient: &http.Client{},
BaseURL: BaseURL,
Token: token,
}
}
func (c *Client) doRequest(method, path string, body io.Reader) (*http.Response, error) {
req, err := http.NewRequest(method, c.BaseURL+path, body)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bearer "+c.Token)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")
return c.HTTPClient.Do(req)
}
type User struct {
ID string `json:"id"`
Email string `json:"email"`
Name string `json:"name"`
}
func (c *Client) ListUsers(page int) ([]User, error) {
resp, err := c.doRequest("GET", fmt.Sprintf("/users?page=%d", page), nil)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var result struct {
Data []User `json:"data"`
}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, err
}
return result.Data, nil
}
func (c *Client) CreateUser(user User) (*User, error) {
body, _ := json.Marshal(user)
resp, err := c.doRequest("POST", "/users", bytes.NewReader(body))
if err != nil {
return nil, err
}
defer resp.Body.Close()
var result struct {
Data User `json:"data"`
}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, err
}
return &result.Data, nil
}
PHASE 8: GUIDES AND WALKTHROUGNS
provide narrative documentation beyond reference.
quick start guide
Get started with the API in 5 minutes.
Sign up at https://example.com/signup and get your API key from the dashboard.
curl "https://api.example.com/v1/users" \
-H "X-API-Key: ${API_KEY}"
Response:
{
"data": [],
"pagination": {
"page": 1,
"limit": 20,
"total": 0,
"pages": 0
}
}
curl -X POST "https://api.example.com/v1/users" \
-H "X-API-Key: ${API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com",
"name": "Hello World"
}'
Browse the full API reference or check out more guides.
common workflows
List endpoints support pagination:
import requests
def get_all_users():
page = 1
all_users = []
while True:
response = requests.get(
"https://api.example.com/v1/users",
headers={"Authorization": f"Bearer {token}"},
params={"page": page, "limit": 100}
).json()
all_users.extend(response["data"])
if not response["pagination"]["has_next"]:
break
page += 1
return all_users
# Filter active users, sorted by name
curl "https://api.example.com/v1/users?filter[status]=active&sort=name_asc" \
-H "Authorization: Bearer ${API_TOKEN}"
# Complex filtering
curl "https://api.example.com/v1/users?filter[age][gte]=18&filter[status]=active&sort=created_desc" \
-H "Authorization: Bearer ${API_TOKEN}"
import requests
from requests.exceptions import HTTPError
def api_call(method, endpoint, **kwargs):
try:
response = requests.request(
method,
f"https://api.example.com/v1{endpoint}",
headers={"Authorization": f"Bearer {token}"},
**kwargs
)
response.raise_for_status()
return response.json()
except HTTPError as e:
error_data = e.response.json()
print(f"Error: {error_data['error']['code']}")
print(f"Message: {error_data['error']['message']}")
# Handle specific error codes
if error_data['error']['code'] == 'rate_limit_exceeded':
# Implement backoff retry
pass
elif error_data['error']['code'] == 'unauthorized':
# Refresh token
pass
raise
Set up webhooks to receive notifications:
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/webhook', methods=['POST'])
def handle_webhook():
# Verify signature
signature = request.headers.get('X-Webhook-Signature')
if not verify_signature(signature, request.data):
return jsonify({"error": "invalid signature"}), 401
# Process event
event = request.json
event_type = event['type']
if event_type == 'user.created':
handle_user_created(event['data'])
elif event_type == 'user.deleted':
handle_user_deleted(event['data'])
return jsonify({"status": "received"}), 200
PHASE 9: VERSIONING DOCUMENTATION
clearly document API versioning strategy.
versioning overview
The API uses URL-based versioning. The current version is v1.
https://api.example.com/v{version}/{resource}
Example:
https://api.example.com/v1/users
https://api.example.com/v2/users
| Version | Status | Release Date | Deprecation Date | Sunset Date |
|---|---|---|---|---|
| v2 | Current | 2024-06-01 | - | - |
| v1 | Deprecated | 2023-01-01 | 2024-06-01 | 2025-01-01 |
Deprecation headerDeprecation header:
Deprecation: true
Sunset: Sat, 01 Jan 2025 00:00:00 GMT
Link: <https://docs.example.com/api/v2>; rel="successor-version"
version changes documentation
Breaking changes in v2:
[1] Response format change
v1: { "users": [...] }
v2: { "data": [...] }
Migration: Update response parsing:
```python
# v1
users = response.json()["users"]
# v2
users = response.json()["data"]
```
[2] Authentication header changed
v1: X-Auth-Token: <token>
v2: Authorization: Bearer <token>
Migration: Update authentication headers
[3] User ID format changed
v1: Integer IDs (123)
v2: String IDs (usr_abc123xyz)
Migration: Update ID handling code
[4] Pagination parameter renamed
v1: per_page
v2: limit
Migration: Update parameter names
Non-breaking additions in v2:
PHASE 10: TESTING AND EXAMPLES
provide testable examples and sandbox.
sandbox environment
Test the API without affecting real data.
https://sandbox-api.example.com/v1
The sandbox provides:
# Sandbox test account
curl "https://sandbox-api.example.com/v1/auth/token" \
-H "Content-Type: application/json" \
-d '{
"client_id": "test_client_id",
"client_secret": "test_client_secret",
"grant_type": "client_credentials"
}'
Response:
{
"access_token": "test_token_abc123",
"token_type": "Bearer",
"expires_in": 3600
}
example data sets
provide realistic example data:
{
"id": "usr_abc123xyz",
"email": "alice.johnson@example.com",
"name": "Alice Johnson",
"avatar_url": "https://cdn.example.com/avatars/usr_abc123xyz.jpg",
"status": "active",
"role": "user",
"timezone": "America/New_York",
"locale": "en",
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-20T14:22:00Z",
"last_login_at": "2024-01-25T09:15:00Z",
"profile": {
"bio": "Software developer and coffee enthusiast",
"location": "San Francisco, CA",
"website": "https://alice.example.com",
"twitter": "@alicejohnson"
},
"stats": {
"posts_count": 42,
"followers_count": 156,
"following_count": 89
}
}
{
"error": {
"code": "validation_error",
"message": "Validation failed for one or more fields",
"details": [
{
"field": "email",
"message": "Email is already registered",
"code": "duplicate_email"
},
{
"field": "password",
"message": "Password must be at least 8 characters",
"code": "password_too_short"
}
],
"request_id": "req_xyz789abc",
"timestamp": "2024-01-25T10:30:00Z"
}
}
PHASE 11: RATE LIMITING DOCUMENTATION
explain rate limits clearly.
rate limits overview
To ensure fair usage, the API enforces rate limits.
| Plan | Requests per hour | Requests per day | Burst allowance |
|---|---|---|---|
| Free | 1,000 | 10,000 | 50 |
| Basic | 10,000 | 100,000 | 200 |
| Pro | 100,000 | 1,000,000 | 1,000 |
| Enterprise | Unlimited | Unlimited | 10,000 |
Every response includes rate limit information:
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 945
X-RateLimit-Reset: 1706169600
X-RateLimit-Reset-After: 342
Header descriptions:
X-RateLimit-Limit: Maximum requests per windowX-RateLimit-Remaining: Requests remaining in windowX-RateLimit-Reset: Unix timestamp when limit resetsX-RateLimit-Reset-After: Seconds until resethandling rate limits
import time
import requests
def api_request_with_retry(url, max_retries=5):
retries = 0
while retries < max_retries:
response = requests.get(url, headers=headers)
# Check rate limit status
remaining = int(response.headers.get('X-RateLimit-Remaining', 1))
if remaining < 10:
# Proactively slow down
time.sleep(1)
# Handle rate limit
if response.status_code == 429:
reset_after = int(response.headers.get('Retry-After', 60))
print(f"Rate limited. Waiting {reset_after} seconds...")
time.sleep(reset_after)
retries += 1
continue
return response
raise Exception("Max retries exceeded due to rate limiting")
async function fetchWithBackoff(url, maxRetries = 5) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
const response = await fetch(url, { headers });
if (response.status !== 429) {
return response;
}
// Calculate backoff with jitter
const baseDelay = Math.pow(2, attempt) * 1000; // 1s, 2s, 4s, 8s, 16s
const jitter = Math.random() * 1000;
const delay = baseDelay + jitter;
console.log(`Rate limited. Retrying after ${delay}ms...`);
await new Promise(r => setTimeout(r, delay));
}
throw new Error('Max retries exceeded');
}
PHASE 12: WEBHOOKS DOCUMENTATION
document webhook system thoroughly.
webhook overview
Webhooks allow your application to receive real-time notifications when events occur.
curl -X POST "https://api.example.com/v1/webhooks" \
-H "Authorization: Bearer ${API_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-app.com/webhooks",
"events": ["user.created", "user.updated", "user.deleted"],
"secret": "your_webhook_secret"
}'
Response:
{
"data": {
"id": "whk_abc123",
"url": "https://your-app.com/webhooks",
"events": ["user.created", "user.updated", "user.deleted"],
"status": "active",
"created_at": "2024-01-25T10:30:00Z"
}
}
webhook events
| Event | Description | Payload |
|---|---|---|
user.created | New user registered | User object |
user.updated | User profile changed | User object + changes |
user.deleted | User account deleted | User ID |
payment.succeeded | Payment completed | Payment object |
payment.failed | Payment failed | Payment + error |
subscription.started | Subscription created | Subscription object |
subscription.ended | Subscription cancelled | Subscription object |
{
"id": "evt_abc123xyz",
"type": "user.created",
"data": {
"id": "usr_def456",
"email": "newuser@example.com",
"name": "New User",
"created_at": "2024-01-25T10:30:00Z"
},
"timestamp": "2024-01-25T10:30:00Z",
"delivered_attempts": 1
}
webhook verification
Each webhook includes a signature header:
X-Webhook-Signature: t=1643104800,v1=abc123...
Verification logic:
import hmac
import hashlib
from flask import Flask, request, jsonify
app = Flask(__name__)
WEBHOOK_SECRET = "your_webhook_secret"
def verify_signature(payload, signature_header):
# Split signature
parts = signature_header.split(',')
signature_dict = {}
for part in parts:
key, value = part.split('=')
signature_dict[key] = value
# Create expected signature
expected_payload = f"{signature_dict['t']}.{payload}"
expected_signature = hmac.new(
WEBHOOK_SECRET.encode(),
expected_payload.encode(),
hashlib.sha256
).hexdigest()
# Compare signatures
received_signature = signature_dict.get('v1', '')
return hmac.compare_digest(expected_signature, received_signature)
@app.route('/webhooks', methods=['POST'])
def handle_webhook():
signature = request.headers.get('X-Webhook-Signature')
payload = request.get_data(as_text=True)
if not verify_signature(payload, signature):
return jsonify({"error": "Invalid signature"}), 401
# Process webhook
event = request.json
# ... handle event ...
return jsonify({"status": "ok"}), 200
webhook best practices
[ ] Always verify signatures [ ] Return 200 OK quickly, process asynchronously [ ] Handle duplicate events (idempotency) [ ] Implement retry logic for failures [ ] Log all webhook events [ ] Use HTTPS only [ ] Don't expose sensitive data in webhook URLs
example webhook handler:
import hashlib
from typing import Dict
from datetime import datetime
class WebhookHandler:
def __init__(self, secret: str):
self.secret = secret
self.processed_events = set() # In production, use Redis or database
def verify(self, payload: str, signature: str) -> bool:
"""Verify webhook signature."""
expected = hmac.new(
self.secret.encode(),
payload.encode(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)
def is_duplicate(self, event_id: str) -> bool:
"""Check if event was already processed."""
return event_id in self.processed_events
def mark_processed(self, event_id: str):
"""Mark event as processed."""
self.processed_events.add(event_id)
def handle(self, event: Dict) -> None:
"""Handle webhook event."""
event_id = event['id']
if self.is_duplicate(event_id):
print(f"Duplicate event {event_id}, skipping")
return
# Route to appropriate handler
handlers = {
'user.created': self.handle_user_created,
'user.updated': self.handle_user_updated,
'user.deleted': self.handle_user_deleted,
}
handler = handlers.get(event['type'])
if handler:
handler(event['data'])
self.mark_processed(event_id)
def handle_user_created(self, data):
"""Handle user created event."""
print(f"New user: {data['email']}")
# ... other handlers ...
PHASE 13: CHANGELOG DOCUMENTATION
maintain a history of API changes.
changelog format
All notable changes to the API are documented in this file.
The format is based on Keep a Changelog.
include parameter for related resourcesper_page parameter (use limit instead){ data, meta } structureAuthorization: Bearer headerSee migration guide for details.
migration guides
for major versions, provide detailed migration guides:
This guide helps you migrate from v1 to v2.
[1] Authentication
v1: X-Auth-Token header
v2: Authorization: Bearer header
[2] Response Structure
v1: { resource_name: [...] }
v2: { data: [...], meta: {...} }
[3] ID Format v1: Integer IDs v2: String IDs with prefixes
[4] Pagination
v1: per_page parameter
v2: limit parameter
Before (v1):
response = requests.get('https://api.example.com/v1/users', headers={
'X-Auth-Token': token
})
users = response.json()['users']
After (v2):
response = requests.get('https://api.example.com/v2/users', headers={
'Authorization': f'Bearer {token}'
})
users = response.json()['data']
Use the v2 sandbox environment:
https://sandbox-api.example.com/v2
Follow the testing checklist.
PHASE 14: API DOCUMENTATION CHECKLIST
content completeness
[ ] overview section [ ] what the API does [ ] who it's for [ ] key features
[ ] authentication section [ ] all auth methods documented [ ] how to get credentials [ ] token refresh process [ ] scopes and permissions
[ ] all endpoints documented [ ] HTTP method and path [ ] description [ ] all parameters documented [ ] all headers documented [ ] request body schema [ ] all response codes [ ] response schemas [ ] examples for each
[ ] common patterns documented [ ] pagination [ ] filtering [ ] sorting [ ] error handling [ ] rate limiting
[ ] code examples [ ] curl [ ] javascript [ ] python [ ] at least one backend language
[ ] guides [ ] quick start [ ] common workflows [ ] integration examples [ ] troubleshooting
quality checks
[ ] all examples are tested [ ] curl commands run successfully [ ] code examples execute [ ] example responses are accurate
[ ] OpenAPI spec is valid npm install -g @apidevtools/swagger-cli swagger-cli validate openapi.yaml
[ ] links work [ ] internal links [ ] external links [ ] code links
[ ] consistent terminology [ ] always use same terms for concepts [ ] avoid synonyms [ ] define jargon
[ ] clear and concise [ ] no unnecessary verbosity [ ] no ambiguity [ ] active voice
[ ] accessible [ ] readable without styling [ ] alt text for images [ ] color contrast
PHASE 15: API DOCUMENTATION RULES (STRICT MODE)
while this skill is active, these rules are MANDATORY:
[1] DOCUMENT EVERY ENDPOINT no endpoint should be undocumented if an endpoint exists publicly, it must be documented
[2] NEVER SKIP ERROR RESPONSES document all possible error codes for each endpoint include example error responses
[3] ALWAYS PROVIDE EXAMPLES at minimum: curl example better: curl + one language example best: curl + 2+ language examples
[4] VALIDATE OPENAPI SPECS before committing, validate: swagger-cli validate openapi.yaml
[5] TEST YOUR EXAMPLES every code example must be tested if you can't run it, don't include it
[6] DOCUMENT PARAMETERS FULLY name, type, required/optional, description, example, constraints no parameter should be partially documented
[7] INCLUDE AUTHENTICATION every endpoint must show auth requirements if no auth needed, explicitly state it
[8] MAINTAIN CHANGELOG every API change goes in the changelog no silent changes
[9] PROVIDE MIGRATION GUIDES for any breaking change, provide migration guide deprecation notice at least 6 months before removal
[10] THINK FROM USER PERSPECTIVE would a new developer understand this? would a non-technical stakeholder understand? if not, rewrite
PHASE 16: DOCUMENTATION MAINTENANCE
keeping docs current
[ ] review schedule weekly: check for new endpoints monthly: full documentation review quarterly: user feedback review
[ ] automated checks - openapi spec validation in ci - example testing in ci - link checking
[ ] feedback loop - add feedback widget to docs - track documentation issues - survey users annually
[ ] version control - tag docs with API version - keep old versions accessible - maintain docs in same repo as code
example CI check:
# .github/workflows/docs-check.yml
name: Documentation Checks
on: [pull_request]
jobs:
validate-openapi:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Validate OpenAPI spec
run: |
npm install -g @apidevtools/swagger-cli
swagger-cli validate openapi.yaml
test-examples:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Test curl examples
run: |
# Extract and test curl commands from docs
./scripts/test-examples.sh
FINAL REMINDERS
documentation is a product interface
the API docs are often the first thing developers see. poor documentation = poor developer experience. excellent documentation = happy developers, fewer support tickets.
docs are never done
APIs evolve. documentation must evolve with them. make docs part of your development process.
the rule of clarity
if you have to read it twice, rewrite it. if you have to explain it, it's not clear. simple, direct language wins.
when in doubt
add an example. examples bridge the gap between abstract and concrete. a good example is worth a thousand descriptions.
now go document every endpoint.