Awesome-omni-skill api-design
Design REST and GraphQL APIs. Covers endpoint naming, versioning, OpenAPI 3.1 specs, pagination, error handling, and auth patterns.
git clone https://github.com/diegosouzapw/awesome-omni-skill
T=$(mktemp -d) && git clone --depth=1 https://github.com/diegosouzapw/awesome-omni-skill "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/development/api-design-trevoredris" ~/.claude/skills/diegosouzapw-awesome-omni-skill-api-design-ffac3f && rm -rf "$T"
skills/development/api-design-trevoredris/SKILL.mdAPI Design
Quick Start
Just describe your domain:
design an API for an e-commerce platform with products, orders, and customers
You'll get a complete endpoint design and OpenAPI snippet like:
paths: /v1/products: get: summary: List products parameters: - $ref: '#/components/parameters/PageLimit' - $ref: '#/components/parameters/PageCursor' responses: '200': description: Paginated product list content: application/json: schema: $ref: '#/components/schemas/ProductListResponse' /v1/products/{id}: get: summary: Get product by ID responses: '200': description: Product detail '404': $ref: '#/components/responses/NotFound'
What to include in your request:
- Domain or system name (e-commerce, billing, identity)
- Key resources (users, products, orders)
- Relationships (orders belong to users, orders have line items)
- Consumer context (mobile app, internal service, public API)
- Existing constraints (auth system, versioning requirements)
Triggers
| Trigger | Example |
|---|---|
| "design an API for a task management system" |
| "create endpoints for user authentication" |
| "OpenAPI spec for the billing service" |
| "GraphQL schema for a content management domain" |
| "review this API for problems" |
| "add endpoints for order fulfillment" |
| "design REST API for the notification service" |
Key Terms
| Term | Definition |
|---|---|
| REST | Representational State Transfer — stateless, resource-oriented HTTP API style |
| GraphQL | Query language for APIs with a single endpoint and client-driven data fetching |
| OpenAPI | Standard specification format for describing REST APIs (current version: 3.1) |
| Endpoint | A URL path + HTTP method combination that handles a specific operation |
| Resource | A noun representing an entity in your domain (user, order, product) |
| Schema | Formal definition of the shape and constraints of request/response data |
| Idempotency | An operation that produces the same result no matter how many times it's called |
| HATEOAS | Hypermedia As The Engine Of Application State — embedding navigable links in responses |
| Pagination | Strategy for returning large datasets in chunks (offset-based or cursor-based) |
| Rate Limiting | Restricting how often a client can call an endpoint within a time window |
Quick Reference
| Task | Approach | Key Consideration |
|---|---|---|
| New API | Start with resource identification | Model the domain, not the UI |
| Versioning | URL path for simplicity | Commit to support policy before launch |
| Pagination | Cursor-based for large/live data | Offset breaks on concurrent writes |
| Auth | Bearer token (JWT) as default | Scope to least privilege |
| Error handling | envelope | Machine-readable codes, human-readable messages |
| GraphQL vs REST | REST for CRUD, GraphQL for flexible queries | Consider client diversity and caching needs |
Process Overview
Your API Requirements | v +-----------------------------------------------------+ | Phase 1: REQUIREMENTS | | * Identify resources and relationships | | * Determine consumers (mobile, web, service-to-service) | | * Define auth model and access control needs | | * Enumerate constraints (rate limits, SLAs, quotas) | +-----------------------------------------------------+ | v +-----------------------------------------------------+ | Phase 2: DESIGN | | * Define URL structure and resource hierarchy | | * Map HTTP methods to CRUD operations | | * Design request/response schemas | | * Choose auth mechanism and scoping | +-----------------------------------------------------+ | v +-----------------------------------------------------+ | Phase 3: SPECIFY | | * Generate OpenAPI 3.1 spec (or GraphQL SDL) | | * Define error codes and Problem Details format | | * Document pagination strategy and parameters | | * Add examples for every endpoint | +-----------------------------------------------------+ | v +-----------------------------------------------------+ | Phase 4: VALIDATE | | * Run verification checklist | | * Assess breaking change risk | | * Review auth coverage | | * Confirm documentation completeness | +-----------------------------------------------------+ | v Production-Ready API Specification
Commands
| Command | When to Use | Action |
|---|---|---|
| Starting fresh | Full endpoint design + OpenAPI spec |
| Extending existing API | New resource endpoints |
| Formalizing existing design | Complete OpenAPI 3.1 document |
| GraphQL API | Type definitions + queries + mutations |
| Auditing existing API | Checklist audit + recommendations |
Workflow: Start with
design api → formalize with openapi spec → extend with add endpoints → audit with review api
Core Principles
| Principle | WHY | Implementation |
|---|---|---|
| Resource-oriented design | Resources are stable; operations on them are predictable | Nouns in URLs, verbs in HTTP methods |
| Consistent naming | Reduces cognitive load for consumers | Plural nouns, kebab-case paths, camelCase fields |
| Explicit versioning | Allows evolution without breaking clients | Version in URL path or header, committed from day one |
| Defensive schemas | Fail fast on bad input; never trust caller data | Required fields, format constraints, max lengths |
Anti-Patterns
| Avoid | Why | Instead |
|---|---|---|
Verbs in URLs (, ) | Conflates HTTP method semantics | , |
| Inconsistent pluralization | Forces clients to memorize per-resource conventions | Always plural: , , |
| No versioning on day one | First breaking change forces emergency migration | Add from the start |
Generic error responses () | Client can't distinguish or act on error | with typed codes |
| Breaking changes without deprecation | Breaks deployed clients silently | Additive changes only; header for removals |
| Nesting resources more than 2 levels deep | URLs become unmaintainable and coupling increases | yes; no |
| FLOAT for monetary values in responses | Floating-point rounding corrupts financial data | Return money as integer cents or string decimal |
| Exposing internal IDs or database row numbers | Leaks implementation, complicates migration | Use opaque IDs (UUIDs or slugs) |
Verification Checklist
After designing an API:
- All resource URLs use plural nouns
- No verbs in URL paths
- Nesting depth is 2 levels maximum
- URL paths use kebab-case for multi-word segments
- HTTP methods match semantics (GET=read, POST=create, PUT=replace, PATCH=partial, DELETE=remove)
- PUT and DELETE are idempotent
- Status codes are appropriate (201 for creation, 204 for no-content DELETE, 409 for conflict)
- Version is in URL path or header, documented from day one
- All endpoints with state mutation require auth
- Error responses use
envelope{ error: { type, message, subcode } } - Validation errors include field-level detail
- Pagination included for all collection endpoints
- Pagination response includes
,meta.total
, ormeta.cursorlinks.next - Money/currency fields use integer cents or string decimal (never float)
- Dates use ISO 8601 in UTC (
)2025-10-31T14:30:00Z - Content-Type is
on all JSON endpointsapplication/json - Rate limiting headers documented (
,X-RateLimit-Limit
)X-RateLimit-Remaining - OpenAPI spec validates without errors
- Every endpoint has at least one request/response example
- Breaking changes assessed against existing consumers
See api-design-checklist.md for the full pre-launch review checklist. See openapi-templates.md for concrete OpenAPI 3.1 spec templates. See scalar-api-docs.md for interactive API documentation setup (Go, Python, TypeScript, Rust).
<details> <summary><strong>Deep Dive: REST Endpoint Design</strong></summary>
Resource Naming
| Rule | Good | Bad |
|---|---|---|
| Plural nouns | , | , |
| kebab-case | , | , |
| No verbs | | |
| Opaque IDs | | |
URL Structure
/v1/{resource} # collection /v1/{resource}/{id} # single resource /v1/{resource}/{id}/{child} # sub-resource (max 2 levels)
Examples:
GET /v1/orders # list orders POST /v1/orders # create order GET /v1/orders/{id} # get order PUT /v1/orders/{id} # replace order PATCH /v1/orders/{id} # partial update DELETE /v1/orders/{id} # delete order GET /v1/orders/{id}/items # list items for order POST /v1/orders/{id}/items # add item to order
HTTP Method Mapping
| Method | Semantics | Idempotent | Body |
|---|---|---|---|
| GET | Read, no side effects | Yes | No |
| POST | Create, trigger action | No | Yes |
| PUT | Replace entire resource | Yes | Yes |
| PATCH | Partial update | No* | Yes |
| DELETE | Remove resource | Yes | No |
*PATCH can be made idempotent with conditional requests (
If-Match)
Status Code Reference
| Code | Meaning | When to Use |
|---|---|---|
| 200 | OK | Successful GET, PUT, PATCH |
| 201 | Created | Successful POST creating a resource |
| 204 | No Content | Successful DELETE, or PUT/PATCH with no body |
| 400 | Bad Request | Malformed request, validation failure |
| 401 | Unauthorized | Missing or invalid authentication |
| 403 | Forbidden | Authenticated but not authorized |
| 404 | Not Found | Resource does not exist |
| 409 | Conflict | Duplicate create, optimistic lock failure |
| 422 | Unprocessable Entity | Semantically invalid (business rule violation) |
| 429 | Too Many Requests | Rate limit exceeded |
| 500 | Internal Server Error | Unexpected server failure |
| 503 | Service Unavailable | Temporary outage, maintenance |
Sub-Resource Patterns
Keep sub-resources to actions that only make sense in context of the parent:
# Good: items only exist within an order GET /v1/orders/{id}/items POST /v1/orders/{id}/items # Prefer flat resources when entities stand alone GET /v1/items/{id} # better than /v1/orders/{oid}/items/{iid}
For actions that don't map cleanly to CRUD, use a verb suffix as a last resort:
</details> <details> <summary><strong>Deep Dive: Response Format</strong></summary>POST /v1/orders/{id}/cancel POST /v1/orders/{id}/fulfill POST /v1/users/{id}/verify-email
Envelope Structure
Use a consistent envelope for all responses:
{ "data": { ... }, "meta": { "requestId": "req_abc123", "timestamp": "2025-10-31T14:30:00Z" }, "links": { "self": "/v1/orders/ord_xyz", "next": "/v1/orders?cursor=eyJpZCI6MTAwfQ" }, "errors": [ ... ] }
— the primary payload (object for single, array for collection)data
— pagination cursors, request IDs, timestampsmeta
— HATEOAS navigation (self, next, prev, related actions)links
— only present on error responses (4xx/5xx)errors
HATEOAS: Links and Actions
Use the
links object for both navigation and available actions on the resource. Each key is a link relation type:
{ "data": { "id": "ord_xyz", "status": "pending", "total": 1099 }, "links": { "self": { "href": "/v1/orders/ord_xyz" }, "cancel": { "href": "/v1/orders/ord_xyz/cancel", "method": "POST" }, "items": { "href": "/v1/orders/ord_xyz/items" }, "payment": { "href": "/v1/orders/ord_xyz/pay", "method": "POST" } } }
Key principles:
- Actions are state-dependent —
only appears when the order is cancellablecancel - Include
when the action is not a GET (avoids client guessing)method - Use IANA-registered relation types where they exist (
,self
,next
,prev
)collection - Use descriptive custom relation types for domain actions (
,cancel
,approve
)payment - Do NOT use a separate
array — this is Siren format, not widely adopted. Standardactions
with method hints covers the same use case with less complexitylinks
Field Naming Convention
| Convention | When | Example |
|---|---|---|
| camelCase | JSON APIs (default) | , |
| snake_case | Python/Go backends matching language convention | , |
Pick one and apply it everywhere. Document it in your OpenAPI spec's
x-naming-convention extension.
Null Handling
- Omit optional fields that have no value rather than returning
where possiblenull - When
is semantically meaningful (explicitly unset vs absent), include it with documentationnull - Never return
for arrays — returnnull[]
Date Format
Always ISO 8601 in UTC:
"createdAt": "2025-10-31T14:30:00Z" # correct "createdAt": "10/31/2025 2:30 PM" # wrong — ambiguous and locale-dependent "createdAt": 1730382600 # avoid — requires client-side conversion
Monetary Values
{ "amount": 1099, "currency": "USD", "amountFormatted": "$10.99" }
- Store and transmit as integer cents (or smallest currency unit)
- Include currency code with every monetary field
- Optionally include a pre-formatted display string for convenience
Strategy Comparison
| Strategy | Example | Pros | Cons |
|---|---|---|---|
| URL path | | Visible, cacheable, easy to test | Pollutes URL, not REST-pure |
| Request header | | Clean URLs, REST-pure | Harder to test, requires header tooling |
| Content negotiation | | Standards-compliant | Verbose, limited browser/tool support |
| Query parameter | | Easy to test | Cache-unfriendly, easy to forget |
Recommendation
Default: URL path versioning.
- Easiest to understand, document, and test
- Works natively with all HTTP clients and proxies
- Every environment can hit a specific version directly
Use header versioning only when you have multiple clients with very different needs and want a single URL namespace.
Version Lifecycle
v1 (stable) ──────────────────────────────────► active v2 (preview) ────────► stable ─────────────────► active v1 (sunset) Sunset header added ──────► deprecated ──► removed
Timeline example:
- v1 launch:
is stable, no deprecation/v1/ - v2 launch: Announce v2, keep v1 running
- v1 sunset: Add
header to v1 responsesSunset: Sat, 31 Dec 2026 23:59:59 GMT - v1 removal: 6–12 months after Sunset header, remove v1
Backward-Compatible Changes (No Version Bump Needed)
- Adding optional request fields
- Adding new response fields
- Adding new endpoints
- Adding new enum values (if clients handle unknown values)
- Relaxing validation (e.g., increasing max length)
Breaking Changes (Require Version Bump)
- Removing or renaming fields
- Changing field types
- Removing endpoints
- Changing HTTP methods
- Tightening validation
- Changing authentication requirements
Offset-Based vs Cursor-Based
| Factor | Offset | Cursor |
|---|---|---|
| Implementation | Simple (LIMIT/OFFSET) | Moderate (encode last-seen ID) |
| Performance on large datasets | Degrades (full table scan) | Consistent (index seek) |
| Handles concurrent writes | Skips/duplicates items | Stable position |
| Supports jump to page N | Yes | No |
| Best for | Small datasets, admin UIs | Large/live datasets, feeds |
Offset Pagination Response
{ "data": [ ... ], "meta": { "total": 1842, "page": 3, "perPage": 20, "totalPages": 93 }, "links": { "self": "/v1/orders?page=3&perPage=20", "prev": "/v1/orders?page=2&perPage=20", "next": "/v1/orders?page=4&perPage=20", "first": "/v1/orders?page=1&perPage=20", "last": "/v1/orders?page=93&perPage=20" } }
Cursor Pagination Response
{ "data": [ ... ], "meta": { "hasNext": true, "hasPrev": true, "cursor": { "next": "eyJpZCI6MTAwLCJjcmVhdGVkQXQiOiIyMDI1LTEwLTMxIn0=", "prev": "eyJpZCI6ODEsImNyZWF0ZWRBdCI6IjIwMjUtMTAtMzAifQ==" } } }
Client usage:
GET /v1/orders?limit=20&after=eyJpZCI6MTAwfQ==
Cursor Implementation Pattern
Encode the sort key(s) and last-seen ID in the cursor:
{ "id": 100, "createdAt": "2025-10-31" }
Base64-encode this JSON — opaque to the client, but deterministic on the server.
Pagination Parameters
| Parameter | Offset | Cursor |
|---|---|---|
| Page number (1-indexed) | — |
/ | Items per page | Items per page |
| — | Cursor for next page |
| — | Cursor for prev page |
Always enforce a maximum
limit (e.g., 100) server-side regardless of what the client requests.
</details>
<details>
<summary><strong>Deep Dive: Error Handling</strong></summary>
Error Response Format
Standard error envelope:
{ "error": { "type": "BAD_REQUEST", "message": "Invalid email address format", "subcode": 12001 } }
| Field | Description |
|---|---|
| Machine-readable error category (SCREAMING_SNAKE_CASE) |
| Short, human-readable description of the error |
| Numeric code for programmatic handling and documentation lookup |
Validation errors add a
fields array:
{ "error": { "type": "VALIDATION_FAILED", "message": "One or more fields failed validation", "subcode": 10001, "fields": [ { "field": "quantity", "code": "INVALID_RANGE", "message": "Must be between 1 and 999" }, { "field": "productId", "code": "NOT_FOUND", "message": "Product prod_zzz does not exist" } ] } }
Request and Trace IDs
Every response (success and error) must include correlation headers:
X-Request-Id: req_abc123 # Server-generated if client omits X-Trace-Id: trace_def456 # Distributed tracing correlation Retry-After: 60 # Seconds until retry (429, 503)
- X-Request-Id: If the client sends one, echo it back. Otherwise, generate a UUID server-side. Log it with every request.
- X-Trace-Id: Propagated through all downstream service calls for distributed tracing.
- Do NOT put trace IDs in the error body. Headers are the canonical location — they're present on all responses (not just errors), are accessible to proxies/load balancers, and avoid coupling error schemas to tracing infrastructure. Clients needing to report an error to support should reference the
header value.X-Request-Id
Error Code Registry
Define typed error codes as constants in your API:
VALIDATION_FAILED 422 One or more fields are invalid 10001 RESOURCE_NOT_FOUND 404 The requested resource does not exist 10002 DUPLICATE_RESOURCE 409 A resource with this identifier exists 10003 INSUFFICIENT_FUNDS 422 Account balance is insufficient 10004 RATE_LIMIT_EXCEEDED 429 Too many requests; retry after X seconds 10005 UNAUTHORIZED 401 Authentication credentials invalid 10006 FORBIDDEN 403 Caller lacks permission for this action 10007
Document the full registry in your OpenAPI spec under
x-error-codes.
HTTP Status Decision Tree
Is the request malformed JSON? → 400 Bad Request Do the fields fail validation? → 422 Unprocessable Entity Is authentication missing or invalid? → 401 Unauthorized Is the user authenticated but denied? → 403 Forbidden Does the resource not exist? → 404 Not Found Does a conflict exist (duplicate)? → 409 Conflict Did a business rule fail? → 422 Unprocessable Entity Is the server broken? → 500 Internal Server Error Is the service temporarily unavailable? → 503 Service Unavailable
RFC 9457 Compatibility
The error format above is intentionally simpler than RFC 9457 (Problem Details). If your API must comply with RFC 9457 (e.g., for interoperability with standards-mandating clients), use:
{ "type": "https://api.example.com/errors/validation-failed", "title": "Validation Failed", "status": 422, "detail": "One or more fields failed validation.", "instance": "/v1/orders/ord_xyz" }
Set
Content-Type: application/problem+json for RFC 9457 responses.
</details>
<details>
<summary><strong>Deep Dive: Authentication and Authorization</strong></summary>
Auth Mechanism Comparison
| Mechanism | Best For | Complexity | Stateless |
|---|---|---|---|
| API Key | Server-to-server, simple integrations | Low | Yes |
| Bearer JWT | User-facing APIs, short-lived tokens | Medium | Yes |
| OAuth 2.0 + PKCE | User-delegated access, third-party integrations | High | Yes |
| Session cookies | Traditional web apps, SSR | Low | No |
Bearer Token (JWT) Pattern
Authorization: Bearer eyJhbGciOiJSUzI1NiJ9...
JWT payload convention:
{ "sub": "usr_abc123", "iss": "https://auth.example.com", "aud": "https://api.example.com", "exp": 1730386200, "iat": 1730382600, "scope": "orders:read orders:write" }
API Key Pattern
X-API-Key: sk_live_abc123xyz
- Prefix keys with environment (
,sk_live_
) for instant recognitionsk_test_ - Hash keys at rest; never store plaintext
- Issue per-integration, not per-user
- Support key rotation without downtime (accept both old and new during transition)
OAuth 2.0 Flows
| Flow | When |
|---|---|
| Authorization Code + PKCE | Web/mobile apps on behalf of a user |
| Client Credentials | Service-to-service (no user context) |
| Device Code | Devices without browsers (CLI tools, smart TVs) |
Scoping
Define scopes as
{resource}:{action} pairs:
orders:read Read orders orders:write Create and update orders orders:delete Delete orders users:read Read user profiles admin:full All access (restrict heavily)
Request minimum scopes. Validate scopes server-side on every request.
Rate Limiting Headers
Always return rate limit state in responses:
X-RateLimit-Limit: 1000 X-RateLimit-Remaining: 847 X-RateLimit-Reset: 1730383200
On 429:
</details> <details> <summary><strong>Deep Dive: OpenAPI 3.1 Specification</strong></summary>Retry-After: 47
Skeleton Template
openapi: 3.1.0 info: title: My Service API version: 1.0.0 description: | Brief description of the API's purpose and audience. contact: name: Platform Team email: platform@example.com license: name: MIT servers: - url: https://api.example.com/v1 description: Production - url: https://api-staging.example.com/v1 description: Staging security: - bearerAuth: [] paths: /resources: get: summary: List resources operationId: listResources tags: [Resources] parameters: - $ref: '#/components/parameters/Limit' - $ref: '#/components/parameters/After' responses: '200': description: Paginated resource list content: application/json: schema: $ref: '#/components/schemas/ResourceListResponse' '401': $ref: '#/components/responses/Unauthorized' components: securitySchemes: bearerAuth: type: http scheme: bearer bearerFormat: JWT apiKey: type: apiKey in: header name: X-API-Key parameters: Limit: name: limit in: query schema: type: integer minimum: 1 maximum: 100 default: 20 After: name: after in: query schema: type: string description: Cursor from previous response for next page responses: Unauthorized: description: Authentication credentials missing or invalid content: application/json: schema: $ref: '#/components/schemas/ProblemDetails' NotFound: description: Resource not found content: application/json: schema: $ref: '#/components/schemas/ProblemDetails' schemas: ProblemDetails: type: object required: [type, title, status] properties: type: type: string format: uri title: type: string status: type: integer detail: type: string instance: type: string requestId: type: string
Reusable Schema Patterns
Envelope response:
ResourceListResponse: type: object properties: data: type: array items: $ref: '#/components/schemas/Resource' meta: $ref: '#/components/schemas/PaginationMeta' links: $ref: '#/components/schemas/PaginationLinks' PaginationMeta: type: object properties: hasNext: type: boolean hasPrev: type: boolean PaginationLinks: type: object properties: self: type: string format: uri next: type: string format: uri prev: type: string format: uri
$ref
Strategy
$ref- All reusable schemas in
components/schemas - All reusable parameters in
components/parameters - All reusable responses in
components/responses - Never inline schema objects in
— alwayspaths$ref
Validation Tooling
— OpenAPI linting with configurable rulesetsspectral
— Spec validation + doc generationredocly
— Basic structural validationswagger-cli validate
Type Definitions
scalar DateTime scalar Decimal type User { id: ID! email: String! createdAt: DateTime! orders(first: Int, after: String): OrderConnection! } type Order { id: ID! status: OrderStatus! total: Decimal! currency: String! lineItems: [LineItem!]! createdAt: DateTime! } type LineItem { id: ID! product: Product! quantity: Int! unitPrice: Decimal! } enum OrderStatus { PENDING CONFIRMED SHIPPED DELIVERED CANCELLED }
Query, Mutation, Subscription Patterns
type Query { # Single resource by ID order(id: ID!): Order # Paginated collection (Relay-style) orders(first: Int, after: String, filter: OrderFilter): OrderConnection! # Current user context me: User } type Mutation { # Returns the created/mutated resource createOrder(input: CreateOrderInput!): CreateOrderPayload! cancelOrder(id: ID!): CancelOrderPayload! } type Subscription { # Subscribe to events for a specific resource orderStatusChanged(orderId: ID!): Order! }
Input Types
input CreateOrderInput { lineItems: [LineItemInput!]! shippingAddressId: ID! couponCode: String } input LineItemInput { productId: ID! quantity: Int! }
Relay-Style Pagination (Connection Pattern)
type OrderConnection { edges: [OrderEdge!]! pageInfo: PageInfo! } type OrderEdge { cursor: String! node: Order! } type PageInfo { hasNextPage: Boolean! hasPreviousPage: Boolean! startCursor: String endCursor: String }
Error Handling: Errors Array vs Union Types
Errors array (GraphQL default):
{ "data": { "createOrder": null }, "errors": [{ "message": "Product not found", "path": ["createOrder"] }] }
Union result types (recommended for mutations):
union CreateOrderResult = Order | ValidationError | NotFoundError type Mutation { createOrder(input: CreateOrderInput!): CreateOrderResult! }
This approach makes errors first-class, typed, and queryable by clients.
Complexity and Depth Limiting
Prevent abuse by enforcing query complexity limits:
- Max depth: 7–10 levels (reject deeply nested queries)
- Max complexity: Assign cost to each field; reject queries over budget
- Introspection: Disable in production for public APIs
Libraries:
graphql-depth-limit, graphql-query-complexity
</details>
<details>
<summary><strong>Deep Dive: Breaking Change Management</strong></summary>
What Counts as Breaking
| Change | Breaking? | Notes |
|---|---|---|
| Add optional request field | No | Clients ignore unknown fields |
| Add response field | No | Clients ignore unknown fields |
| Remove request field | Yes | Existing clients may send it |
| Remove response field | Yes | Existing clients may read it |
| Rename field | Yes | Equivalent to remove + add |
| Change field type | Yes | Parsing will fail |
| Tighten validation | Yes | Previously valid requests rejected |
| Loosen validation | No | More permissive |
| Remove endpoint | Yes | 404 where clients expect 200 |
| Change auth requirement | Yes | Auth failures for existing clients |
| Add required request field | Yes | Existing requests missing the field |
| Add new enum value | Usually no* | Depends on client handling unknown values |
*Clients should be designed to handle unknown enum values gracefully.
Additive-Only Evolution
Design for additive-only changes within a major version:
- Add new optional fields to requests
- Add new fields to responses
- Add new endpoints
- Add new optional query parameters
- Add new enum values (with consumer guidance on handling unknowns)
Deprecation Process
Step 1: Mark deprecated in OpenAPI spec
/v1/orders/{id}/cancel: post: deprecated: true description: "Deprecated: Use PATCH /v1/orders/{id} with status=cancelled instead."
Step 2: Add Sunset header to responses
Sunset: Sat, 31 Dec 2026 23:59:59 GMT Deprecation: true Link: <https://api.example.com/docs/migrations/v1-to-v2>; rel="deprecation"
Step 3: Notify consumers
- Email registered API consumers
- Post in developer changelog
- Add in-response warning in
arraymeta.warnings
Step 4: Enforce removal timeline
- Minimum 6 months from Sunset header to removal for external APIs
- Minimum 2 sprints for internal service-to-service APIs
Migration Guide Template
</details>## Migrating from v1 to v2: Orders ### What changed - `GET /v1/orders` response: `items` renamed to `lineItems` - `POST /v1/orders` now requires `shippingAddressId` - Error format changed from `{"error": "..."}` to `{ error: { type, message, subcode } }` ### Migration steps 1. Update `items` → `lineItems` in your order parsing code 2. Add `shippingAddressId` to order creation requests 3. Update error handling to read `type` and `detail` from responses ### Timeline - v1 sunset: 2026-12-31 - v1 removal: 2027-03-31
Extension Points
- Framework-Specific Patterns: Express middleware, FastAPI dependency injection, Go
routing conventionsnet/http - API Gateway Integration: Kong, AWS API Gateway, Apigee — rate limiting, auth delegation, routing
- Contract Testing: Pact for consumer-driven contract tests; validate against OpenAPI spec with
dredd - SDK Generation:
for client SDKs from OpenAPI spec;openapi-generator
for TypeScript typesgraphql-code-generator - Spec Validation Automation:
rulesets in CI,spectral
for doc quality gatesredocly lint - API Mocking:
for mock servers from OpenAPI spec; enables parallel frontend/backend developmentprism - Interactive Documentation: Scalar for modern, themeable API reference UI — see references/scalar-api-docs.md