Awesome-omni-skill api-design

Design REST and GraphQL APIs. Covers endpoint naming, versioning, OpenAPI 3.1 specs, pagination, error handling, and auth patterns.

install
source · Clone the upstream repo
git clone https://github.com/diegosouzapw/awesome-omni-skill
Claude Code · Install into ~/.claude/skills/
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"
manifest: skills/development/api-design-trevoredris/SKILL.md
source content

API 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

TriggerExample
design an API for
"design an API for a task management system"
create endpoints for
"create endpoints for user authentication"
OpenAPI spec for
"OpenAPI spec for the billing service"
GraphQL schema for
"GraphQL schema for a content management domain"
review api
"review this API for problems"
add endpoints for
"add endpoints for order fulfillment"
design REST API
"design REST API for the notification service"

Key Terms

TermDefinition
RESTRepresentational State Transfer — stateless, resource-oriented HTTP API style
GraphQLQuery language for APIs with a single endpoint and client-driven data fetching
OpenAPIStandard specification format for describing REST APIs (current version: 3.1)
EndpointA URL path + HTTP method combination that handles a specific operation
ResourceA noun representing an entity in your domain (user, order, product)
SchemaFormal definition of the shape and constraints of request/response data
IdempotencyAn operation that produces the same result no matter how many times it's called
HATEOASHypermedia As The Engine Of Application State — embedding navigable links in responses
PaginationStrategy for returning large datasets in chunks (offset-based or cursor-based)
Rate LimitingRestricting how often a client can call an endpoint within a time window

Quick Reference

TaskApproachKey Consideration
New APIStart with resource identificationModel the domain, not the UI
VersioningURL path
/v1/
for simplicity
Commit to support policy before launch
PaginationCursor-based for large/live dataOffset breaks on concurrent writes
AuthBearer token (JWT) as defaultScope to least privilege
Error handling
{ error: { type, message, subcode } }
envelope
Machine-readable codes, human-readable messages
GraphQL vs RESTREST for CRUD, GraphQL for flexible queriesConsider 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

CommandWhen to UseAction
design api for {domain}
Starting freshFull endpoint design + OpenAPI spec
add endpoints for {resource}
Extending existing APINew resource endpoints
openapi spec for {api}
Formalizing existing designComplete OpenAPI 3.1 document
graphql schema for {domain}
GraphQL APIType definitions + queries + mutations
review api
Auditing existing APIChecklist audit + recommendations

Workflow: Start with

design api
→ formalize with
openapi spec
→ extend with
add endpoints
→ audit with
review api


Core Principles

PrincipleWHYImplementation
Resource-oriented designResources are stable; operations on them are predictableNouns in URLs, verbs in HTTP methods
Consistent namingReduces cognitive load for consumersPlural nouns, kebab-case paths, camelCase fields
Explicit versioningAllows evolution without breaking clientsVersion in URL path or header, committed from day one
Defensive schemasFail fast on bad input; never trust caller dataRequired fields, format constraints, max lengths

Anti-Patterns

AvoidWhyInstead
Verbs in URLs (
/getUser
,
/createOrder
)
Conflates HTTP method semantics
GET /users/{id}
,
POST /orders
Inconsistent pluralizationForces clients to memorize per-resource conventionsAlways plural:
/users
,
/orders
,
/products
No versioning on day oneFirst breaking change forces emergency migrationAdd
/v1/
from the start
Generic error responses (
{"error": "failed"}
)
Client can't distinguish or act on error
{ error: { type, message, subcode } }
with typed codes
Breaking changes without deprecationBreaks deployed clients silentlyAdditive changes only;
Sunset
header for removals
Nesting resources more than 2 levels deepURLs become unmaintainable and coupling increases
/orders/{id}/items
yes;
/users/{id}/orders/{id}/items/{id}
no
FLOAT for monetary values in responsesFloating-point rounding corrupts financial dataReturn money as integer cents or string decimal
Exposing internal IDs or database row numbersLeaks implementation, complicates migrationUse 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
    { error: { type, message, subcode } }
    envelope
  • Validation errors include field-level detail
  • Pagination included for all collection endpoints
  • Pagination response includes
    meta.total
    ,
    meta.cursor
    , or
    links.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
    application/json
    on all JSON endpoints
  • 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

RuleGoodBad
Plural nouns
/users
,
/orders
/user
,
/order
kebab-case
/line-items
,
/tax-rates
/lineItems
,
/taxRates
No verbs
POST /orders
POST /createOrder
Opaque IDs
/users/usr_abc123
/users/42

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

MethodSemanticsIdempotentBody
GETRead, no side effectsYesNo
POSTCreate, trigger actionNoYes
PUTReplace entire resourceYesYes
PATCHPartial updateNo*Yes
DELETERemove resourceYesNo

*PATCH can be made idempotent with conditional requests (

If-Match
)

Status Code Reference

CodeMeaningWhen to Use
200OKSuccessful GET, PUT, PATCH
201CreatedSuccessful POST creating a resource
204No ContentSuccessful DELETE, or PUT/PATCH with no body
400Bad RequestMalformed request, validation failure
401UnauthorizedMissing or invalid authentication
403ForbiddenAuthenticated but not authorized
404Not FoundResource does not exist
409ConflictDuplicate create, optimistic lock failure
422Unprocessable EntitySemantically invalid (business rule violation)
429Too Many RequestsRate limit exceeded
500Internal Server ErrorUnexpected server failure
503Service UnavailableTemporary 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:

POST /v1/orders/{id}/cancel
POST /v1/orders/{id}/fulfill
POST /v1/users/{id}/verify-email
</details> <details> <summary><strong>Deep Dive: Response Format</strong></summary>

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": [ ... ]
}
  • data
    — the primary payload (object for single, array for collection)
  • meta
    — pagination cursors, request IDs, timestamps
  • links
    — HATEOAS navigation (self, next, prev, related actions)
  • errors
    — only present on error responses (4xx/5xx)

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 —
    cancel
    only appears when the order is cancellable
  • Include
    method
    when the action is not a GET (avoids client guessing)
  • 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
    actions
    array — this is Siren format, not widely adopted. Standard
    links
    with method hints covers the same use case with less complexity

Field Naming Convention

ConventionWhenExample
camelCaseJSON APIs (default)
createdAt
,
lineItems
snake_casePython/Go backends matching language convention
created_at
,
line_items

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
    null
    where possible
  • When
    null
    is semantically meaningful (explicitly unset vs absent), include it with documentation
  • Never return
    null
    for arrays — return
    []

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
</details> <details> <summary><strong>Deep Dive: Versioning Strategies</strong></summary>

Strategy Comparison

StrategyExampleProsCons
URL path
/v1/orders
Visible, cacheable, easy to testPollutes URL, not REST-pure
Request header
Accept-Version: 2
Clean URLs, REST-pureHarder to test, requires header tooling
Content negotiation
Accept: application/vnd.api.v2+json
Standards-compliantVerbose, limited browser/tool support
Query parameter
/orders?version=2
Easy to testCache-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:
    /v1/
    is stable, no deprecation
  • v2 launch: Announce v2, keep v1 running
  • v1 sunset: Add
    Sunset: Sat, 31 Dec 2026 23:59:59 GMT
    header to v1 responses
  • 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
</details> <details> <summary><strong>Deep Dive: Pagination</strong></summary>

Offset-Based vs Cursor-Based

FactorOffsetCursor
ImplementationSimple (LIMIT/OFFSET)Moderate (encode last-seen ID)
Performance on large datasetsDegrades (full table scan)Consistent (index seek)
Handles concurrent writesSkips/duplicates itemsStable position
Supports jump to page NYesNo
Best forSmall datasets, admin UIsLarge/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

ParameterOffsetCursor
page
Page number (1-indexed)
perPage
/
limit
Items per pageItems per page
after
Cursor for next page
before
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
  }
}
FieldDescription
error.type
Machine-readable error category (SCREAMING_SNAKE_CASE)
error.message
Short, human-readable description of the error
error.subcode
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
    X-Request-Id
    header value.

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

MechanismBest ForComplexityStateless
API KeyServer-to-server, simple integrationsLowYes
Bearer JWTUser-facing APIs, short-lived tokensMediumYes
OAuth 2.0 + PKCEUser-delegated access, third-party integrationsHighYes
Session cookiesTraditional web apps, SSRLowNo

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_
    ,
    sk_test_
    ) for instant recognition
  • 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

FlowWhen
Authorization Code + PKCEWeb/mobile apps on behalf of a user
Client CredentialsService-to-service (no user context)
Device CodeDevices 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:

Retry-After: 47
</details> <details> <summary><strong>Deep Dive: OpenAPI 3.1 Specification</strong></summary>

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

  • All reusable schemas in
    components/schemas
  • All reusable parameters in
    components/parameters
  • All reusable responses in
    components/responses
  • Never inline schema objects in
    paths
    — always
    $ref

Validation Tooling

  • spectral
    — OpenAPI linting with configurable rulesets
  • redocly
    — Spec validation + doc generation
  • swagger-cli validate
    — Basic structural validation
</details> <details> <summary><strong>Deep Dive: GraphQL Schema Design</strong></summary>

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

ChangeBreaking?Notes
Add optional request fieldNoClients ignore unknown fields
Add response fieldNoClients ignore unknown fields
Remove request fieldYesExisting clients may send it
Remove response fieldYesExisting clients may read it
Rename fieldYesEquivalent to remove + add
Change field typeYesParsing will fail
Tighten validationYesPreviously valid requests rejected
Loosen validationNoMore permissive
Remove endpointYes404 where clients expect 200
Change auth requirementYesAuth failures for existing clients
Add required request fieldYesExisting requests missing the field
Add new enum valueUsually 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
    meta.warnings
    array

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

## 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
</details>

Extension Points

  1. Framework-Specific Patterns: Express middleware, FastAPI dependency injection, Go
    net/http
    routing conventions
  2. API Gateway Integration: Kong, AWS API Gateway, Apigee — rate limiting, auth delegation, routing
  3. Contract Testing: Pact for consumer-driven contract tests; validate against OpenAPI spec with
    dredd
  4. SDK Generation:
    openapi-generator
    for client SDKs from OpenAPI spec;
    graphql-code-generator
    for TypeScript types
  5. Spec Validation Automation:
    spectral
    rulesets in CI,
    redocly lint
    for doc quality gates
  6. API Mocking:
    prism
    for mock servers from OpenAPI spec; enables parallel frontend/backend development
  7. Interactive Documentation: Scalar for modern, themeable API reference UI — see references/scalar-api-docs.md