OpenAPI & Documentation#

A well-documented API is a usable API. The OpenAPI Specification (formerly Swagger) provides a machine-readable description of your API that powers documentation, code generation, testing, and validation.

What is OpenAPI?#

OpenAPI is a standard for describing REST APIs. It’s a YAML or JSON document that describes every endpoint, parameter, request body, response, and authentication mechanism your API offers.

OpenAPI Document Structure#

openapi: 3.1.0
info:
  title: Orders API
  description: API for managing customer orders
  version: 1.2.0
  contact:
    name: Platform Team
    email: platform@example.com

servers:
  - url: https://api.example.com/v1
    description: Production
  - url: https://staging-api.example.com/v1
    description: Staging

paths:
  /orders:
    get: ...
    post: ...
  /orders/{id}:
    get: ...
    put: ...
    delete: ...

components:
  schemas: ...
  securitySchemes: ...

security:
  - bearerAuth: []

Key Sections#

SectionPurposeContains
infoAPI metadataTitle, version, description, contact
serversBase URLsProduction, staging, dev environments
pathsEndpointsAll routes with operations
componentsReusable definitionsSchemas, parameters, security, responses
securityGlobal auth requirementsDefault security schemes
tagsGroupingLogical grouping of operations

Schema Definition#

Schemas define the shape of request and response bodies:

components:
  schemas:
    Order:
      type: object
      required:
        - customer_id
        - items
      properties:
        id:
          type: integer
          readOnly: true
          example: 1234
        customer_id:
          type: integer
          description: ID of the customer placing the order
          example: 42
        status:
          type: string
          enum: [pending, confirmed, shipped, delivered, cancelled]
          default: pending
        items:
          type: array
          minItems: 1
          items:
            $ref: '#/components/schemas/OrderItem'
        total:
          type: number
          format: double
          readOnly: true
          example: 149.97
        created_at:
          type: string
          format: date-time
          readOnly: true

    OrderItem:
      type: object
      required:
        - product_id
        - quantity
      properties:
        product_id:
          type: integer
        quantity:
          type: integer
          minimum: 1
          maximum: 100
        unit_price:
          type: number
          format: double
          readOnly: true

    Error:
      type: object
      required:
        - type
        - title
        - status
      properties:
        type:
          type: string
          format: uri
        title:
          type: string
        status:
          type: integer
        detail:
          type: string
        request_id:
          type: string

Path Definition Example#

paths:
  /orders:
    get:
      summary: List orders
      description: Returns a paginated list of orders for the authenticated user
      operationId: listOrders
      tags:
        - Orders
      parameters:
        - name: status
          in: query
          schema:
            type: string
            enum: [pending, confirmed, shipped, delivered, cancelled]
        - name: page
          in: query
          schema:
            type: integer
            default: 1
            minimum: 1
        - name: per_page
          in: query
          schema:
            type: integer
            default: 20
            minimum: 1
            maximum: 100
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Order'
                  meta:
                    $ref: '#/components/schemas/PaginationMeta'
        '401':
          $ref: '#/components/responses/Unauthorized'

    post:
      summary: Create an order
      operationId: createOrder
      tags:
        - Orders
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateOrderRequest'
            example:
              customer_id: 42
              items:
                - product_id: 101
                  quantity: 2
      responses:
        '201':
          description: Order created
          headers:
            Location:
              schema:
                type: string
              description: URL of the created order
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Order'
        '422':
          $ref: '#/components/responses/ValidationError'

Security Schemes#

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: JWT access token from OAuth2 flow

    apiKey:
      type: apiKey
      in: header
      name: X-API-Key
      description: API key for server-to-server access

    oauth2:
      type: oauth2
      flows:
        authorizationCode:
          authorizationUrl: https://auth.example.com/authorize
          tokenUrl: https://auth.example.com/token
          scopes:
            read:orders: Read order data
            write:orders: Create and modify orders

Contract-First Development#

In contract-first (or design-first) development, you write the OpenAPI spec before writing any code. The spec becomes the source of truth.

flowchart LR
    A[Design API spec] --> B[Review with stakeholders]
    B --> C[Generate server stubs]
    B --> D[Generate client SDKs]
    B --> E[Generate test mocks]
    C --> F[Implement business logic]
    D --> G[Frontend development]
    E --> H[Integration testing]
    F --> I[Validate against spec]
    G --> I

Contract-First vs Code-First#

AspectContract-FirstCode-First
Spec is source of truth✅❌ (code is)
Frontend can start early✅❌ (waits for implementation)
API consistencyHigh (designed holistically)Variable (grows organically)
Initial effortHigher (design upfront)Lower (just start coding)
Spec drift riskLowHigh (spec and code diverge)
Best forMulti-team projects, public APIsSmall teams, rapid prototyping

Code Generation#

From an OpenAPI spec, you can generate:

Generated ArtifactToolPurpose
Server stubsopenapi-generator, swagger-codegenScaffold endpoint handlers
Client SDKsopenapi-generatorType-safe API clients in any language
TypeScript typesopenapi-typescriptFrontend type safety
Mock serversPrism, WireMockTest without real backend
Validation middlewareexpress-openapi-validatorRequest/response validation
Test casesSchemathesisProperty-based API testing
# Generate Python client from spec
openapi-generator generate \
  -i openapi.yaml \
  -g python \
  -o ./generated/python-client

# Generate TypeScript types
npx openapi-typescript openapi.yaml -o src/api-types.ts

# Start mock server
prism mock openapi.yaml

Documentation Tools#

Swagger UI#

Interactive documentation that lets developers try API calls directly from the browser:

<!-- Self-hosted Swagger UI -->
<div id="swagger-ui"></div>
<script src="https://unpkg.com/swagger-ui-dist/swagger-ui-bundle.js"></script>
<script>
  SwaggerUIBundle({
    url: "/openapi.yaml",
    dom_id: '#swagger-ui',
    presets: [SwaggerUIBundle.presets.apis],
  });
</script>

Redoc#

Beautiful, responsive API documentation with three-panel layout:

<!-- Redoc -->
<redoc spec-url="/openapi.yaml"></redoc>
<script src="https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js"></script>

Tool Comparison#

FeatureSwagger UIRedocStoplight
Try-it-out (live requests)✅❌ (premium only)✅
Three-panel layout❌✅✅
Code samplesBasicGoodExcellent
CustomisationLimitedGood (theming)Extensive
HostingSelf-hosted / SwaggerHubSelf-hosted / RedoclyCloud / self-hosted
CostFreeFree (premium available)Paid

Versioning in Documentation#

Document all supported API versions clearly:

# Separate specs per version
/docs/v1/openapi.yaml
/docs/v2/openapi.yaml

# Or use server URLs
servers:
  - url: https://api.example.com/v1
    description: Version 1 (stable)
  - url: https://api.example.com/v2
    description: Version 2 (current)

Documenting Deprecation#

paths:
  /users/{id}/orders:
    get:
      deprecated: true
      summary: List user's orders (DEPRECATED)
      description: |
        **Deprecated since v1.4.** Use `GET /orders?user_id={id}` instead.
        This endpoint will be removed on 2025-06-01.
      x-sunset: "2025-06-01"

Writing Good API Documentation#

Beyond the spec, good documentation includes:

Essential Documentation Sections#

SectionPurposeExample Content
Getting StartedFirst successful request in 5 minutesAuth setup, curl example
AuthenticationHow to get and use tokensFull OAuth2 walkthrough
ErrorsHow errors work and what codes meanError catalogue
PaginationHow to traverse collectionsCursor vs offset examples
Rate LimitsLimits and how to handle 429sHeaders, backoff strategy
ChangelogWhat changed in each versionBreaking changes highlighted
SDKsOfficial client librariesLinks, installation, usage

Documentation Anti-Patterns#

Anti-PatternProblemBetter
Auto-generated onlyNo context, no guidesSupplement with tutorials
No examplesTheory without practiceShow real request/response pairs
Outdated examplesErode trustAutomated testing of doc examples
Missing error docsDevelopers can’t handle failuresDocument every error code
No changelogBreaking changes surprise consumersPublish changes before rollout

Spec Validation and Testing#

Keep your spec accurate by validating it automatically:

# Lint the OpenAPI spec
spectral lint openapi.yaml

# Validate responses match the spec in tests
# (using express-openapi-validator in Node.js)
app.use(
  OpenApiValidator.middleware({
    apiSpec: './openapi.yaml',
    validateRequests: true,
    validateResponses: true,
  })
);

Continuous Spec Validation#

flowchart LR
    A[Edit spec] --> B[Lint in CI]
    B --> C{Valid?}
    C -->|No| D[Fail build]
    C -->|Yes| E[Run API tests]
    E --> F{Responses match spec?}
    F -->|No| D
    F -->|Yes| G[Deploy]
    G --> H[Regenerate docs]
    G --> I[Regenerate SDKs]

Key Takeaways#

  1. OpenAPI is the industry standard — learn it once, use it everywhere (docs, codegen, testing, validation)
  2. Contract-first unlocks parallel development — frontend and backend teams work simultaneously against the agreed spec
  3. Schemas are documentation — well-typed schemas with descriptions, examples, and constraints ARE your docs
  4. Auto-generation is not enough — supplement generated reference docs with guides, tutorials, and use-case examples
  5. Validate continuously — spec lint in CI + response validation in tests catches drift before it reaches consumers
  6. Examples are non-negotiable — every endpoint should show a real request and response
  7. Document errors as carefully as successes — developers spend more time debugging failures than celebrating 200s