API Documentation Writer Skill
Overview
This skill helps you create clear, comprehensive API documentation that developers love. Covers OpenAPI/Swagger specifications, endpoint references, authentication guides, code examples in multiple languages, and developer experience best practices.
Documentation Philosophy
The Three C's
- Clear: Unambiguous, jargon-free explanations
- Complete: All parameters, responses, and edge cases documented
- Current: Always in sync with the actual API behavior
What to Document
- DO: Document every public endpoint
- DO: Include request/response examples for all scenarios
- DO: Document error codes with remediation steps
- DO: Provide code examples in popular languages
- DON'T: Document internal/private endpoints
- DON'T: Assume readers know your domain
- DON'T: Let documentation drift from implementation
OpenAPI Specification
Basic Structure
# openapi.yaml
openapi: 3.1.0
info:
title: My API
version: 1.0.0
description: |
Welcome to the My API documentation.
## Getting Started
1. Sign up for an API key at [dashboard.example.com](https://dashboard.example.com)
2. Include your key in the `Authorization` header
3. Start making requests!
## Rate Limits
- Free tier: 100 requests/minute
- Pro tier: 1000 requests/minute
contact:
name: API Support
email: api@example.com
url: https://support.example.com
license:
name: MIT
url: https://opensource.org/licenses/MIT
servers:
- url: https://api.example.com/v1
description: Production
- url: https://staging-api.example.com/v1
description: Staging
tags:
- name: Users
description: User management operations
- name: Items
description: Item CRUD operationsEndpoint Documentation
paths:
/users:
get:
tags:
- Users
summary: List all users
description: |
Retrieves a paginated list of users. Results are sorted by creation date (newest first).
**Permissions required:** `users:read`
operationId: listUsers
parameters:
- name: page
in: query
description: Page number (1-indexed)
required: false
schema:
type: integer
minimum: 1
default: 1
example: 1
- name: limit
in: query
description: Number of results per page (max 100)
required: false
schema:
type: integer
minimum: 1
maximum: 100
default: 20
example: 20
- name: status
in: query
description: Filter by user status
required: false
schema:
type: string
enum: [active, inactive, pending]
responses:
'200':
description: Successful response
content:
application/json:
schema:
$ref: '#/components/schemas/UserList'
example:
data:
- id: "usr_123"
email: "john@example.com"
name: "John Doe"
status: "active"
created_at: "2024-01-15T10:30:00Z"
meta:
page: 1
limit: 20
total: 150
total_pages: 8
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'429':
$ref: '#/components/responses/RateLimited'
post:
tags:
- Users
summary: Create a new user
description: |
Creates a new user account. An email verification will be sent to the provided address.
**Permissions required:** `users:write`
operationId: createUser
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateUserRequest'
examples:
basic:
summary: Basic user creation
value:
email: "jane@example.com"
name: "Jane Doe"
with_metadata:
summary: User with metadata
value:
email: "jane@example.com"
name: "Jane Doe"
metadata:
department: "Engineering"
role: "Developer"
responses:
'201':
description: User created successfully
content:
application/json:
schema:
$ref: '#/components/schemas/User'
'400':
$ref: '#/components/responses/BadRequest'
'409':
description: User with this email already exists
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
example:
error:
code: "USER_EXISTS"
message: "A user with this email already exists"Schema Definitions
components:
schemas:
User:
type: object
description: Represents a user in the system
required:
- id
- email
- status
- created_at
properties:
id:
type: string
description: Unique identifier (prefixed with `usr_`)
pattern: '^usr_[a-zA-Z0-9]+$'
example: "usr_123abc"
email:
type: string
format: email
description: User's email address (unique)
example: "user@example.com"
name:
type: string
description: User's display name
maxLength: 100
example: "John Doe"
status:
type: string
enum: [active, inactive, pending]
description: |
Account status:
- `active`: User can sign in and use the service
- `inactive`: Account has been deactivated
- `pending`: Awaiting email verification
example: "active"
metadata:
type: object
additionalProperties: true
description: Custom key-value pairs for storing additional data
example:
department: "Engineering"
created_at:
type: string
format: date-time
description: ISO 8601 timestamp of account creation
example: "2024-01-15T10:30:00Z"
updated_at:
type: string
format: date-time
description: ISO 8601 timestamp of last update
example: "2024-01-16T14:45:00Z"
CreateUserRequest:
type: object
required:
- email
properties:
email:
type: string
format: email
description: Email address for the new user (must be unique)
name:
type: string
description: User's display name
maxLength: 100
metadata:
type: object
additionalProperties: true
Error:
type: object
required:
- error
properties:
error:
type: object
required:
- code
- message
properties:
code:
type: string
description: Machine-readable error code
message:
type: string
description: Human-readable error description
details:
type: array
description: Additional error details for validation errors
items:
type: object
properties:
field:
type: string
message:
type: stringAuthentication Documentation
components:
securitySchemes:
BearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
description: |
JWT token obtained from the `/auth/token` endpoint.
Include in requests as:Authorization: Bearer <your-token>
Tokens expire after 1 hour. Use refresh tokens for long-lived sessions.
ApiKeyAuth:
type: apiKey
in: header
name: X-API-Key
description: |
API key for server-to-server communication.
Obtain your key from the [Developer Dashboard](https://dashboard.example.com).
Include in requests as:X-API-Key: <your-api-key>
security:
- BearerAuth: []
- ApiKeyAuth: []Reusable Responses
components:
responses:
BadRequest:
description: Invalid request parameters
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
examples:
validation_error:
summary: Validation error
value:
error:
code: "VALIDATION_ERROR"
message: "Request validation failed"
details:
- field: "email"
message: "Must be a valid email address"
missing_field:
summary: Missing required field
value:
error:
code: "MISSING_FIELD"
message: "Required field 'email' is missing"
Unauthorized:
description: Missing or invalid authentication
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
example:
error:
code: "UNAUTHORIZED"
message: "Invalid or expired authentication token"
Forbidden:
description: Insufficient permissions
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
example:
error:
code: "FORBIDDEN"
message: "You don't have permission to access this resource"
NotFound:
description: Resource not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
example:
error:
code: "NOT_FOUND"
message: "The requested resource was not found"
RateLimited:
description: Rate limit exceeded
headers:
X-RateLimit-Limit:
schema:
type: integer
description: Request limit per minute
X-RateLimit-Remaining:
schema:
type: integer
description: Remaining requests in current window
X-RateLimit-Reset:
schema:
type: integer
description: Unix timestamp when the rate limit resets
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
example:
error:
code: "RATE_LIMITED"
message: "Too many requests. Please retry after 60 seconds"Markdown Documentation
Endpoint Reference Template
# Create User
Create a new user account.
## EndpointPOST /v1/users
## Authentication
Requires API key with `users:write` permission.
## Request Body
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `email` | string | Yes | User's email address (must be unique) |
| `name` | string | No | Display name (max 100 characters) |
| `metadata` | object | No | Custom key-value pairs |
### Example Request
curl -X POST https://api.example.com/v1/users \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "email": "jane@example.com", "name": "Jane Doe" }'
## Response
### Success (201 Created)
{ "id": "usr_abc123", "email": "jane@example.com", "name": "Jane Doe", "status": "pending", "created_at": "2024-01-15T10:30:00Z" }
### Errors
| Status | Code | Description |
| --- | --- | --- |
| 400 | `VALIDATION_ERROR` | Invalid email format or missing required field |
| 401 | `UNAUTHORIZED` | Invalid or missing authentication |
| 409 | `USER_EXISTS` | User with this email already exists |
| 429 | `RATE_LIMITED` | Too many requests |
### Error Example
{ "error": { "code": "USER_EXISTS", "message": "A user with this email already exists" } }
Code Examples
Multi-Language Examples
## Code Examples
### cURL
curl -X GET "https://api.example.com/v1/users?limit=10" \ -H "Authorization: Bearer YOUR_TOKEN"
### JavaScript (fetch)
const response = await fetch('https://api.example.com/v1/users?limit=10', { headers: { 'Authorization': 'Bearer YOUR_TOKEN' } });
const data = await response.json(); console.log(data);
### JavaScript (axios)
import axios from 'axios';
const { data } = await axios.get('https://api.example.com/v1/users', { params: { limit: 10 }, headers: { 'Authorization': 'Bearer YOUR_TOKEN' } });
### Python
import requests
response = requests.get( 'https://api.example.com/v1/users', params={'limit': 10}, headers={'Authorization': 'Bearer YOUR_TOKEN'} )
data = response.json() print(data)
### Ruby
require 'net/http' require 'json'
uri = URI('https://api.example.com/v1/users?limit=10') request = Net::HTTP::Get.new(uri) request['Authorization'] = 'Bearer YOUR_TOKEN'
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http| http.request(request) end
data = JSON.parse(response.body) puts data
### Go
package main
import ( "encoding/json" "fmt" "net/http" )
func main() { req, _ := http.NewRequest("GET", "https://api.example.com/v1/users?limit=10", nil) req.Header.Set("Authorization", "Bearer YOUR_TOKEN")
client := &http.Client{} resp, _ := client.Do(req) defer resp.Body.Close()
var data map[string]interface{} json.NewDecoder(resp.Body).Decode(&data) fmt.Println(data) }
Error Documentation
Error Code Reference
# Error Codes
All API errors follow a consistent format:
{ "error": { "code": "ERROR_CODE", "message": "Human-readable description", "details": [] // Optional additional information } }
## Authentication Errors
| Code | HTTP Status | Description | Resolution |
| --- | --- | --- | --- |
| `UNAUTHORIZED` | 401 | Missing or invalid token | Include a valid `Authorization` header |
| `TOKEN_EXPIRED` | 401 | Token has expired | Obtain a new token via `/auth/token` |
| `INVALID_API_KEY` | 401 | API key not recognized | Check your API key in the dashboard |
| `FORBIDDEN` | 403 | Insufficient permissions | Request appropriate scopes for your token |
## Validation Errors
| Code | HTTP Status | Description | Resolution |
| --- | --- | --- | --- |
| `VALIDATION_ERROR` | 400 | Request body validation failed | Check the `details` array for specific fields |
| `MISSING_FIELD` | 400 | Required field not provided | Include all required fields |
| `INVALID_FORMAT` | 400 | Field format is incorrect | Match the expected format (see schema) |
## Resource Errors
| Code | HTTP Status | Description | Resolution |
| --- | --- | --- | --- |
| `NOT_FOUND` | 404 | Resource doesn't exist | Verify the resource ID is correct |
| `ALREADY_EXISTS` | 409 | Resource already exists | Use a different identifier or update existing |
| `CONFLICT` | 409 | State conflict | Refresh and retry the operation |
## Rate Limiting
| Code | HTTP Status | Description | Resolution |
| --- | --- | --- | --- |
| `RATE_LIMITED` | 429 | Too many requests | Wait and retry (see `Retry-After` header) |
Changelog Documentation
API Changelog Format
# API Changelog
## 2024-01-15 - v1.2.0
### Added
- `GET /users/{id}/activity` - Retrieve user activity history
- `metadata` field on User object for custom data storage
### Changed
- Pagination now 1-indexed (previously 0-indexed)
- Rate limits increased: Free tier 100 → 200 req/min
### Deprecated
- `GET /users/search` - Use `GET /users` with query parameters instead
- Will be removed in v2.0.0
### Fixed
- `created_at` now correctly returns UTC timezone
---
## 2024-01-01 - v1.1.0
### Added
- Webhook support for user events
- `status` filter on `GET /users`
### Security
- API keys now support IP allowlistingInteractive Documentation
Swagger UI Configuration
// swagger-config.js
const swaggerUiOptions = {
customCss: '.swagger-ui .topbar { display: none }',
customSiteTitle: 'API Documentation',
customfavIcon: '/favicon.ico',
swaggerOptions: {
persistAuthorization: true,
displayRequestDuration: true,
filter: true,
tryItOutEnabled: true,
},
};Redoc Configuration
# redoc.yaml
x-tagGroups:
- name: Getting Started
tags:
- Authentication
- name: Core Resources
tags:
- Users
- Items
- name: Utilities
tags:
- Webhooks
- Health
x-logo:
url: 'https://example.com/logo.png'
altText: 'API Logo'Documentation Checklist
Per Endpoint
- Summary (one line)
- Description (detailed explanation)
- All parameters documented with types and examples
- All response codes with examples
- Error codes with remediation steps
- Code examples in at least 2 languages
- Authentication requirements stated
Overall API
- Getting started guide
- Authentication guide with examples
- Rate limiting documentation
- Pagination patterns
- Error handling guide
- Changelog maintained
- Versioning strategy documented
- SDK/library links
Developer Experience
- Interactive "Try It" functionality
- Copy-paste ready examples
- Consistent terminology
- Search functionality
- Mobile-friendly rendering
Tools Integration
Generate from Code
# From Express/Node.js routes
npx swagger-jsdoc -d swaggerDef.js -o openapi.yaml
# From TypeScript types
npx openapi-typescript-codegen --input openapi.yaml --output ./sdkValidate OpenAPI
# Validate spec
npx @redocly/cli lint openapi.yaml
# Preview documentation
npx @redocly/cli preview-docs openapi.yamlWhen to Use This Skill
Invoke this skill when:
- Creating new API documentation from scratch
- Adding documentation for new endpoints
- Writing OpenAPI/Swagger specifications
- Creating code examples for multiple languages
- Documenting authentication flows
- Building developer portals
- Improving existing API documentation
- Setting up interactive documentation (Swagger UI, Redoc)