| name | api-design |
| description | Design resource-oriented APIs following patterns from "API Design Patterns" by JJ Geewax (Google) |
API Design Patterns Skill
This skill helps you design consistent, scalable, and flexible APIs following the patterns from "API Design Patterns" by JJ Geewax and Google's API Improvement Proposals (AIP).
Additional Resources
- patterns-reference.md - Deep dives into each pattern with trade-offs and implementation details
- templates/openapi-template.yaml - OpenAPI 3.1 template following these patterns
- examples/task-management-api.yaml - Complete example API design
Core Philosophy
Resource-Oriented Design: APIs are built around resources (nouns), not actions (verbs). A small set of standard methods operate on these resources.
"The fundamental building blocks of an API are individually-named resources and the relationships and hierarchy that exist between them."
Part 1: Design Principles
1.1 Naming Conventions
Resources (Nouns)
- Use plural nouns for collections:
/users, /orders, /products
- Use singular nouns for singletons:
/users/{id}/profile
- Use lowercase with hyphens:
/user-accounts not /userAccounts
- Be consistent and predictable across the entire API
Fields
- Use
snake_case for JSON field names: created_at, user_id
- Use clear, unambiguous names:
email_address not just email
- Avoid abbreviations:
configuration not config
Methods
- Standard methods use HTTP verbs (GET, POST, PUT, PATCH, DELETE)
- Custom methods use POST with verb suffix:
POST /orders/{id}:cancel
1.2 Resource Scope and Hierarchy
Hierarchy Rules
- Model resources as trees with parent-child relationships
- Maximum recommended depth: 3-4 levels
- Resources must form a Directed Acyclic Graph (no circular references)
Example Structure
/projects/{project_id}
/projects/{project_id}/databases/{database_id}
/projects/{project_id}/databases/{database_id}/tables/{table_id}
Resource Names
- Full resource name:
projects/my-project/databases/main/tables/users
- Relative resource name:
databases/main/tables/users
1.3 Data Types and Defaults
Standard Field Types
| Type | Format | Example |
|---|
| Timestamps | RFC 3339 | 2024-01-15T10:30:00Z |
| Durations | Seconds with 's' suffix | 3600s |
| Money | Object with currency | {"amount": "10.00", "currency": "USD"} |
| Bytes | Base64 encoded | SGVsbG8gV29ybGQ= |
Default Values
- Booleans default to
false
- Numbers default to
0
- Strings default to empty string
""
- Arrays default to empty array
[]
- Never use
null as a meaningful default
Part 2: Fundamentals
2.1 Resource Identification
Resource IDs
- Use opaque, unique identifiers: UUIDs or custom IDs
- IDs should be URL-safe: alphanumeric, hyphens, underscores
- Consider client-provided IDs for idempotency
{
"name": "projects/my-project/users/abc123",
"id": "abc123",
"display_name": "John Doe"
}
2.2 Standard Methods
| Method | HTTP | Request Body | Response | Idempotent |
|---|
| List | GET | None | Collection | Yes |
| Get | GET | None | Resource | Yes |
| Create | POST | Resource | Resource | No* |
| Update | PUT/PATCH | Resource | Resource | Yes |
| Delete | DELETE | None | Empty | Yes |
*Create can be idempotent with client-provided IDs
List Method
GET /users?page_size=25&page_token=xyz
Response:
{
"users": [...],
"next_page_token": "abc"
}
Get Method
GET /users/123
Create Method
POST /users
Content-Type: application/json
{
"display_name": "Jane Doe",
"email": "jane@example.com"
}
Update Method (Full)
PUT /users/123
Content-Type: application/json
{
"display_name": "Jane Smith",
"email": "jane.smith@example.com"
}
Delete Method
DELETE /users/123
2.3 Partial Updates and Retrievals
Partial Update (PATCH)
Use field masks to specify which fields to update:
PATCH /users/123?update_mask=display_name,email
Content-Type: application/json
{
"display_name": "New Name",
"email": "new@example.com"
}
Partial Retrieval
Use field masks to limit response fields:
GET /users/123?read_mask=display_name,email
2.4 Custom Methods
For operations that don't map to standard CRUD:
POST /orders/123:cancel
POST /documents/456:publish
POST /emails/789:send
POST /users/123:deactivate
Custom Method Rules
- Use POST (sometimes GET for read-only)
- Append colon + verb to resource name
- Keep the set of custom methods small
- Prefer standard methods when possible
2.5 Long-Running Operations
For operations that take significant time:
Initial Request
POST /databases/123:backup
Response (Operation)
{
"name": "operations/backup-xyz",
"done": false,
"metadata": {
"@type": "type.googleapis.com/BackupMetadata",
"progress_percent": 0
}
}
Poll for Status
GET /operations/backup-xyz
Completed Response
{
"name": "operations/backup-xyz",
"done": true,
"response": {
"@type": "type.googleapis.com/Backup",
"name": "backups/backup-123"
}
}
2.6 Rerunnable Jobs
For repeated scheduled operations:
{
"name": "jobs/nightly-backup",
"schedule": "0 0 * * *",
"job_config": {
"backup_type": "FULL"
},
"last_run": {...},
"next_run_time": "2024-01-16T00:00:00Z"
}
Execute immediately
POST /jobs/nightly-backup:run
Part 3: Resource Relationships
3.1 Singleton Sub-Resources
For resources that exist exactly once per parent:
/users/123/profile (not /users/123/profiles)
/users/123/settings
/projects/456/config
3.2 Cross References
Reference resources by their full name:
{
"name": "orders/123",
"customer": "users/456",
"items": [
{"product": "products/789", "quantity": 2}
]
}
3.3 Association Resources
For many-to-many relationships with metadata:
/groups/123/memberships/456
{
"name": "groups/123/memberships/456",
"user": "users/789",
"role": "ADMIN",
"joined_at": "2024-01-01T00:00:00Z"
}
3.4 Add/Remove Custom Methods
For simple many-to-many without metadata:
POST /groups/123:addMember
{
"user": "users/789"
}
POST /groups/123:removeMember
{
"user": "users/789"
}
3.5 Polymorphism
Handle dynamic types with discriminators:
{
"name": "notifications/123",
"type": "EMAIL",
"email_config": {
"recipient": "user@example.com",
"subject": "Hello"
}
}
Or using @type field:
{
"@type": "type.googleapis.com/EmailNotification",
"recipient": "user@example.com"
}
Part 4: Collective Operations
4.1 Copy and Move
POST /files/123:copy
{
"destination_parent": "folders/456"
}
POST /files/123:move
{
"destination_parent": "folders/789"
}
4.2 Batch Operations
Batch Get
GET /users:batchGet?ids=123,456,789
Batch Create/Update/Delete
POST /users:batchCreate
{
"requests": [
{"user": {"display_name": "User 1"}},
{"user": {"display_name": "User 2"}}
]
}
Atomicity: Batch operations should be atomic (all succeed or all fail).
4.3 Criteria-Based Deletion
Delete multiple resources matching criteria:
POST /logs:purge
{
"filter": "timestamp < '2023-01-01'"
}
Returns a long-running operation for tracking.
4.4 Anonymous Writes
For high-volume data ingestion without addressing:
POST /metrics:write
{
"entries": [
{"name": "cpu_usage", "value": 0.75},
{"name": "memory_usage", "value": 0.60}
]
}
4.5 Pagination
Request
GET /users?page_size=25&page_token=eyJvZmZzZXQiOjI1fQ
Response
{
"users": [...],
"next_page_token": "eyJvZmZzZXQiOjUwfQ",
"total_size": 150
}
Rules
- Use opaque page tokens (not page numbers)
- Default page size: 25-100
- Maximum page size: 1000
- Include
total_size when feasible
4.6 Filtering
GET /users?filter=status="ACTIVE" AND created_at>"2024-01-01"
Filter Syntax
- Comparisons:
=, !=, <, >, <=, >=
- Logical:
AND, OR, NOT
- Functions:
has(), contains()
- Wildcards:
* for partial matching
4.7 Importing and Exporting
Export
POST /databases/123:export
{
"destination": "gs://bucket/exports/",
"format": "CSV"
}
Import
POST /databases/123:import
{
"source": "gs://bucket/data.csv",
"format": "CSV"
}
Both return long-running operations.
Part 5: Safety and Security
5.1 Versioning and Compatibility
URL Versioning
/v1/users
/v2/users
Header Versioning
GET /users
API-Version: 2024-01-01
Compatibility Rules
- Adding fields is backward compatible
- Removing fields is NOT backward compatible
- Changing field types is NOT backward compatible
- Use deprecation notices before removal
5.2 Soft Deletion
{
"name": "users/123",
"display_name": "John Doe",
"delete_time": "2024-01-15T10:00:00Z",
"expire_time": "2024-02-15T10:00:00Z"
}
Restore
POST /users/123:undelete
5.3 Request Deduplication
Use client-provided request IDs:
POST /orders
X-Request-Id: unique-request-123
Content-Type: application/json
{...}
Server stores request ID and returns cached response on retry.
5.4 Request Validation
Dry-run mode for validation without execution:
POST /users?validate_only=true
{
"display_name": "Test User"
}
Returns validation errors without creating the resource.
5.5 Resource Revisions
Track change history:
GET /documents/123/revisions
GET /documents/123@revision=5
{
"name": "documents/123",
"revision_id": "5",
"revision_create_time": "2024-01-15T10:00:00Z",
"content": "..."
}
5.6 Request Retrial
Idempotency Keys
POST /payments
Idempotency-Key: payment-abc-123
Retry Strategies
- Exponential backoff with jitter
- Maximum retry attempts
- Circuit breaker pattern
5.7 Request Authentication
API Keys
GET /users
X-API-Key: your-api-key
Bearer Tokens (OAuth 2.0)
GET /users
Authorization: Bearer eyJhbGciOiJSUzI1NiIs...
Request Signing
For sensitive operations, sign requests with timestamps to prevent replay attacks.
Quick Reference: HTTP Status Codes
| Code | Meaning | When to Use |
|---|
| 200 | OK | Successful GET, PUT, PATCH |
| 201 | Created | Successful POST creating resource |
| 204 | No Content | Successful DELETE |
| 400 | Bad Request | Invalid request syntax |
| 401 | Unauthorized | Missing/invalid authentication |
| 403 | Forbidden | Valid auth but no permission |
| 404 | Not Found | Resource doesn't exist |
| 409 | Conflict | Resource state conflict |
| 429 | Too Many Requests | Rate limit exceeded |
| 500 | Internal Error | Server-side failure |
| 503 | Service Unavailable | Temporary overload |
Error Response Format
{
"error": {
"code": 400,
"status": "INVALID_ARGUMENT",
"message": "Display name is required",
"details": [
{
"@type": "type.googleapis.com/FieldViolation",
"field": "display_name",
"description": "Field is required"
}
]
}
}
Checklist: API Design Review
Usage Examples
Design a new API
/api-design
Help me design an API for a task management system with users, projects, and tasks.
Review existing API
/api-design
Review this API endpoint design and suggest improvements following best practices.
Add a feature to existing API
/api-design
How should I add soft deletion to my existing users API?
References