API Documentation¶
Write documentation that developers actually use.
Documentation Principles¶
1. Show, Don't Just Tell¶
❌ Bad: "Use the POST method to create a user"
✅ Good:
```bash
curl -X POST https://api.example.com/users \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"email": "user@example.com", "name": "John"}'
Response:
### 2. Document the Why, Not Just the What
```markdown
❌ Bad: "The `page` parameter specifies the page number"
✅ Good: "Use pagination to efficiently retrieve large datasets.
Results are returned in pages of 20 items by default.
Use `page` and `per_page` to navigate through results."
3. Cover Edge Cases¶
✅ Good:
**Note:** If no users match the filter criteria, an empty array is returned
with a 200 status code, not a 404.
**Rate Limiting:** This endpoint allows 100 requests per minute.
Exceeding this limit returns a 429 error with a `Retry-After` header.
Documentation Structure¶
API Overview¶
Production: https://api.sartiq.com/v1 Staging: https://api-staging.sartiq.com/v1## Authentication
All requests require a Bearer token in the Authorization header:
```bash
curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
https://api.sartiq.com/v1/users/me
Obtain tokens via OAuth 2.0 authentication (see Authentication section below).
Rate Limits¶
| Plan | Requests/minute | Requests/day |
|---|---|---|
| Free | 60 | 1,000 |
| Pro | 300 | 10,000 |
| Enterprise | Custom | Custom |
Rate limit headers are included in every response:
- X-RateLimit-Limit: Maximum requests allowed
- X-RateLimit-Remaining: Requests remaining
- X-RateLimit-Reset: Unix timestamp when limit resets
Versioning¶
The API version is included in the URL path (/v1/).
See versioning policy for compatibility guarantees.
### Request
#### Headers
| Header | Required | Description |
|--------|----------|-------------|
| Authorization | Yes | Bearer token |
| Content-Type | Yes | `application/json` |
| Idempotency-Key | No | Unique key for idempotent requests |
#### Body
```json
{
"email": "user@example.com",
"name": "John Doe",
"password": "securepassword123",
"role": "user"
}
| Field | Type | Required | Description |
|---|---|---|---|
| string | Yes | Valid email address | |
| name | string | Yes | Full name (1-100 chars) |
| password | string | Yes | Password (min 12 chars) |
| role | string | No | user (default) or admin |
Response¶
Success (201 Created)¶
{
"id": 123,
"email": "user@example.com",
"name": "John Doe",
"role": "user",
"created_at": "2024-01-15T10:30:00Z"
}
Errors¶
| Status | Error Code | Description |
|---|---|---|
| 400 | invalid_request |
Missing required fields |
| 409 | duplicate_email |
Email already registered |
| 422 | validation_error |
Invalid field values |
Example¶
curl -X POST https://api.example.com/v1/users \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"email": "john@example.com",
"name": "John Doe",
"password": "securepassword123"
}'
Code Examples¶
Python¶
import httpx
response = httpx.post(
"https://api.example.com/v1/users",
headers={"Authorization": f"Bearer {token}"},
json={
"email": "john@example.com",
"name": "John Doe",
"password": "securepassword123",
},
)
user = response.json()
TypeScript¶
const response = await fetch("https://api.example.com/v1/users", {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
email: "john@example.com",
name: "John Doe",
password: "securepassword123",
}),
});
const user = await response.json();
## FastAPI Documentation Enhancement
### Docstrings for OpenAPI
```python
@app.post(
"/users",
response_model=User,
status_code=201,
summary="Create user",
responses={
201: {"description": "User created successfully"},
409: {"description": "Email already exists"},
422: {"description": "Validation error"},
},
)
async def create_user(user: UserCreate):
"""
Create a new user account.
- **email**: Valid email address (must be unique)
- **name**: Full name (1-100 characters)
- **password**: Secure password (minimum 12 characters)
Returns the created user object with a unique ID.
"""
pass
Custom OpenAPI Schema¶
from fastapi import FastAPI
from fastapi.openapi.utils import get_openapi
def custom_openapi():
if app.openapi_schema:
return app.openapi_schema
openapi_schema = get_openapi(
title="Sartiq API",
version="1.0.0",
description=open("docs/api-overview.md").read(),
routes=app.routes,
)
# Add security scheme
openapi_schema["components"]["securitySchemes"] = {
"bearerAuth": {
"type": "http",
"scheme": "bearer",
"bearerFormat": "JWT",
}
}
# Add servers
openapi_schema["servers"] = [
{"url": "https://api.sartiq.com/v1", "description": "Production"},
{"url": "https://api-staging.sartiq.com/v1", "description": "Staging"},
]
app.openapi_schema = openapi_schema
return app.openapi_schema
app.openapi = custom_openapi
Interactive Examples¶
Try It Now Sections¶
## Try It
<div class="api-try-it">
<form>
<label>Email:</label>
<input type="email" name="email" value="test@example.com">
<label>Name:</label>
<input type="text" name="name" value="Test User">
<button type="submit">Send Request</button>
</form>
<pre class="response"></pre>
</div>
Postman/Insomnia Collections¶
{
"info": {
"name": "Sartiq API",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"auth": {
"type": "bearer",
"bearer": [{"key": "token", "value": "{{access_token}}"}]
},
"item": [
{
"name": "Users",
"item": [
{
"name": "Create User",
"request": {
"method": "POST",
"url": "{{base_url}}/users",
"body": {
"mode": "raw",
"raw": "{\"email\": \"test@example.com\", \"name\": \"Test\"}"
}
}
}
]
}
]
}
SDK Documentation¶
Getting Started¶
Quick Start¶
from sartiq import Sartiq
client = Sartiq(api_key="your-api-key")
# Create a user
user = client.users.create(
email="john@example.com",
name="John Doe",
)
# List photos
photos = client.photos.list(user_id=user.id)
# Upload a photo
photo = client.photos.upload(
file=open("sunset.jpg", "rb"),
title="Sunset",
tags=["nature", "sunset"],
)
Error Handling¶
from sartiq.exceptions import (
SartiqError,
ValidationError,
NotFoundError,
RateLimitError,
)
try:
user = client.users.get(123)
except NotFoundError:
print("User not found")
except RateLimitError as e:
print(f"Rate limited. Retry after {e.retry_after} seconds")
except SartiqError as e:
print(f"API error: {e.message}")
## Changelog
```markdown
# Changelog
All notable changes to the API are documented here.
## [2.0.0] - 2024-03-01
### Breaking Changes
- Removed deprecated `GET /users/list` endpoint. Use `GET /users` instead.
- Changed `name` field to `full_name` in User response.
### Added
- New `PATCH /users/{id}` endpoint for partial updates.
- Added `avatar_url` field to User response.
### Deprecated
- `GET /photos/recent` will be removed in v3. Use `GET /photos?sort=-created_at` instead.
## [1.5.0] - 2024-02-15
### Added
- Pagination support for all list endpoints.
- New `X-RateLimit-*` headers.
### Fixed
- Fixed incorrect 500 error when creating user with duplicate email (now returns 409).
Documentation Testing¶
Test Examples Work¶
import pytest
import subprocess
def extract_code_blocks(markdown: str, language: str) -> list[str]:
"""Extract code blocks of a specific language from markdown."""
import re
pattern = rf"```{language}\n(.*?)```"
return re.findall(pattern, markdown, re.DOTALL)
def test_curl_examples():
"""Test that curl examples in docs are valid."""
docs = open("docs/api.md").read()
curl_examples = extract_code_blocks(docs, "bash")
for example in curl_examples:
if example.strip().startswith("curl"):
# Validate curl syntax (dry run)
result = subprocess.run(
["curl", "--dry-run"] + example.split()[1:],
capture_output=True,
)
assert result.returncode == 0
def test_python_examples():
"""Test that Python examples are syntactically valid."""
docs = open("docs/api.md").read()
python_examples = extract_code_blocks(docs, "python")
for example in python_examples:
compile(example, "<string>", "exec") # Syntax check
Keep Docs in Sync¶
# .github/workflows/docs.yml
name: Validate Documentation
on: [push, pull_request]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Validate OpenAPI spec
run: |
pip install openapi-spec-validator
openapi-spec-validator openapi.json
- name: Test code examples
run: pytest tests/test_docs.py
- name: Check for broken links
run: |
pip install linkchecker
linkchecker docs/
Best Practices Summary¶
| Practice | Implementation |
|---|---|
| Show real examples | curl, Python, TypeScript snippets |
| Document all fields | Type, required, constraints, defaults |
| Cover edge cases | Empty responses, errors, limits |
| Provide SDKs | Reduce boilerplate for clients |
| Version changelog | Track all API changes |
| Test examples | Ensure docs stay accurate |
| Interactive docs | Swagger UI, try-it-now |
| Quick start guide | Get users running in minutes |