Skip to content

API Design and Contracts (OpenAPI/Swagger)

API Design and Contracts (OpenAPI/Swagger)

Overview

An API contract describes requests and responses. A good contract reduces miscommunication and accelerates development.

Key practices

  • Consistent naming (nouns for resources)
  • Use proper status codes and error shapes
  • Pagination and filtering conventions
  • Idempotency for PUT/PATCH

Tools

  • OpenAPI 3.0/3.1 YAML
  • Swagger UI / Redoc
  • Codegen for clients/servers

Example error shape

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "title is required",
    "fields": { "title": "required" }
  }
}

Checklist

  • Write OpenAPI spec for core endpoints
  • Share mock servers or example responses
  • Add contract tests

API Design and Contracts (OpenAPI / Swagger)

Designing an API is like designing a user interface—only the users are other developers (your teammates, other services, external partners). A clear contract lowers coordination cost, prevents subtle breaking changes, and speeds up parallel work across frontend, backend, and QA.


1. Core Concepts

API Contract: A formal, versioned description of the surface area: endpoints, methods, request parameters, authentication requirements, and response schemas (including errors).
Schema‑First (Design‑First): Write or evolve the OpenAPI spec before/alongside implementation so other teams can stub clients or mock servers early.
Backward Compatibility: Avoid breaking existing clients—prefer additive changes (new fields, new endpoints) over mutation.

Mental model: Treat your API like a public library function. If you silently change the shape or meaning of an existing field, you impose hidden upgrade costs on every caller.


2. Resource Modeling

Pick clear nouns and stable identifiers. Map real world/business concepts, not UI labels.

Anti-Pattern Better Reason
/doPayment /payments (POST) Use nouns + HTTP verbs instead of RPC-ish verbs
/user/list /users (GET) Collections are plural nouns
/createProject /projects (POST) Consistency aids discoverability
/orders/getActive /orders?status=active Filtering via query parameters

Prefer hierarchical nesting only when the subordinate resource has no meaning outside the parent (e.g., /projects/{id}/members). Do NOT nest deeply (over 2 levels) – it hurts caching and clarity.


3. HTTP Methods & Idempotency

Method Typical Use Idempotent? Notes
GET Retrieve resource(s) Yes Must not alter state
POST Create / non-idempotent action No Returns 201 + Location header on create
PUT Full replace (sends whole resource) Yes Client must send complete representation
PATCH Partial update Varies (should be) Use JSON Patch or merge semantics; document behavior
DELETE Remove resource Yes 204 on success; deletes should be safe to retry

Idempotency helps clients retry safely during network hiccups. For POST endpoints creating external effects (payments, emails), consider an Idempotency-Key header.


4. Status Codes & Error Design

Use the small, predictable subset below. Avoid 200 for failures just because they “worked” technically.

Situation Code Example
Created new resource 201 POST /projects
Empty success (no body) 204 DELETE /projects/{id}
Client sent invalid JSON/body 400 Malformed request
Unauthorized (no/invalid auth) 401 Missing token
Authenticated but not allowed 403 Role lacks permission
Resource not found 404 Unknown ID
Conflict / version mismatch 409 Duplicate key, optimistic lock fail
Too many requests 429 Rate limit exceeded
Service bug/downstream failure 500 Unexpected exception
Temporary upstream failure 502/503 Dependency outage

Error Envelope Pattern

Keep errors structured and machine-friendly while still readable.

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "title is required",
    "fields": { "title": "required" },
    "request_id": "9f3d6d2a"
  }
}
Include a request_id (trace id) so support/observability tools can correlate logs.


5. Pagination, Filtering, Sorting

Document defaults and maximums explicitly.

Pattern Pros Cons
limit + offset Simple, SQL friendly Large offsets slow; duplicates after inserts
Cursor (opaque token) Stable ordering across mutations Slightly harder for newbies to debug

Prefer cursor-based pagination for high-churn datasets (activity feeds) and offset for small, static lists (admin tables). Return a structure like:

{
  "data": [ {"id":1}, {"id":2} ],
  "next_cursor": "eyJpZCI6Mn0=",
  "total": 42
}
total is optional (can be expensive). If omitted, document it.

Filtering: support simple equality ?status=active first; layer additional filters only as real use cases appear.

Sorting: ?sort=-created_at (dash for descending) is a common concise pattern.


6. Versioning Strategy

Avoid versioning too early; once you NEED it, pick one style and stick to it.

Style Example Comments
URI /v1/projects Easiest; leaks version into bookmarks
Header Accept: application/vnd.api+json;version=1 Cleaner URIs; harder to demo in browser
Resource evolution Additive only, deprecate fields Works until a breaking semantic shift is needed

Deprecate before removing: mark a field as deprecated in the schema + changelog + logs when clients still request it.


7. OpenAPI (Design‑First Workflow)

  1. Sketch resource list & main use cases (whiteboard or doc).
  2. Draft /openapi.yaml with core paths + schemas.
  3. Share via Swagger UI or Stoplight; gather feedback BEFORE coding.
  4. Generate server stubs / client SDKs.
  5. Implement handlers; add contract tests guaranteeing spec ↔ implementation alignment.
  6. CI step: validate spec (lint) + diff detection (fail if breaking change).

Minimal YAML Example

openapi: 3.0.3
info:
  title: Project Service API
  version: 1.0.0
paths:
  /projects:
    post:
      summary: Create a project
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateProject'
      responses:
        '201':
          description: Created
          headers:
            Location:
              schema:
                type: string
        '400': { $ref: '#/components/responses/BadRequest' }
components:
  schemas:
    CreateProject:
      type: object
      required: [name]
      properties:
        name: { type: string, maxLength: 80 }
        description: { type: string }
  responses:
    BadRequest:
      description: Invalid user input

8. Consistency Conventions

Concern Convention Example
Field naming snake_case or camelCase (pick one) created_at
Timestamps ISO 8601 UTC 2025-01-12T15:04:05Z
Booleans Avoid tri-state unless needed is_active
Enums Upper snake or lower kebab (document) status: active|archived
IDs UUID v4 or numeric; stay consistent "a8f9..."
Null vs empty Prefer empty list over null array [] not null

Document these in a short "API style guide" page if your team grows.


9. Security & Auth Touchpoints

Indicate security schemes in OpenAPI (components.securitySchemes). Common patterns: * Bearer JWT: Authorization: Bearer <token> * API Key (less preferred): X-API-Key: <key> * OAuth2 flows (authorizationCode with PKCE for SPAs)

Also document rate limits: headers like X-RateLimit-Remaining help clients back off gracefully.


10. Testing the Contract

Layer Purpose Tooling
Schema lint Catch style + structural issues Spectral, Redocly CLI
Mock server Parallel frontend dev Prism, Stoplight
Contract tests Ensure implementation matches spec Dredd, Schemathesis
Negative tests Validate errors defined Custom test harness

Automate: a CI job fails if spec changed with a breaking diff (e.g., removed field). Keep a historical artifact (e.g., openapi.previous.json).


11. Example End-to-End Flow (Mermaid)

sequenceDiagram
  participant FE as Frontend
  participant SPEC as OpenAPI Spec
  participant BE as Backend Service
  participant QA as Contract Tests

  FE->>SPEC: Read schema & generate client
  BE->>SPEC: Update path /projects
  SPEC-->>FE: New field `description` (non-breaking)
  QA->>BE: Run tests against spec
  BE-->>QA: 201 Created responses match
  FE->>BE: POST /projects JSON
  BE-->>FE: 201 + Location header

12. Checklist

  • Core resources named with nouns & consistent plurality
  • HTTP methods & status codes documented
  • Error envelope standardized (code/message/request_id)
  • Pagination & filtering strategy documented
  • Versioning approach chosen (or explicitly deferred)
  • OpenAPI spec validated & linted in CI
  • Breaking-change diff guard enabled
  • Auth schemes + rate limits defined
  • Contract/negative tests implemented
  • Changelog entries for schema additions

13. Quick Reference (Cheat Table)

Need Where to Look
Field types & validation components.schemas.*
Auth method components.securitySchemes + security
Error shapes Shared #/components/responses
Pagination convention Listing endpoints description
Rate limits Usage docs / headers section

14. Further Resources