AP
Documentation
Api Design core v1.0.0
API Documentation
Overview
Good API documentation enables developers to integrate quickly and correctly. This skill covers OpenAPI/Swagger specifications, documentation best practices, and developer experience.
Key Concepts
Documentation Components
┌─────────────────────────────────────────────────────────────┐
│ API Documentation Components │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. Reference Documentation (OpenAPI): │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ • Endpoints and methods │ │
│ │ • Request/response schemas │ │
│ │ • Authentication requirements │ │
│ │ • Error codes and responses │ │
│ │ • Query parameters and headers │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 2. Conceptual Documentation: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ • Getting started guide │ │
│ │ • Authentication overview │ │
│ │ • Rate limiting policies │ │
│ │ • Pagination conventions │ │
│ │ • Error handling guide │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 3. Tutorials and Examples: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ • Use case walkthroughs │ │
│ │ • Code samples in multiple languages │ │
│ │ • Interactive examples (try it) │ │
│ │ • SDKs and client libraries │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 4. Changelog and Migration: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ • Version history │ │
│ │ • Breaking changes │ │
│ │ • Deprecation notices │ │
│ │ • Migration guides │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
Best Practices
1. Document Every Endpoint
Include all parameters, responses, and errors.
2. Provide Runnable Examples
Show complete, copy-paste-ready code.
3. Document Error Scenarios
Include all possible error responses.
4. Keep Documentation in Sync
Generate from code or validate continuously.
5. Include Authentication Details
Show how to authenticate in every example.
Code Examples
Example 1: OpenAPI Specification
openapi: 3.1.0
info:
title: Order Management API
description: |
API for managing customer orders.
## Authentication
All endpoints require a Bearer token in the Authorization header.
## Rate Limiting
- Standard tier: 100 requests/minute
- Premium tier: 1000 requests/minute
version: 2.0.0
contact:
name: API Support
email: api-support@example.com
url: https://developer.example.com/support
license:
name: Apache 2.0
url: https://www.apache.org/licenses/LICENSE-2.0
servers:
- url: https://api.example.com/v2
description: Production
- url: https://api-staging.example.com/v2
description: Staging
tags:
- name: Orders
description: Order management operations
- name: Customers
description: Customer management operations
paths:
/orders:
get:
tags: [Orders]
summary: List orders
description: |
Retrieve a paginated list of orders with optional filtering.
**Permissions required:** `orders:read`
operationId: listOrders
parameters:
- name: status
in: query
description: Filter by order status
schema:
$ref: '#/components/schemas/OrderStatus'
- name: customerId
in: query
description: Filter by customer ID
schema:
type: string
format: uuid
- name: page
in: query
description: Page number (0-indexed)
schema:
type: integer
minimum: 0
default: 0
- name: size
in: query
description: Page size
schema:
type: integer
minimum: 1
maximum: 100
default: 20
responses:
'200':
description: Successful response
content:
application/json:
schema:
$ref: '#/components/schemas/OrderListResponse'
example:
data:
- id: "ord_123abc"
status: "pending"
total: 99.99
createdAt: "2024-01-15T10:30:00Z"
pagination:
page: 0
size: 20
totalElements: 150
totalPages: 8
'401':
$ref: '#/components/responses/Unauthorized'
'429':
$ref: '#/components/responses/RateLimited'
post:
tags: [Orders]
summary: Create order
description: |
Create a new order for a customer.
**Permissions required:** `orders:write`
operationId: createOrder
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateOrderRequest'
example:
customerId: "cust_456def"
items:
- productId: "prod_789ghi"
quantity: 2
- productId: "prod_012jkl"
quantity: 1
shippingAddress:
street: "123 Main St"
city: "San Francisco"
state: "CA"
postalCode: "94102"
country: "US"
responses:
'201':
description: Order created
headers:
Location:
description: URL of created order
schema:
type: string
content:
application/json:
schema:
$ref: '#/components/schemas/Order'
'400':
$ref: '#/components/responses/BadRequest'
'422':
$ref: '#/components/responses/ValidationError'
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
description: |
JWT token obtained from the authentication endpoint.
Example: `Authorization: Bearer eyJhbGciOiJIUzI1NiIs...`
schemas:
Order:
type: object
required:
- id
- status
- customerId
- items
- total
- createdAt
properties:
id:
type: string
description: Unique order identifier
example: "ord_123abc"
status:
$ref: '#/components/schemas/OrderStatus'
customerId:
type: string
format: uuid
description: Customer who placed the order
items:
type: array
items:
$ref: '#/components/schemas/OrderItem'
total:
type: number
format: decimal
description: Total order amount
example: 99.99
createdAt:
type: string
format: date-time
description: When the order was created
updatedAt:
type: string
format: date-time
description: When the order was last updated
OrderStatus:
type: string
enum: [pending, confirmed, shipped, delivered, cancelled]
description: |
Order status:
- `pending`: Order received, awaiting confirmation
- `confirmed`: Order confirmed and processing
- `shipped`: Order shipped to customer
- `delivered`: Order delivered
- `cancelled`: Order cancelled
ProblemDetail:
type: object
description: RFC 7807 Problem Details format
properties:
type:
type: string
format: uri
description: URI identifying the problem type
title:
type: string
description: Short, human-readable summary
status:
type: integer
description: HTTP status code
detail:
type: string
description: Human-readable explanation
instance:
type: string
format: uri
description: URI of the specific occurrence
traceId:
type: string
description: Request trace ID for debugging
responses:
Unauthorized:
description: Authentication required or invalid token
content:
application/problem+json:
schema:
$ref: '#/components/schemas/ProblemDetail'
example:
type: "https://api.example.com/errors/unauthorized"
title: "Unauthorized"
status: 401
detail: "Bearer token is missing or invalid"
RateLimited:
description: Rate limit exceeded
headers:
Retry-After:
description: Seconds until rate limit resets
schema:
type: integer
X-RateLimit-Limit:
description: Rate limit ceiling
schema:
type: integer
X-RateLimit-Remaining:
description: Remaining requests in window
schema:
type: integer
content:
application/problem+json:
schema:
$ref: '#/components/schemas/ProblemDetail'
security:
- bearerAuth: []
Example 2: SpringDoc OpenAPI Configuration
@Configuration
public class OpenApiConfig {
@Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.info(new Info()
.title("Order Management API")
.version("2.0.0")
.description("API for managing customer orders")
.contact(new Contact()
.name("API Support")
.email("api-support@example.com")
.url("https://developer.example.com"))
.license(new License()
.name("Apache 2.0")
.url("https://www.apache.org/licenses/LICENSE-2.0")))
.externalDocs(new ExternalDocumentation()
.description("Full Documentation")
.url("https://developer.example.com/docs"))
.addSecurityItem(new SecurityRequirement().addList("bearerAuth"))
.components(new Components()
.addSecuritySchemes("bearerAuth", new SecurityScheme()
.type(SecurityScheme.Type.HTTP)
.scheme("bearer")
.bearerFormat("JWT")
.description("JWT authentication token")));
}
@Bean
public GroupedOpenApi publicApi() {
return GroupedOpenApi.builder()
.group("public")
.pathsToMatch("/api/v2/**")
.addOperationCustomizer((operation, handlerMethod) -> {
// Add common headers
operation.addParametersItem(new Parameter()
.in("header")
.name("X-Request-ID")
.description("Client-generated request ID for tracing")
.schema(new StringSchema()));
return operation;
})
.build();
}
}
@RestController
@RequestMapping("/api/v2/orders")
@Tag(name = "Orders", description = "Order management operations")
public class OrderController {
@Operation(
summary = "Create a new order",
description = """
Create a new order for a customer.
The order will be created in 'pending' status and needs to be confirmed
before processing begins.
**Permissions required:** `orders:write`
""",
responses = {
@ApiResponse(
responseCode = "201",
description = "Order created successfully",
headers = @Header(
name = "Location",
description = "URL of the created order"
),
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = OrderResponse.class),
examples = @ExampleObject(
name = "Created order",
value = """
{
"id": "ord_123abc",
"status": "pending",
"total": 99.99,
"createdAt": "2024-01-15T10:30:00Z"
}
"""
)
)
),
@ApiResponse(
responseCode = "422",
description = "Validation failed",
content = @Content(
mediaType = "application/problem+json",
schema = @Schema(implementation = ProblemDetail.class)
)
)
}
)
@PostMapping
public ResponseEntity<OrderResponse> createOrder(
@io.swagger.v3.oas.annotations.parameters.RequestBody(
description = "Order details",
required = true,
content = @Content(
schema = @Schema(implementation = CreateOrderRequest.class),
examples = @ExampleObject(
name = "Example order",
value = """
{
"customerId": "cust_456",
"items": [
{"productId": "prod_789", "quantity": 2}
]
}
"""
)
)
)
@Valid @RequestBody CreateOrderRequest request) {
// Implementation
}
@Operation(
summary = "Get order by ID",
parameters = {
@Parameter(
name = "id",
description = "Order ID",
required = true,
example = "ord_123abc"
)
}
)
@GetMapping("/{id}")
public ResponseEntity<OrderResponse> getOrder(@PathVariable String id) {
// Implementation
}
}
Example 3: Request/Response Documentation
@Schema(description = "Request to create a new order")
@Data
public class CreateOrderRequest {
@Schema(
description = "Customer ID placing the order",
example = "cust_456def",
requiredMode = Schema.RequiredMode.REQUIRED
)
@NotNull
private String customerId;
@Schema(
description = "Items to include in the order",
minLength = 1
)
@NotEmpty
@Valid
private List<OrderItemRequest> items;
@Schema(description = "Shipping address for the order")
@Valid
private AddressRequest shippingAddress;
@Schema(
description = "Special instructions for the order",
example = "Please leave at front door",
maxLength = 500
)
@Size(max = 500)
private String notes;
}
@Schema(description = "Order item")
@Data
public class OrderItemRequest {
@Schema(
description = "Product ID",
example = "prod_789ghi",
requiredMode = Schema.RequiredMode.REQUIRED
)
@NotNull
private String productId;
@Schema(
description = "Quantity to order",
minimum = "1",
maximum = "100",
example = "2",
requiredMode = Schema.RequiredMode.REQUIRED
)
@Min(1)
@Max(100)
private Integer quantity;
}
@Schema(description = "Order response")
@Data
public class OrderResponse {
@Schema(
description = "Unique order identifier",
example = "ord_123abc"
)
private String id;
@Schema(
description = "Current order status",
example = "pending"
)
private OrderStatus status;
@Schema(
description = "Order total in customer's currency",
example = "99.99"
)
private BigDecimal total;
@Schema(
description = "Currency code",
example = "USD"
)
private String currency;
@Schema(description = "Order line items")
private List<OrderItemResponse> items;
@Schema(
description = "When the order was created",
example = "2024-01-15T10:30:00Z"
)
private Instant createdAt;
@Schema(description = "HATEOAS links for available actions")
private Map<String, Link> _links;
}
Example 4: Interactive Documentation Examples
// Code samples for documentation
public class ApiExamples {
/**
* Generate code samples for different languages
*/
public Map<String, String> getCodeSamples(String endpoint, String method, Object requestBody) {
Map<String, String> samples = new LinkedHashMap<>();
samples.put("curl", generateCurlExample(endpoint, method, requestBody));
samples.put("python", generatePythonExample(endpoint, method, requestBody));
samples.put("javascript", generateJavaScriptExample(endpoint, method, requestBody));
samples.put("java", generateJavaExample(endpoint, method, requestBody));
return samples;
}
private String generateCurlExample(String endpoint, String method, Object body) {
StringBuilder curl = new StringBuilder();
curl.append("curl -X ").append(method).append(" \\\n");
curl.append(" 'https://api.example.com/v2").append(endpoint).append("' \\\n");
curl.append(" -H 'Authorization: Bearer YOUR_TOKEN' \\\n");
curl.append(" -H 'Content-Type: application/json'");
if (body != null) {
curl.append(" \\\n -d '").append(toJson(body)).append("'");
}
return curl.toString();
}
private String generatePythonExample(String endpoint, String method, Object body) {
return """
import requests
response = requests.%s(
'https://api.example.com/v2%s',
headers={
'Authorization': 'Bearer YOUR_TOKEN',
'Content-Type': 'application/json'
},
json=%s
)
print(response.json())
""".formatted(method.toLowerCase(), endpoint, toPythonDict(body));
}
private String generateJavaScriptExample(String endpoint, String method, Object body) {
return """
const response = await fetch('https://api.example.com/v2%s', {
method: '%s',
headers: {
'Authorization': 'Bearer YOUR_TOKEN',
'Content-Type': 'application/json'
},
body: JSON.stringify(%s)
});
const data = await response.json();
console.log(data);
""".formatted(endpoint, method, toJson(body));
}
}
// Endpoint to generate live examples
@RestController
@RequestMapping("/api/docs")
public class DocumentationController {
private final ApiExamples examples;
@GetMapping("/examples/{operation}")
public ResponseEntity<Map<String, String>> getExamples(@PathVariable String operation) {
// Return code examples for the operation
return switch (operation) {
case "create-order" -> ResponseEntity.ok(
examples.getCodeSamples("/orders", "POST",
Map.of("customerId", "cust_456", "items", List.of(
Map.of("productId", "prod_789", "quantity", 2)
))
)
);
case "get-order" -> ResponseEntity.ok(
examples.getCodeSamples("/orders/ord_123", "GET", null)
);
default -> ResponseEntity.notFound().build();
};
}
}
Example 5: Changelog and Migration Documentation
@RestController
@RequestMapping("/api/docs")
public class ChangelogController {
@GetMapping("/changelog")
public ResponseEntity<Changelog> getChangelog() {
return ResponseEntity.ok(new Changelog(List.of(
new ChangelogEntry(
"2.1.0",
LocalDate.of(2024, 2, 1),
List.of(
new Change(ChangeType.ADDED,
"Added `metadata` field to Order response"),
new Change(ChangeType.ADDED,
"New endpoint: GET /orders/{id}/history"),
new Change(ChangeType.DEPRECATED,
"`notes` field deprecated, use `metadata.notes` instead")
)
),
new ChangelogEntry(
"2.0.0",
LocalDate.of(2024, 1, 1),
List.of(
new Change(ChangeType.BREAKING,
"User.name split into firstName and lastName"),
new Change(ChangeType.BREAKING,
"Renamed /users/{id}/orders to /orders?customerId={id}"),
new Change(ChangeType.ADDED,
"Added address field to User response"),
new Change(ChangeType.REMOVED,
"Removed legacy XML response format")
)
)
)));
}
@GetMapping("/migration/{from}/{to}")
public ResponseEntity<MigrationGuide> getMigrationGuide(
@PathVariable String from,
@PathVariable String to) {
if ("1".equals(from) && "2".equals(to)) {
return ResponseEntity.ok(new MigrationGuide(
"v1", "v2",
"""
# Migration Guide: v1 to v2
## Breaking Changes
### 1. User Name Field Split
**Before (v1):**
```json
{ "name": "John Doe" }
```
**After (v2):**
```json
{ "firstName": "John", "lastName": "Doe" }
```
**Migration steps:**
1. Update your code to use `firstName` and `lastName`
2. If you need full name, concatenate: `${firstName} ${lastName}`
### 2. Orders Endpoint Change
**Before (v1):**
```
GET /users/{userId}/orders
```
**After (v2):**
```
GET /orders?customerId={userId}
```
## New Features
### Address Field
User responses now include an `address` object.
## Timeline
- v1 sunset date: 2025-07-01
- v1 deprecation warnings: Enabled
""",
LocalDate.of(2025, 7, 1)
));
}
return ResponseEntity.notFound().build();
}
}
enum ChangeType {
ADDED, CHANGED, DEPRECATED, REMOVED, FIXED, SECURITY, BREAKING
}
record Change(ChangeType type, String description) {}
record ChangelogEntry(String version, LocalDate date, List<Change> changes) {}
record Changelog(List<ChangelogEntry> entries) {}
record MigrationGuide(String fromVersion, String toVersion, String content, LocalDate sunsetDate) {}
Anti-Patterns
❌ Outdated Documentation
Documentation that doesn’t match implementation is worse than none.
❌ Missing Error Documentation
# WRONG - only happy path
responses:
'200':
description: Success
# ✅ CORRECT - all responses
responses:
'200':
description: Success
'400':
description: Invalid request
'401':
description: Authentication required
'404':
description: Resource not found