# API_DESIGN.md - API Design Standards
**Authority:** guidance (comprehensive API design with exact specifications, schemas, and patterns)
**Layer:** Architecture
**Binding:** No
**Scope:** REST, GraphQL, gRPC API design with exact specifications for pre-inference context
---
## 1. REST API Design
### 1.1 Resource Naming Conventions
```yaml
# Rules:
# - Use nouns, not verbs (GET /users not GET /getUsers)
# - Use plural for collections (/users not /user)
# - Use kebab-case for multi-word paths (/user-profiles not /userProfiles)
# - Nest resources for relationships (max 2 levels deep)
# - Use query parameters for filtering, sorting, pagination
# Good examples:
GET /users # List users
GET /users/{userId} # Get single user
POST /users # Create user
PUT /users/{userId} # Full update (replace)
PATCH /users/{userId} # Partial update
DELETE /users/{userId} # Delete user
GET /users/{userId}/orders # User's orders (nested)
GET /users/{userId}/orders/{orderId} # Specific order
# Bad examples:
GET /getUser?id=123 # Verb in path
GET /user/123 # Singular
POST /createUser # Verb in path
DELETE /user/123/orders/all # 3 levels deep
# Query parameters:
GET /users?status=active&sort=created_at:desc&limit=20&offset=0
GET /orders?created_after=2024-01-01&total_gt=100
GET /products?category=electronics&in_stock=true
GET /users?search=john&fields=id,name,email
```
### 1.2 HTTP Methods
```yaml
GET # Retrieve resource(s) - idempotent, no body
POST # Create new resource - not idempotent
PUT # Replace resource entirely - idempotent
PATCH # Partial update - idempotent (with proper semantics)
DELETE # Remove resource - idempotent
HEAD # Like GET but headers only
OPTIONS # CORS preflight, supported methods
# Safe methods: GET, HEAD, OPTIONS (don't modify server state)
# Idempotent methods: GET, PUT, DELETE, HEAD, OPTIONS
# (Idempotent = same request = same result, even if called multiple times)
```
### 1.3 Complete Request/Response Examples
#### Create Resource (POST)
```http
POST /v1/users HTTP/1.1
Host: api.example.com
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
Accept: application/json
X-Request-ID: f47ac10b-58cc-4372-a567-0e02b2c3d479
X-Correlation-ID: abc123
{
"data": {
"type": "users",
"attributes": {
"email": "john.doe@example.com",
"name": "John Doe",
"role": "engineer",
"department": "engineering",
"metadata": {
"hire_date": "2024-01-15",
"location": "New York"
}
},
"relationships": {
"manager": {
"data": { "type": "users", "id": "usr_789xyz" }
},
"teams": {
"data": [
{ "type": "teams", "id": "team_alpha" },
{ "type": "teams", "id": "team_beta" }
]
}
}
}
}
```
```http
HTTP/1.1 201 Created
Content-Type: application/vnd.api+json
Location: /v1/users/usr_abc123
X-Request-ID: f47ac10b-58cc-4372-a567-0e02b2c3d479
ETag: "v1"
Cache-Control: no-cache
{
"data": {
"id": "usr_abc123",
"type": "users",
"links": {
"self": "/v1/users/usr_abc123"
},
"attributes": {
"email": "john.doe@example.com",
"name": "John Doe",
"role": "engineer",
"department": "engineering",
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T10:30:00Z",
"metadata": {
"hire_date": "2024-01-15",
"location": "New York"
}
},
"relationships": {
"manager": {
"links": {
"related": "/v1/users/usr_abc123/manager"
},
"data": { "type": "users", "id": "usr_789xyz" }
},
"teams": {
"links": {
"related": "/v1/users/usr_abc123/teams"
},
"data": [
{ "type": "teams", "id": "team_alpha" },
{ "type": "teams", "id": "team_beta" }
]
}
},
"meta": {
"created_by": "usr_system",
"version": 1
}
},
"included": [
{
"id": "usr_789xyz",
"type": "users",
"attributes": {
"name": "Jane Manager"
}
},
{
"id": "team_alpha",
"type": "teams",
"attributes": {
"name": "Platform Team"
}
},
{
"id": "team_beta",
"type": "teams",
"attributes": {
"name": "Infrastructure Team"
}
}
]
}
```
#### Get Resource with Filtering (GET)
```http
GET /v1/users/usr_abc123?include=manager,teams&fields[users]=id,name,email,role HTTP/1.1
Host: api.example.com
Accept: application/json
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
```
```http
HTTP/1.1 200 OK
Content-Type: application/vnd.api+json
ETag: "v3"
Last-Modified: Mon, 15 Jan 2024 11:45:00 GMT
Cache-Control: private, max-age=300
{
"data": {
"id": "usr_abc123",
"type": "users",
"attributes": {
"name": "John Doe",
"email": "john.doe@example.com",
"role": "engineer"
},
"relationships": {
"manager": {
"data": { "type": "users", "id": "usr_789xyz" }
},
"teams": {
"data": [
{ "type": "teams", "id": "team_alpha" },
{ "type": "teams", "id": "team_beta" }
]
}
}
},
"included": [
{
"id": "usr_789xyz",
"type": "users",
"attributes": {
"name": "Jane Manager",
"email": "jane@example.com"
}
},
{
"id": "team_alpha",
"type": "teams",
"attributes": {
"name": "Platform Team"
}
}
]
}
```
### 1.4 Pagination Specifications
#### Cursor-based Pagination (Preferred for large datasets)
```http
GET /v1/orders?page[limit]=25&page[cursor]=eyJpZCI6MTIzfQ== HTTP/1.1
```
```json
{
"data": [...],
"pagination": {
"cursors": {
"before": "eyJpZCI6MTAwfQ==",
"after": "eyJpZCI6MTI1fQ=="
},
"has_more": true,
"total": null
},
"links": {
"first": "/v1/orders?page[limit]=25",
"next": "/v1/orders?page[limit]=25&page[cursor]=eyJpZCI6MTI1fQ==",
"prev": "/v1/orders?page[limit]=25&page[cursor]=eyJpZCI6MTAwfQ=="
}
}
```
#### Offset-based Pagination (Simple use cases)
```http
GET /v1/users?page[limit]=20&page[offset]=0&page[number]=1 HTTP/1.1
```
```json
{
"data": [...],
"pagination": {
"limit": 20,
"offset": 0,
"total": 1500,
"current_page": 1,
"total_pages": 75
},
"links": {
"first": "/v1/users?page[limit]=20&page[offset]=0",
"next": "/v1/users?page[limit]=20&page[offset]=20",
"prev": null,
"last": "/v1/users?page[limit]=20&page[offset]=1480"
}
}
```
#### Keyset Pagination (For extreme performance)
```http
# Use compound sort keys for stable pagination
GET /v1/events?sort=created_at,id&after_id=evt_123&limit=50
# After getting results, use last item's sort keys for next page:
GET /v1/events?sort=created_at,id&after_created_at=2024-01-15T10:30:00Z&after_id=evt_456&limit=50
```
### 1.5 Response Envelope Patterns
```json
{
"request_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"timestamp": "2024-01-15T10:30:00Z",
"api_version": "v1",
"pagination": {...} | null,
"count": 150,
"filters_applied": {
"status": "active",
"created_after": "2024-01-01"
}
},
"links": {...}
}
```
### 1.6 Error Response Patterns
```json
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"details": [
{
"field": "email",
"code": "INVALID_FORMAT",
"message": "Email format is invalid",
"value": "not-an-email"
},
{
"field": "age",
"code": "OUT_OF_RANGE",
"message": "Age must be between 0 and 150",
"value": -5
}
],
"source": {
"pointer": "/data/attributes/email",
"parameter": "email"
},
"documentation_url": "https://api.example.com/docs/errors/VALIDATION_ERROR",
"trace_id": "abc123",
"request_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479"
},
"meta": {
"timestamp": "2024-01-15T10:30:00Z"
}
}
```
#### HTTP Status Codes
```yaml
# 2xx Success
200 OK # GET, PUT, PATCH succeeded
201 Created # POST created new resource
202 Accepted # Async operation queued
204 No Content # DELETE succeeded, no body
# 4xx Client Errors
400 Bad Request # Malformed request, invalid syntax
401 Unauthorized # No/invalid authentication
403 Forbidden # Authenticated but not authorized
404 Not Found # Resource doesn't exist
405 Method Not Allowed # HTTP method not supported
409 Conflict # State conflict (duplicate, version mismatch)
410 Gone # Resource permanently deleted
422 Unprocessable Entity # Validation failed (semantic errors)
429 Too Many Requests # Rate limit exceeded
# 5xx Server Errors
500 Internal Server Error # Unexpected error
501 Not Implemented # Feature not implemented
502 Bad Gateway # Upstream/service failure
503 Service Unavailable # Temporarily unavailable
504 Gateway Timeout # Upstream timeout
```
---
## 2. GraphQL API Design
### 2.1 Schema Design
```graphql
# schema.graphql
scalar DateTime
scalar JSON
scalar UUID
enum UserRole {
ADMIN
ENGINEER
MANAGER
VIEWER
}
enum OrderStatus {
PENDING
PROCESSING
SHIPPED
DELIVERED
CANCELLED
}
type User {
id: ID!
email: String!
name: String!
role: UserRole!
# Relations
manager: User
directReports: [User!]!
orders: OrderConnection!
# Computed
fullName: String!
isActive: Boolean!
# Timestamps
createdAt: DateTime!
updatedAt: DateTime!
# Meta
metadata: JSON
}
type Order {
id: ID!
status: OrderStatus!
total: Decimal!
currency: String!
# Relations
user: User!
items: [OrderItem!]!
# Timestamps
createdAt: DateTime!
updatedAt: DateTime!
}
type OrderItem {
id: ID!
quantity: Int!
unitPrice: Decimal!
totalPrice: Decimal!
product: Product!
}
type Product {
id: ID!
name: String!
description: String
price: Decimal!
inStock: Boolean!
category: Category!
}
type Category {
id: ID!
name: String!
slug: String!
parent: Category
children: [Category!]!
products: ProductConnection!
}
# Pagination
type UserConnection {
edges: [UserEdge!]!
pageInfo: PageInfo!
totalCount: Int!
}
type UserEdge {
node: User!
cursor: String!
}
type PageInfo {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
startCursor: String
endCursor: String
}
# Input types
input CreateUserInput {
email: String!
name: String!
role: UserRole = VIEWER
metadata: JSON
}
input UpdateUserInput {
email: String
name: String
role: UserRole
metadata: JSON
}
input UserFilterInput {
role: UserRole
search: String
createdAfter: DateTime
createdBefore: DateTime
}
input OrderByInput {
field: OrderSortField!
direction: SortDirection = ASC
}
enum OrderSortField {
CREATED_AT
UPDATED_AT
TOTAL
}
enum SortDirection {
ASC
DESC
}
```
### 2.2 Complete Query/Mutation Examples
```graphql
# Query with nested relations and pagination
query GetUserWithOrders($userId: ID!, $orderLimit: Int = 10) {
user(id: $userId) {
id
email
name
role
manager {
id
name
email
}
orders(first: $orderLimit, after: null, sort: [{ field: CREATED_AT, direction: DESC }]) {
edges {
node {
id
status
total
currency
createdAt
items {
id
quantity
product {
id
name
}
}
}
cursor
}
pageInfo {
hasNextPage
endCursor
}
}
}
}
# Variables:
{
"userId": "usr_abc123",
"orderLimit": 20
}
# Mutation with input and error handling
mutation CreateOrder($input: CreateOrderInput!) {
createOrder(input: $input) {
order {
id
status
total
items {
id
quantity
product {
id
name
}
}
}
user {
id
email
loyaltyPoints
}
errors {
field
message
code
}
}
}
# Input:
{
"input": {
"userId": "usr_abc123",
"items": [
{ "productId": "prod_xyz", "quantity": 2 },
{ "productId": "prod_abc", "quantity": 1 }
],
"shippingAddress": {
"street": "123 Main St",
"city": "New York",
"state": "NY",
"zip": "10001",
"country": "US"
}
}
}
# Response:
{
"data": {
"createOrder": {
"order": {
"id": "ord_123",
"status": "PENDING",
"total": "149.99",
"items": [
{
"id": "item_1",
"quantity": 2,
"product": { "id": "prod_xyz", "name": "Widget Pro" }
}
]
},
"user": {
"id": "usr_abc123",
"email": "user@example.com",
"loyaltyPoints": 150
},
"errors": null
}
}
}
```
### 2.3 DataLoader Pattern (N+1 Prevention)
```python
# DataLoader: Batch and cache database queries to prevent N+1
from dataloader import DataLoader
from functools import cached_property
class UserLoader(DataLoader):
@cached_property
def batch_load_fn(self):
async def batch_load(ids):
users = await User.query.where(User.id.in_(ids)).fetch_all()
return [next((u for u in users if u.id == id), None) for id in ids]
return batch_load
class OrderLoader(DataLoader):
@cached_property
def batch_load_fn(self):
async def batch_load(user_ids):
orders = await Order.query.where(Order.user_id.in_(user_ids)).fetch_all()
# Group by user_id
orders_by_user = {}
for order in orders:
if order.user_id not in orders_by_user:
orders_by_user[order.user_id] = []
orders_by_user[order.user_id].append(order)
return [orders_by_user.get(uid, []) for uid in user_ids]
return batch_load
# Usage in resolver
class UserType:
@staticmethod
async def resolve_orders(user, info):
loader = info.context.loaders.order_loader
return await loader.load(user.id)
```
### 2.4 GraphQL Error Handling
```python
# Custom error types
class GraphQLError(Exception):
def __init__(self, message, code, field=None, details=None):
self.message = message
self.code = code
self.field = field
self.details = details or {}
# Union type for errors
class Error:
pass
class ValidationError(Error):
field: str
message: str
class NotFoundError(Error):
message: str
class UnauthorizedError(Error):
message: str
type CreateOrderResult {
order: Order
errors: [ValidationError!]
}
# Use in mutation
async def resolve_create_order(_, info, input):
errors = []
# Validate input
if not input.get('userId'):
errors.append({'field': 'userId', 'message': 'Required'})
# Check product availability
for item in input.get('items', []):
product = await get_product(item.productId)
if not product:
errors.append({
'field': f'items.{item.productId}',
'message': 'Product not found'
})
if errors:
return {'order': None, 'errors': errors}
# Create order
order = await order_service.create(input)
return {'order': order, 'errors': None}
```
---
## 3. gRPC & Protocol Buffers
### 3.1 Proto Schema Design
```protobuf
// user_service.proto
syntax = "proto3";
package user.v1;
import "google/protobuf/timestamp.proto";
import "google/protobuf/field_mask.proto";
import "google/protobuf/empty.proto";
import "validate/validate.proto";
option go_package = "github.com/example/user/v1;userpb";
// Service definition
service UserService {
// Unary RPC
rpc GetUser(GetUserRequest) returns (User);
// Server streaming
rpc ListUsers(ListUsersRequest) returns (stream User);
// Client streaming
rpc CreateUsers(stream CreateUserRequest) returns (CreateUsersResponse);
// Bidirectional streaming
rpc StreamUserUpdates(StreamUserUpdatesRequest) returns (stream User);
// Batch operations
rpc BatchGetUsers(BatchGetUsersRequest) returns (BatchGetUsersResponse);
}
message User {
string id = 1 [(validate.rules).string = {
min_len: 3,
max_len: 50,
pattern: "^usr_[a-zA-Z0-9]+$"
}];
string email = 2 [
(validate.rules).string.email = true,
(validate.rules).string.ignore_empty = false
];
string name = 3 [(validate.rules).string = {
min_len: 1,
max_len: 200
}];
UserRole role = 4 [(validate.rules).enum.defined_only = true];
map<string, string> metadata = 5;
google.protobuf.Timestamp created_at = 6;
google.protobuf.Timestamp updated_at = 7;
}
enum UserRole {
USER_ROLE_UNSPECIFIED = 0;
USER_ROLE_VIEWER = 1;
USER_ROLE_ENGINEER = 2;
USER_ROLE_MANAGER = 3;
USER_ROLE_ADMIN = 4;
}
// Request/Response messages
message GetUserRequest {
string id = 1;
oneof identifier {
string user_id = 2;
string email = 3;
}
// Field selection
google.protobuf.FieldMask field_mask = 4;
}
message ListUsersRequest {
int32 page_size = 1 [(validate.rules).int32 = {
gte: 1,
lte: 100
}];
string page_token = 2;
string filter = 3 [(validate.rules).string.max_len = 500];
bool include_deleted = 4;
// Sorting
message OrderBy {
string field = 1;
bool descending = 2;
}
repeated OrderBy order_by = 5;
}
message ListUsersResponse {
repeated User users = 1;
string next_page_token = 2;
int32 total_size = 3;
}
message CreateUserRequest {
string email = 1 [(validate.rules).string.email = true];
string name = 2 [(validate.rules).string.min_len = 1];
UserRole role = 3;
map<string, string> metadata = 4;
}
message CreateUsersResponse {
message CreateResult {
User user = 1;
string error = 2;
}
repeated CreateResult results = 1;
int32 success_count = 2;
int32 failure_count = 3;
}
message BatchGetUsersRequest {
repeated string ids = 1 [(validate.rules).repeated.max_items = 100];
}
message BatchGetUsersResponse {
map<string, User> users = 1;
repeated string not_found = 2;
}
message StreamUserUpdatesRequest {
repeated string user_ids = 1;
}
```
### 3.2 gRPC Streaming Patterns
```python
# Server streaming: GetUserOrders
async def stream_user_orders(request, context):
"""Stream orders for a user."""
user_id = request.user_id
async for order in order_service.stream_orders(user_id):
yield order
# Check for cancellation
if context.cancelled():
return
# Client streaming: CreateUsers
async def create_users(stub, user_requests):
"""Send multiple user creation requests."""
async def request_generator():
for user_data in user_requests:
yield user_data
# Simulate delay between requests
await asyncio.sleep(0.1)
response = await stub.CreateUsers(request_generator())
return response
# Bidirectional streaming: StreamUserUpdates
async def stream_user_updates(stub, user_ids):
"""Real-time user update stream with subscription management."""
async def request_generator():
for user_id in user_ids:
yield StreamUserUpdatesRequest(user_id=user_id)
await asyncio.sleep(30) # Heartbeat
responses = stub.StreamUserUpdates(request_generator())
async for response in responses:
if response.HasField('update'):
print(f"User update: {response.update}")
elif response.HasField('delete'):
print(f"User deleted: {response.delete}")
```
### 3.3 gRPC Error Handling
```python
from grpc import StatusCode
from grpc StatusError
class GrpcError(Exception):
def __init__(self, code, message, details=None):
self.code = code
self.message = message
self.details = details or {}
# Server-side error raising
async def get_user(request, context):
user = await user_service.get_user(request.id)
if not user:
context.abort(
StatusCode.NOT_FOUND,
f"User {request.id} not found"
)
if not user.active:
context.abort(
StatusCode.FAILED_PRECONDITION,
"User account is inactive",
details=[{"type": "user_inactive", "user_id": request.id}]
)
return user
# Client-side error handling
try:
response = await stub.GetUser(request)
except grpc.RpcError as e:
if e.code() == StatusCode.NOT_FOUND:
logger.warning(f"User not found: {e.details()}")
elif e.code() == StatusCode.UNAUTHENTICATED:
# Re-authenticate and retry
await refresh_token()
response = await stub.GetUser(request)
elif e.code() == StatusCode.DEADLINE_EXCEEDED:
logger.error(f"Request timed out: {e.details()}")
else:
raise
```
---
## 4. API Versioning
### 4.1 Versioning Strategies
```yaml
# Strategy 1: URL Path Versioning (Most common)
GET /v1/users
GET /v2/users
# Pros: Easy to route, visible in logs
# Cons: URL changes, more complex routing
# Strategy 2: Header Versioning
GET /users
Accept: application/vnd.example.v2+json
API-Version: 2024-01-01
# Pros: Clean URLs
# Cons: Hidden, harder to test
# Strategy 3: Query Parameter
GET /users?version=2
# Pros: Easy to add
# Cons: Clutters URLs, caching issues
# Recommended: URL Path + Header for deprecation
# URL for routing, Header for fine-grained control
```
### 4.2 Deprecation Policy
```yaml
# Minimum version support: 2 versions active
# Deprecation timeline:
# - Announce deprecation: 6 months before sunset
# - Maintain old version: Minimum 12 months
# - Sunset old version: After new version stable
# Deprecation headers:
Deprecation: true
Sunset: Sat, 31 Dec 2024 23:59:59 GMT
Link: <https://api.example.com/docs/v2>; rel="deprecation"; type="text/html"
X-API-Deprecated: true
X-API-Sunset-Date: 2024-12-31
# Error response for deprecated API:
{
"error": {
"code": "DEPRECATED_VERSION",
"message": "API version v1 is deprecated",
"details": {
"sunset_date": "2024-12-31",
"migration_guide": "https://api.example.com/docs/migration/v1-to-v2"
}
}
}
```
---
## 5. Authentication & Authorization Headers
### 5.1 Standard Auth Headers
```http
# Bearer Token (JWT, OAuth)
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
# Basic Auth (rarely used for APIs)
Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=
# API Key
X-API-Key: sk_live_abc123def456
# OR
Authorization: ApiKey sk_live_abc123def456
# Mutual TLS (no header, uses client cert)
```
### 5.2 Custom Headers Convention
```http
# Request tracing
X-Request-ID: f47ac10b-58cc-4372-a567-0e02b2c3d479
X-Correlation-ID: abc123
X-Forwarded-For: 203.0.113.195, 70.41.3.18, 150.172.238.178
X-Real-IP: 203.0.113.195
# Feature flags / context
X-Tenant-ID: tenant_abc123
X-Feature-Dark-Mode: true
X-Preferred-Language: en-US
# Rate limiting (response)
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
X-RateLimit-Reset: 1706703600
Retry-After: 60
# Pagination
X-Total-Count: 1500
X-Page-Limit: 20
X-Page-Offset: 0
```
---
## 6. CORS Configuration
### 6.1 CORS Headers
```http
# Response headers for CORS
Access-Control-Allow-Origin: https://app.example.com
# OR for multiple origins (must validate in application):
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization, X-Request-ID, X-Correlation-ID
Access-Control-Expose-Headers: X-Request-ID, X-RateLimit-*
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 86400 # 24 hours, cache preflight
# Preflight request (OPTIONS)
OPTIONS /v1/users HTTP/1.1
Origin: https://app.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type, Authorization
```
---
## 7. API Security Checklist
```yaml
# Authentication
- [ ] Require authentication for all non-public endpoints
- [ ] Validate tokens on every request
- [ ] Use short-lived access tokens (15-60 min)
- [ ] Implement refresh token rotation
- [ ] Support API key rotation
# Authorization
- [ ] Check permissions on every request
- [ ] Use least-privilege scopes
- [ ] Implement resource-level access control
- [ ] Log all authorization failures
# Input Validation
- [ ] Validate request body against schema
- [ ] Sanitize all string inputs
- [ ] Limit request body size
- [ ] Validate content-type header
- [ ] Check for SQL injection in query params
# Rate Limiting
- [ ] Implement per-user rate limits
- [ ] Implement per-IP rate limits for unauthenticated
- [ ] Return 429 with Retry-After header
- [ ] Consider burst limits
# Security Headers
- [ ] Content-Security-Policy (if serving HTML)
- [ ] X-Content-Type-Options: nosniff
- [ ] X-Frame-Options: DENY
- [ ] Strict-Transport-Security (HSTS)
- [ ] X-XSS-Protection (legacy browsers)
# Logging & Monitoring
- [ ] Log all authentication failures
- [ ] Log all authorization failures
- [ ] Log suspicious activity (unusual patterns)
- [ ] Alert on rate limit hits
- [ ] Alert on error rate spikes
```
---
## 8. API Design Anti-Patterns
```yaml
# ❌ Chasing the own tail (circular dependency)
# API calls itself through an alias
# User A -> /users -> /users
GET /users
Response: { "aliases": ["/users"] }
# ❌ Random batching
# Batch endpoint that does unrelated operations
POST /api/batch
Body: { "operations": [
{ "op": "get_user", "id": "123" },
{ "op": "delete_order", "id": "456" }
]}
# Should be separate calls or use GraphQL
# ❌ Version in body
POST /api/users
Body: { "version": "2.0", "data": {...} }
# ❌ Wrong HTTP status codes
# 200 for errors
# 500 for validation errors
# 404 for authorization (should be 403)
# ❌ Nested resources too deep
# Bad: /orgs/{org}/teams/{team}/members/{member}/roles/{role}
# Better: /members/{member}?include=roles
# ❌ Inconsistent naming
# /getUser, /list_users, /fetchUserOrders, /userList
# Should all use same convention: GET /users, GET /users/{id}, GET /users/{id}/orders
# ❌ Sensitive data in URLs or logs
# GET /users/123?token=xyz
# Authorization header is better (not logged by default)
# ❌ No pagination on large collections
# Returning 100,000 users in one response
# Must implement pagination
```
---
## Links
### Core Router
- `core/DECAPOD.md` - **Router and navigation charter (START HERE)**
- `core/ENGINEERING_EXCELLENCE.md` - Engineering standards
### Architecture (This Section)
- `architecture/WEB.md` - Web API patterns
- `architecture/AUTH.md` - Authentication patterns
- `architecture/MESSAGING.md` - Async API patterns
- `architecture/KUBERNETES.md` - API gateway in K8s
### Authority (Constitution Layer)
- `specs/INTENT.md` - **Methodology contract (READ FIRST)**
- `specs/SYSTEM.md` - System definition and authority doctrine
- `specs/SECURITY.md` - Security doctrine
### Interface Contracts
- `interfaces/CLAIMS.md` - Promises ledger
- `interfaces/CONTROL_PLANE.md` - Agent sequencing patterns
- `interfaces/DOC_RULES.md` - Doc compilation rules
### Methodology
- `methodology/ARCHITECTURE.md` - Architecture decision methodology
- `methodology/TESTING.md` - API testing methodology
---
## Version History
When agents design APIs:
1. Follow existing patterns in the codebase
2. Document all endpoints
3. Include OpenAPI specs in PR
4. Add integration tests for critical paths
---
## Links
### Related Architecture
- [WEB](WEB.md) - Web architecture
- [SECURITY](SECURITY.md) - API security
- [GRAPHQL](GRAPHQL.md) - GraphQL patterns
- [GRPC](GRPC.md) - gRPC patterns
### Parent Docs
- [DECAPOD](../core/DECAPOD.md) - Router and navigation charter
- [INTERFACES](../core/INTERFACES.md) - Interface contracts
- [INTENT](../specs/INTENT.md) - Intent specification
| 1.0 | 2024-01-15 | Expanded comprehensive API design reference |