api-documentation
Comprehensive guidance for documenting REST, GraphQL, gRPC, and WebSocket APIs. Use when creating API endpoints, updating existing APIs, preparing public API releases, or documenting external integrations.
When & Why to Use This Skill
This Claude skill provides comprehensive, expert-level guidance for documenting REST, GraphQL, gRPC, and WebSocket APIs. It helps developers and technical writers create high-quality, self-service documentation that improves developer experience (DX). By offering structured templates for schemas, authentication flows, and error handling, it ensures that API references are clear, consistent, and easy to integrate, which is essential for the success of any technical product.
Use Cases
- Generating standardized RESTful API documentation including OpenAPI/Swagger specifications and HTTP status code mappings.
- Creating detailed GraphQL references that document schemas, queries, mutations, and complex types with runnable code examples.
- Drafting technical guides for gRPC services, covering Protobuf definitions and multi-language client implementations (Go, Python, etc.).
- Designing WebSocket API documentation that clearly outlines connection lifecycles, message formats, and real-time event handling.
- Writing comprehensive API overviews that include authentication (JWT/OAuth), rate limiting policies, and versioning migration guides.
| name | api-documentation |
|---|---|
| description | Comprehensive guidance for documenting REST, GraphQL, gRPC, and WebSocket APIs. Use when creating API endpoints, updating existing APIs, preparing public API releases, or documenting external integrations. |
API Documentation Skill
Core Principle
APIs are products. Documentation is the user interface.
Your API might be technically perfect, but if developers can't figure out how to use it, it's worthless. Good API documentation:
- Enables self-service (developers don't need to ask questions)
- Shows real examples (copy-paste ready code)
- Documents the "why" not just the "what"
- Stays synchronized with implementation
When to Use This Skill
Use this skill when:
- Creating new API endpoints or services
- Updating existing API interfaces
- Preparing APIs for public release
- Enabling external teams to integrate with your API
- Versioning or deprecating API endpoints
- Writing SDK or client library documentation
- Documenting authentication and authorization flows
- Creating interactive API explorers
API Documentation Types
1. REST API Documentation
Purpose: Document HTTP-based APIs following REST principles
Essential components:
- Base URL and versioning strategy
- Authentication mechanisms
- Resource endpoints (paths)
- HTTP methods (GET, POST, PUT, PATCH, DELETE)
- Request parameters (path, query, body)
- Response formats and status codes
- Error handling and status codes
- Rate limiting and pagination
- Example requests and responses
Example:
## Get User by ID
Retrieve a single user's details by their unique identifier.
### Endpoint
GET /api/v1/users/{userId}
### Path Parameters
| Parameter | Type | Required | Description |
|-----------|--------|----------|-----------------------|
| userId | string | Yes | Unique user identifier|
### Query Parameters
| Parameter | Type | Required | Description |
|-----------|---------|----------|--------------------------------|
| include | string | No | Comma-separated related data |
| | | | Options: `profile`, `settings` |
### Headers
Authorization: Bearer <token>
Accept: application/json
### Response 200 (Success)
```json
{
"id": "usr_123abc",
"email": "john@example.com",
"name": "John Doe",
"created_at": "2024-01-15T10:30:00Z",
"profile": {
"bio": "Software developer",
"avatar_url": "https://example.com/avatar.jpg"
}
}
Response 404 (Not Found)
{
"error": {
"code": "USER_NOT_FOUND",
"message": "No user found with ID: usr_123abc"
}
}
Response 401 (Unauthorized)
{
"error": {
"code": "UNAUTHORIZED",
"message": "Valid authentication token required"
}
}
Example Request
curl -X GET "https://api.example.com/api/v1/users/usr_123abc?include=profile" \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Accept: application/json"
import requests
headers = {
"Authorization": "Bearer YOUR_TOKEN",
"Accept": "application/json"
}
response = requests.get(
"https://api.example.com/api/v1/users/usr_123abc",
params={"include": "profile"},
headers=headers
)
user = response.json()
print(f"User: {user['name']}")
const response = await fetch(
'https://api.example.com/api/v1/users/usr_123abc?include=profile',
{
headers: {
'Authorization': 'Bearer YOUR_TOKEN',
'Accept': 'application/json'
}
}
);
const user = await response.json();
console.log(`User: ${user.name}`);
---
### 2. GraphQL API Documentation
**Purpose:** Document GraphQL schemas, queries, mutations, and subscriptions
**Essential components:**
- Schema definitions (types, interfaces, unions)
- Query documentation with arguments
- Mutation documentation with input types
- Subscription patterns
- Authentication and authorization
- Error handling patterns
- Pagination strategies (cursor-based, offset)
- Example queries with variables
**Example:**
```markdown
## User Queries
### getUser
Fetch a single user by ID with optional related data.
#### Schema Definition
```graphql
type User {
id: ID!
email: String!
name: String!
createdAt: DateTime!
profile: Profile
posts(first: Int, after: String): PostConnection
}
type Profile {
bio: String
avatarUrl: String
location: String
}
type Query {
getUser(id: ID!, includeProfile: Boolean): User
}
Arguments
| Argument | Type | Required | Description |
|---|---|---|---|
| id | ID | Yes | User's unique identifier |
| includeProfile | Boolean | No | Include profile data |
Example Query
query GetUserWithProfile($userId: ID!) {
getUser(id: $userId, includeProfile: true) {
id
email
name
createdAt
profile {
bio
avatarUrl
location
}
}
}
Variables
{
"userId": "usr_123abc"
}
Response
{
"data": {
"getUser": {
"id": "usr_123abc",
"email": "john@example.com",
"name": "John Doe",
"createdAt": "2024-01-15T10:30:00Z",
"profile": {
"bio": "Software developer",
"avatarUrl": "https://example.com/avatar.jpg",
"location": "San Francisco, CA"
}
}
}
}
Error Response
{
"errors": [
{
"message": "User not found",
"locations": [{"line": 2, "column": 3}],
"path": ["getUser"],
"extensions": {
"code": "USER_NOT_FOUND",
"userId": "usr_123abc"
}
}
],
"data": {
"getUser": null
}
}
Client Code Examples
// Using Apollo Client
import { gql, useQuery } from '@apollo/client';
const GET_USER = gql`
query GetUserWithProfile($userId: ID!) {
getUser(id: $userId, includeProfile: true) {
id
name
email
profile {
bio
avatarUrl
}
}
}
`;
function UserProfile({ userId }) {
const { loading, error, data } = useQuery(GET_USER, {
variables: { userId }
});
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<div>
<h1>{data.getUser.name}</h1>
<p>{data.getUser.profile.bio}</p>
</div>
);
}
# Using gql library
from gql import gql, Client
from gql.transport.requests import RequestsHTTPTransport
transport = RequestsHTTPTransport(
url='https://api.example.com/graphql',
headers={'Authorization': 'Bearer YOUR_TOKEN'}
)
client = Client(transport=transport, fetch_schema_from_transport=True)
query = gql("""
query GetUserWithProfile($userId: ID!) {
getUser(id: $userId, includeProfile: true) {
id
name
email
profile {
bio
avatarUrl
}
}
}
""")
result = client.execute(query, variable_values={"userId": "usr_123abc"})
print(f"User: {result['getUser']['name']}")
### GraphQL Mutations
```markdown
## createUser
Create a new user account.
#### Schema Definition
```graphql
input CreateUserInput {
email: String!
name: String!
password: String!
profile: ProfileInput
}
input ProfileInput {
bio: String
location: String
}
type CreateUserPayload {
user: User
token: String!
errors: [UserError!]
}
type UserError {
field: String!
message: String!
}
type Mutation {
createUser(input: CreateUserInput!): CreateUserPayload!
}
Example Mutation
mutation CreateUser($input: CreateUserInput!) {
createUser(input: $input) {
user {
id
email
name
}
token
errors {
field
message
}
}
}
Variables
{
"input": {
"email": "jane@example.com",
"name": "Jane Smith",
"password": "SecurePass123!",
"profile": {
"bio": "Product designer",
"location": "New York, NY"
}
}
}
Success Response
{
"data": {
"createUser": {
"user": {
"id": "usr_456def",
"email": "jane@example.com",
"name": "Jane Smith"
},
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"errors": []
}
}
}
Validation Error Response
{
"data": {
"createUser": {
"user": null,
"token": null,
"errors": [
{
"field": "email",
"message": "Email already registered"
},
{
"field": "password",
"message": "Password must be at least 8 characters"
}
]
}
}
}
---
### 3. gRPC API Documentation
**Purpose:** Document Protocol Buffer based APIs
**Essential components:**
- Service definitions
- Message types (requests/responses)
- Field types and constraints
- Error codes and handling
- Streaming patterns (unary, server stream, client stream, bidirectional)
- Example implementations in multiple languages
**Example:**
```markdown
## User Service
Service for managing user accounts and profiles.
### Proto Definition
```protobuf
syntax = "proto3";
package user.v1;
service UserService {
// Get a user by ID
rpc GetUser(GetUserRequest) returns (GetUserResponse);
// Create a new user
rpc CreateUser(CreateUserRequest) returns (CreateUserResponse);
// Stream user updates (server streaming)
rpc WatchUser(WatchUserRequest) returns (stream UserUpdate);
}
message GetUserRequest {
string user_id = 1; // Required: User identifier
bool include_profile = 2; // Optional: Include profile data
}
message GetUserResponse {
User user = 1;
}
message User {
string id = 1;
string email = 2;
string name = 3;
int64 created_at = 4; // Unix timestamp
Profile profile = 5; // Optional
}
message Profile {
string bio = 1;
string avatar_url = 2;
string location = 3;
}
message UserUpdate {
User user = 1;
UpdateType type = 2;
enum UpdateType {
UPDATED = 0;
DELETED = 1;
}
}
GetUser RPC
Retrieve a single user by their unique identifier.
Request
req := &userpb.GetUserRequest{
UserId: "usr_123abc",
IncludeProfile: true,
}
Response (Success)
resp := &userpb.GetUserResponse{
User: &userpb.User{
Id: "usr_123abc",
Email: "john@example.com",
Name: "John Doe",
CreatedAt: 1705315800, // Unix timestamp
Profile: &userpb.Profile{
Bio: "Software developer",
AvatarUrl: "https://example.com/avatar.jpg",
Location: "San Francisco, CA",
},
},
}
Error Handling
gRPC uses standard status codes:
| Code | Description | When to Use |
|---|---|---|
| OK | Success | Request completed |
| NOT_FOUND | Resource not found | User ID doesn't exist |
| INVALID_ARG | Invalid parameters | Malformed user ID |
| UNAUTHENTICATED | Missing/invalid credentials | No auth token provided |
| PERMISSION_DENIED | Insufficient permissions | Can't access user |
| INTERNAL | Server error | Unexpected failures |
Example Implementations
Go Client:
package main
import (
"context"
"log"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
userpb "example.com/proto/user/v1"
)
func main() {
// Setup TLS credentials
creds, err := credentials.NewClientTLSFromFile("cert.pem", "")
if err != nil {
log.Fatal(err)
}
// Connect to server
conn, err := grpc.Dial(
"api.example.com:443",
grpc.WithTransportCredentials(creds),
)
if err != nil {
log.Fatal(err)
}
defer conn.Close()
// Create client
client := userpb.NewUserServiceClient(conn)
// Make request
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
resp, err := client.GetUser(ctx, &userpb.GetUserRequest{
UserId: "usr_123abc",
IncludeProfile: true,
})
if err != nil {
log.Fatalf("GetUser failed: %v", err)
}
log.Printf("User: %s (%s)", resp.User.Name, resp.User.Email)
}
Python Client:
import grpc
from user.v1 import user_pb2, user_pb2_grpc
# Setup channel with credentials
credentials = grpc.ssl_channel_credentials(
open('cert.pem', 'rb').read()
)
channel = grpc.secure_channel('api.example.com:443', credentials)
# Create client stub
client = user_pb2_grpc.UserServiceStub(channel)
# Make request
try:
response = client.GetUser(
user_pb2.GetUserRequest(
user_id='usr_123abc',
include_profile=True
),
timeout=5.0
)
print(f"User: {response.user.name} ({response.user.email})")
except grpc.RpcError as e:
print(f"Error: {e.code()} - {e.details()}")
---
### 4. WebSocket API Documentation
**Purpose:** Document real-time, bidirectional communication APIs
**Essential components:**
- Connection endpoint and protocol
- Authentication handshake
- Message formats (JSON, binary)
- Event types (client-to-server, server-to-client)
- Connection lifecycle (connect, disconnect, reconnect)
- Error handling and recovery
- Rate limiting and backpressure
**Example:**
```markdown
## Real-Time Notifications API
WebSocket API for receiving real-time user notifications.
### Connection
**Endpoint:** `wss://api.example.com/v1/notifications`
**Protocol:** WebSocket over TLS
### Authentication
Include JWT token in connection query parameter or initial message.
#### Option 1: Query Parameter
```javascript
const ws = new WebSocket(
'wss://api.example.com/v1/notifications?token=YOUR_JWT_TOKEN'
);
Option 2: Initial Message
const ws = new WebSocket('wss://api.example.com/v1/notifications');
ws.onopen = () => {
ws.send(JSON.stringify({
type: 'auth',
token: 'YOUR_JWT_TOKEN'
}));
};
Message Format
All messages are JSON with the following structure:
{
"type": "string",
"id": "string",
"timestamp": "ISO8601",
"data": {}
}
Client-to-Server Messages
Subscribe to Notifications
{
"type": "subscribe",
"id": "msg_001",
"data": {
"channels": ["user_updates", "mentions"]
}
}
Unsubscribe
{
"type": "unsubscribe",
"id": "msg_002",
"data": {
"channels": ["mentions"]
}
}
Ping (Keepalive)
{
"type": "ping",
"id": "msg_003"
}
Server-to-Client Messages
Authentication Success
{
"type": "auth_success",
"id": "srv_001",
"timestamp": "2024-01-15T10:30:00Z",
"data": {
"user_id": "usr_123abc",
"session_id": "sess_xyz789"
}
}
Notification Event
{
"type": "notification",
"id": "notif_456",
"timestamp": "2024-01-15T10:30:00Z",
"data": {
"notification_id": "n_789",
"category": "mention",
"title": "You were mentioned in a comment",
"body": "John Doe mentioned you in 'Project Alpha'",
"action_url": "/projects/alpha#comment-123",
"read": false
}
}
Pong (Keepalive Response)
{
"type": "pong",
"id": "srv_002",
"timestamp": "2024-01-15T10:30:01Z"
}
Error
{
"type": "error",
"id": "srv_003",
"timestamp": "2024-01-15T10:30:02Z",
"data": {
"code": "INVALID_CHANNEL",
"message": "Channel 'unknown_channel' does not exist",
"retry_after": null
}
}
Connection Lifecycle
1. Connect
const ws = new WebSocket('wss://api.example.com/v1/notifications');
ws.onopen = () => {
console.log('Connected to notification service');
// Authenticate
ws.send(JSON.stringify({
type: 'auth',
token: localStorage.getItem('jwt_token')
}));
};
2. Handle Messages
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
switch (message.type) {
case 'auth_success':
console.log('Authenticated:', message.data.user_id);
// Subscribe to channels
ws.send(JSON.stringify({
type: 'subscribe',
data: { channels: ['user_updates', 'mentions'] }
}));
break;
case 'notification':
console.log('New notification:', message.data.title);
displayNotification(message.data);
break;
case 'error':
console.error('Error:', message.data.message);
break;
}
};
3. Handle Errors
ws.onerror = (error) => {
console.error('WebSocket error:', error);
};
4. Handle Disconnection
ws.onclose = (event) => {
console.log('Disconnected:', event.code, event.reason);
// Implement reconnection logic
if (event.code !== 1000) { // 1000 = normal closure
setTimeout(() => {
console.log('Reconnecting...');
connectWebSocket();
}, 5000);
}
};
Error Codes
| Code | Name | Description |
|---|---|---|
| 1000 | Normal Closure | Connection closed normally |
| 1001 | Going Away | Server shutting down |
| 1008 | Policy Violation | Authentication failed |
| 1011 | Internal Error | Unexpected server error |
| 4000 | Invalid Token | JWT token invalid or expired |
| 4001 | Rate Limited | Too many messages sent |
| 4002 | Invalid Message | Malformed JSON or unknown type |
Rate Limiting
- Maximum 100 messages per minute per connection
- Exceeded limits result in error message and potential disconnection
- Reconnection allowed after 1 minute cooldown
Keepalive
- Server sends ping every 30 seconds if no activity
- Client should respond with pong within 10 seconds
- Connection closed if 3 consecutive pings timeout
---
## Essential API Documentation Components
### 1. Overview and Introduction
Start with the big picture:
```markdown
# MyAPI Documentation
## Overview
MyAPI is a RESTful service for managing user accounts and content. It provides
endpoints for authentication, user management, content creation, and real-time
notifications.
## Base URL
Production: `https://api.example.com/v1`
Staging: `https://staging-api.example.com/v1`
## API Version
Current version: **v1** (Released: 2024-01-15)
## Supported Formats
- Request: JSON (`application/json`)
- Response: JSON (`application/json`)
- Encoding: UTF-8
## Rate Limits
- Authenticated: 5000 requests/hour
- Unauthenticated: 100 requests/hour
## Support
- Email: api-support@example.com
- Discord: https://discord.gg/example
- Status: https://status.example.com
2. Authentication Documentation
Document every authentication method thoroughly:
## Authentication
MyAPI uses Bearer token authentication with JWT tokens.
### Obtaining a Token
**Endpoint:** `POST /auth/login`
```json
{
"email": "user@example.com",
"password": "your_password"
}
Response:
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "fdb8fdbecf1d03ce5e6125c067733c0d51de209c",
"token_type": "Bearer",
"expires_in": 3600
}
Using the Token
Include the access token in the Authorization header:
curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
https://api.example.com/v1/users/me
Token Refresh
Endpoint: POST /auth/refresh
{
"refresh_token": "fdb8fdbecf1d03ce5e6125c067733c0d51de209c"
}
Security Best Practices
- Store tokens securely (never in localStorage for sensitive apps)
- Use HTTPS for all API calls
- Refresh tokens before expiration
- Revoke tokens on logout
---
### 3. Error Handling Documentation
Consistent error format is critical:
```markdown
## Error Responses
All errors follow this structure:
```json
{
"error": {
"code": "ERROR_CODE",
"message": "Human-readable error message",
"details": {
"field": "additional context"
},
"request_id": "req_abc123",
"documentation_url": "https://docs.example.com/errors/ERROR_CODE"
}
}
HTTP Status Codes
| Code | Meaning | When Used |
|---|---|---|
| 200 | OK | Successful GET, PUT, PATCH |
| 201 | Created | Successful POST (resource created) |
| 204 | No Content | Successful DELETE |
| 400 | Bad Request | Invalid parameters or body |
| 401 | Unauthorized | Missing or invalid authentication |
| 403 | Forbidden | Authenticated but not authorized |
| 404 | Not Found | Resource doesn't exist |
| 409 | Conflict | Resource conflict (duplicate, etc.) |
| 422 | Unprocessable Entity | Validation failed |
| 429 | Too Many Requests | Rate limit exceeded |
| 500 | Internal Server Error | Server error |
| 503 | Service Unavailable | Temporary downtime |
Common Error Codes
VALIDATION_ERROR (400)
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"details": {
"email": "Invalid email format",
"age": "Must be at least 18"
}
}
}
RESOURCE_NOT_FOUND (404)
{
"error": {
"code": "RESOURCE_NOT_FOUND",
"message": "User not found",
"details": {
"resource_type": "user",
"resource_id": "usr_123abc"
}
}
}
RATE_LIMIT_EXCEEDED (429)
{
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Too many requests",
"details": {
"limit": 5000,
"window": "1 hour",
"retry_after": 3600
}
}
}
Headers:
Retry-After: 3600
X-RateLimit-Limit: 5000
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1705332600
---
### 4. Pagination Documentation
```markdown
## Pagination
List endpoints return paginated results.
### Cursor-Based Pagination (Recommended)
**Request:**
```bash
GET /api/v1/users?limit=20&cursor=eyJpZCI6MTIzfQ
Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
| limit | int | 20 | Items per page (max: 100) |
| cursor | string | null | Pagination cursor from previous |
Response:
{
"data": [
{ "id": "usr_001", "name": "User 1" },
{ "id": "usr_002", "name": "User 2" }
],
"pagination": {
"next_cursor": "eyJpZCI6MTI1fQ",
"has_more": true,
"total_count": null
}
}
Usage:
def fetch_all_users():
users = []
cursor = None
while True:
params = {'limit': 100}
if cursor:
params['cursor'] = cursor
response = requests.get('https://api.example.com/v1/users', params=params)
data = response.json()
users.extend(data['data'])
if not data['pagination']['has_more']:
break
cursor = data['pagination']['next_cursor']
return users
---
### 5. Versioning Documentation
```markdown
## API Versioning
MyAPI uses URL-based versioning.
### Current Version: v1
All endpoints are prefixed with `/v1`:
https://api.example.com/v1/users
### Version Lifecycle
| Version | Status | EOL Date | Notes |
|---------|------------|------------|--------------------------|
| v1 | Current | - | Latest stable version |
| v0 | Deprecated | 2024-06-30 | Migrate to v1 |
### Breaking Changes
We consider these changes as breaking:
- Removing or renaming fields
- Changing field types
- Removing endpoints
- Changing authentication
- Changing status code meanings
### Non-Breaking Changes
These changes may occur in current version:
- Adding new endpoints
- Adding new optional fields
- Adding new query parameters
- Deprecation warnings (6 months notice)
### Migration Guide: v0 → v1
**Authentication Changes:**
v0:
```bash
curl -H "X-API-Key: YOUR_KEY" https://api.example.com/users
v1:
curl -H "Authorization: Bearer YOUR_TOKEN" https://api.example.com/v1/users
Response Format Changes:
v0:
{
"users": [...],
"page": 1,
"total": 100
}
v1:
{
"data": [...],
"pagination": {
"cursor": "...",
"has_more": true
}
}
---
### 6. Rate Limiting Documentation
```markdown
## Rate Limiting
API requests are rate-limited to ensure fair usage.
### Limits
| Authentication | Limit | Window |
|----------------|---------------------|---------|
| Authenticated | 5000 requests | 1 hour |
| Unauthenticated| 100 requests | 1 hour |
| Burst | 100 requests | 1 minute|
### Rate Limit Headers
Every response includes rate limit information:
X-RateLimit-Limit: 5000 X-RateLimit-Remaining: 4850 X-RateLimit-Reset: 1705332600
| Header | Description |
|-------------------------|--------------------------------------|
| X-RateLimit-Limit | Maximum requests in window |
| X-RateLimit-Remaining | Requests remaining in current window |
| X-RateLimit-Reset | Unix timestamp when limit resets |
### Handling Rate Limits
```python
import time
import requests
def api_call_with_retry(url, headers):
while True:
response = requests.get(url, headers=headers)
if response.status_code == 429:
# Rate limited
reset_time = int(response.headers.get('X-RateLimit-Reset', 0))
wait_seconds = reset_time - int(time.time())
print(f"Rate limited. Waiting {wait_seconds} seconds...")
time.sleep(max(wait_seconds, 1))
continue
return response
Rate Limit Strategies
Exponential Backoff:
async function apiCallWithBackoff(url, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
const response = await fetch(url);
if (response.status === 429) {
const resetTime = response.headers.get('X-RateLimit-Reset');
const waitMs = (resetTime * 1000) - Date.now();
await new Promise(resolve => setTimeout(resolve, waitMs));
continue;
}
return response;
}
throw new Error('Max retries exceeded');
}
---
## Documentation Formats and Tools
### 1. OpenAPI/Swagger (REST APIs)
**Purpose:** Machine-readable API specification
**Complete OpenAPI 3.x Example:**
```yaml
openapi: 3.0.3
info:
title: MyAPI
description: User and content management API
version: 1.0.0
contact:
name: API Support
email: api-support@example.com
url: https://example.com/support
license:
name: MIT
url: https://opensource.org/licenses/MIT
servers:
- url: https://api.example.com/v1
description: Production server
- url: https://staging-api.example.com/v1
description: Staging server
security:
- bearerAuth: []
paths:
/users/{userId}:
get:
summary: Get user by ID
description: Retrieve detailed information about a specific user
operationId: getUser
tags:
- Users
parameters:
- name: userId
in: path
required: true
description: Unique user identifier
schema:
type: string
pattern: '^usr_[a-zA-Z0-9]+$'
example: usr_123abc
- name: include
in: query
required: false
description: Comma-separated list of related data to include
schema:
type: string
enum: [profile, settings, posts]
example: profile
responses:
'200':
description: User found successfully
content:
application/json:
schema:
$ref: '#/components/schemas/User'
examples:
withProfile:
summary: User with profile
value:
id: usr_123abc
email: john@example.com
name: John Doe
created_at: '2024-01-15T10:30:00Z'
profile:
bio: Software developer
avatar_url: https://example.com/avatar.jpg
'401':
$ref: '#/components/responses/UnauthorizedError'
'404':
$ref: '#/components/responses/NotFoundError'
'429':
$ref: '#/components/responses/RateLimitError'
/users:
post:
summary: Create new user
description: Register a new user account
operationId: createUser
tags:
- Users
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateUserRequest'
examples:
basic:
summary: Basic user registration
value:
email: jane@example.com
name: Jane Smith
password: SecurePass123!
responses:
'201':
description: User created successfully
headers:
Location:
description: URL of the created user
schema:
type: string
format: uri
example: /v1/users/usr_456def
content:
application/json:
schema:
$ref: '#/components/schemas/User'
'400':
$ref: '#/components/responses/ValidationError'
'409':
description: User already exists
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
example:
error:
code: USER_ALREADY_EXISTS
message: Email already registered
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
description: JWT token obtained from /auth/login
schemas:
User:
type: object
required:
- id
- email
- name
- created_at
properties:
id:
type: string
pattern: '^usr_[a-zA-Z0-9]+$'
example: usr_123abc
email:
type: string
format: email
example: john@example.com
name:
type: string
minLength: 1
maxLength: 100
example: John Doe
created_at:
type: string
format: date-time
example: '2024-01-15T10:30:00Z'
profile:
$ref: '#/components/schemas/Profile'
Profile:
type: object
properties:
bio:
type: string
maxLength: 500
example: Software developer
avatar_url:
type: string
format: uri
example: https://example.com/avatar.jpg
location:
type: string
example: San Francisco, CA
CreateUserRequest:
type: object
required:
- email
- name
- password
properties:
email:
type: string
format: email
name:
type: string
minLength: 1
maxLength: 100
password:
type: string
minLength: 8
maxLength: 128
format: password
description: Must contain uppercase, lowercase, number, and symbol
Error:
type: object
required:
- error
properties:
error:
type: object
required:
- code
- message
properties:
code:
type: string
example: RESOURCE_NOT_FOUND
message:
type: string
example: Resource not found
details:
type: object
additionalProperties: true
request_id:
type: string
example: req_abc123
responses:
UnauthorizedError:
description: Authentication required
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
example:
error:
code: UNAUTHORIZED
message: Valid authentication token required
NotFoundError:
description: Resource not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
example:
error:
code: RESOURCE_NOT_FOUND
message: Resource not found
ValidationError:
description: Request validation failed
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
example:
error:
code: VALIDATION_ERROR
message: Request validation failed
details:
email: Invalid email format
password: Must be at least 8 characters
RateLimitError:
description: Rate limit exceeded
headers:
X-RateLimit-Limit:
schema:
type: integer
description: Maximum requests per window
X-RateLimit-Remaining:
schema:
type: integer
description: Requests remaining
X-RateLimit-Reset:
schema:
type: integer
description: Unix timestamp when limit resets
Retry-After:
schema:
type: integer
description: Seconds to wait before retry
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
Generating OpenAPI Spec from Code:
# Using FastAPI (Python)
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI(
title="MyAPI",
description="User and content management API",
version="1.0.0"
)
class User(BaseModel):
id: str
email: str
name: str
@app.get("/users/{user_id}", response_model=User)
async def get_user(user_id: str):
"""Get user by ID"""
# Auto-generates OpenAPI spec
pass
# Access at: http://localhost:8000/docs
2. GraphQL Documentation Tools
GraphQL Playground / GraphiQL:
// Setup GraphQL Playground
const { ApolloServer } = require('apollo-server');
const server = new ApolloServer({
typeDefs,
resolvers,
playground: {
settings: {
'editor.theme': 'light',
'editor.reuseHeaders': true,
},
},
introspection: true, // Enable schema introspection
});
// Access at: http://localhost:4000/graphql
Schema Documentation Comments:
"""
Represents a user account in the system.
"""
type User {
"""
Unique identifier for the user.
Format: usr_[a-zA-Z0-9]+
"""
id: ID!
"""
User's email address.
Must be unique across all users.
"""
email: String!
"""
Display name for the user.
Max length: 100 characters
"""
name: String!
}
"""
Query operations for users.
"""
type Query {
"""
Retrieve a single user by their unique identifier.
Returns null if user not found.
Requires authentication.
"""
getUser(
"""User's unique identifier"""
id: ID!
"""Whether to include the user's profile data"""
includeProfile: Boolean = false
): User
}
3. API Documentation Tools
Swagger UI:
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist/swagger-ui.css" />
</head>
<body>
<div id="swagger-ui"></div>
<script src="https://unpkg.com/swagger-ui-dist/swagger-ui-bundle.js"></script>
<script>
SwaggerUIBundle({
url: "/openapi.yaml",
dom_id: '#swagger-ui',
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIBundle.SwaggerUIStandalonePreset
],
layout: "BaseLayout"
});
</script>
</body>
</html>
Redoc:
<!DOCTYPE html>
<html>
<head>
<title>API Documentation</title>
</head>
<body>
<redoc spec-url="/openapi.yaml"></redoc>
<script src="https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js"></script>
</body>
</html>
Stoplight:
- Visual API designer
- Automatic mock servers
- API style guides
- Collaborative editing
- Hosted documentation
Postman Collections:
{
"info": {
"name": "MyAPI",
"description": "User and content management API",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"item": [
{
"name": "Users",
"item": [
{
"name": "Get User",
"request": {
"method": "GET",
"header": [
{
"key": "Authorization",
"value": "Bearer {{access_token}}"
}
],
"url": {
"raw": "{{base_url}}/users/:userId?include=profile",
"host": ["{{base_url}}"],
"path": ["users", ":userId"],
"query": [
{
"key": "include",
"value": "profile"
}
],
"variable": [
{
"key": "userId",
"value": "usr_123abc"
}
]
}
}
}
]
}
]
}
API Documentation Best Practices
1. Provide Runnable Examples
❌ Bad:
Call the API with the user ID.
✅ Good:
**Example Request:**
```bash
curl -X GET "https://api.example.com/v1/users/usr_123abc" \
-H "Authorization: Bearer YOUR_TOKEN"
Expected Response:
{
"id": "usr_123abc",
"name": "John Doe",
"email": "john@example.com"
}
---
### 2. Show Error Scenarios
❌ **Bad:**
```markdown
Returns 404 if not found.
✅ Good:
**Error Response (404 Not Found):**
```json
{
"error": {
"code": "USER_NOT_FOUND",
"message": "No user found with ID: usr_123abc",
"details": {
"resource_type": "user",
"resource_id": "usr_123abc"
}
}
}
Handling the Error:
response = requests.get(f"https://api.example.com/v1/users/{user_id}")
if response.status_code == 404:
error = response.json()['error']
print(f"User not found: {error['message']}")
# Fallback logic here
---
### 3. Include Multiple Language Examples
Provide examples in popular languages:
```markdown
**Python:**
```python
import requests
response = requests.get(
"https://api.example.com/v1/users/usr_123abc",
headers={"Authorization": f"Bearer {token}"}
)
user = response.json()
JavaScript:
const response = await fetch(
'https://api.example.com/v1/users/usr_123abc',
{
headers: {
'Authorization': `Bearer ${token}`
}
}
);
const user = await response.json();
Go:
req, _ := http.NewRequest("GET",
"https://api.example.com/v1/users/usr_123abc", nil)
req.Header.Set("Authorization", "Bearer "+token)
resp, err := http.DefaultClient.Do(req)
defer resp.Body.Close()
var user User
json.NewDecoder(resp.Body).Decode(&user)
cURL:
curl -X GET "https://api.example.com/v1/users/usr_123abc" \
-H "Authorization: Bearer YOUR_TOKEN"
---
### 4. Document Rate Limits Clearly
```markdown
## Rate Limiting
Your requests are limited to **5000 per hour**.
Check remaining quota:
```bash
curl -I https://api.example.com/v1/users
Response headers:
X-RateLimit-Limit: 5000
X-RateLimit-Remaining: 4850
X-RateLimit-Reset: 1705332600
If rate limited:
{
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Too many requests"
}
}
Wait until X-RateLimit-Reset timestamp before retrying.
---
### 5. Keep Documentation Synchronized with Code
**Strategies:**
- **Generate docs from code** (FastAPI, Swagger annotations)
- **Test examples in CI** (ensure they work)
- **Version docs with API** (v1 docs match v1 code)
- **Review docs in PRs** (docs changes required for API changes)
- **Automate OpenAPI generation** (from code annotations)
**Example CI Check:**
```yaml
# .github/workflows/docs.yml
name: Validate Documentation
on: [pull_request]
jobs:
test-examples:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Test API Examples
run: |
# Extract code examples from docs
python scripts/extract_examples.py docs/
# Run examples against staging API
python scripts/test_examples.py --api-url https://staging-api.example.com
6. Provide SDKs and Client Libraries
Document language-specific clients:
## Official SDKs
### Python
```bash
pip install myapi-client
from myapi import Client
client = Client(api_key="YOUR_API_KEY")
user = client.users.get("usr_123abc")
print(user.name)
JavaScript
npm install @myapi/client
import { MyAPIClient } from '@myapi/client';
const client = new MyAPIClient({ apiKey: 'YOUR_API_KEY' });
const user = await client.users.get('usr_123abc');
console.log(user.name);
---
## Quick Reference Checklist
Before publishing API documentation:
- [ ] **Overview** — Purpose, base URL, version documented
- [ ] **Authentication** — All auth methods explained with examples
- [ ] **Endpoints** — Every endpoint documented
- [ ] **Parameters** — All parameters described with types and constraints
- [ ] **Examples** — Request/response examples for each endpoint
- [ ] **Multiple Languages** — Examples in Python, JavaScript, cURL minimum
- [ ] **Error Handling** — All error codes and responses documented
- [ ] **Rate Limiting** — Limits, headers, and handling documented
- [ ] **Pagination** — Strategy documented with examples
- [ ] **Versioning** — Current version and migration guides
- [ ] **Status Codes** — All HTTP codes explained
- [ ] **Testing** — Examples are tested and work
- [ ] **Interactive Docs** — Swagger/GraphQL Playground available
- [ ] **Search** — Documentation is searchable
- [ ] **Changelog** — API changes documented
---
## Integration with Other Skills
### With Technical Writing
- Apply technical writing principles to API docs
- Use active voice, specific language
- Show don't tell (examples over descriptions)
- Progressive disclosure (essentials first)
### With Code Review
- Review API documentation in pull requests
- Ensure breaking changes are documented
- Verify examples are accurate
- Check that OpenAPI spec is updated
### With Documentation Writing
- Follow documentation standards
- Maintain consistent structure
- Keep docs in version control
- Update docs with code changes
### With Testing Strategy
- Test API examples in CI/CD
- Validate OpenAPI specs automatically
- Generate API tests from documentation
- Contract testing (Pact, Spring Cloud Contract)
---
**Remember:** Your API is only as good as its documentation. Developers will judge your API by how easy it is to get started. Invest in comprehensive, accurate, example-rich documentation. Test your examples. Keep docs synchronized with code. Make documentation a first-class citizen in your development process.