Loading learning content...
An API specification is a blueprint. But blueprints don't build houses—tools do. The power of standards like OpenAPI lies not in the specification itself but in the vast ecosystem of tools that transform specifications into interactive documentation, generate client libraries, create mock servers, and validate API behavior.
Choosing the right documentation tools is a critical architectural decision. The right toolchain automates tedious tasks, catches errors early, ensures consistency across environments, and creates documentation experiences that delight developers.
This page surveys the complete API documentation tooling landscape: from specification editors where specifications are authored, through documentation generators that produce consumer-facing docs, to the testing and validation tools that ensure accuracy. By the end, you'll understand how to assemble a documentation pipeline that scales from startup to enterprise.
By the end of this page, you will understand: (1) Categories of API documentation tools and their purposes; (2) In-depth comparison of documentation generators (Swagger UI, ReDoc, Stoplight, elements); (3) Specification editors and linters for authoring; (4) Mock servers for frontend development; (5) Testing and validation tools; (6) CI/CD integration patterns; (7) Tool selection criteria based on project needs.
The API documentation tooling ecosystem can be organized into distinct categories, each serving a specific purpose in the documentation lifecycle:
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ AUTHORING │ -> │ GENERATION │ -> │ PUBLISHING │
│ │ │ │ │ │
│ • Editors │ │ • Doc generators│ │ • Hosting │
│ • Linters │ │ • Code generators │ • CDN │
│ • Validators │ │ • Mock servers │ │ • Portals │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │ │
v v v
┌─────────────────────────────────────────────────────────────────┐
│ CI/CD INTEGRATION │
│ • Automated validation • Testing • Deployment • Versioning │
└─────────────────────────────────────────────────────────────────┘
| Category | Purpose | Key Tools | When Used |
|---|---|---|---|
| Specification Editors | Author and edit OpenAPI specs | Swagger Editor, Stoplight Studio, VS Code + extensions | API design phase |
| Linters & Validators | Enforce style, catch errors | Spectral, OpenAPI-validator, Redocly CLI | CI/CD, pre-commit |
| Documentation Generators | Create interactive docs from specs | Swagger UI, ReDoc, Stoplight Elements, RapiDoc | Developer portals |
| Code Generators | Generate SDKs and server stubs | OpenAPI Generator, Orval, openapi-typescript | SDK creation |
| Mock Servers | Simulate API responses | Prism, Mockoon, Stoplight Prism | Frontend development |
| Testing Tools | Validate API against spec | Dredd, Schemathesis, Portman | Integration testing |
| API Portals | Complete documentation platforms | ReadMe, Stoplight, Redocly, SwaggerHub | External developer docs |
For each category, you face a build vs. buy decision. Open-source tools (Swagger UI, Spectral, Prism) provide flexibility and no licensing cost but require self-hosting and integration. Managed platforms (ReadMe, Stoplight, SwaggerHub) offer turnkey solutions with analytics, collaboration features, and support, but at a subscription cost. Evaluate based on team size, budget, and customization needs.
Documentation generators transform OpenAPI specifications into interactive, navigable documentation. They're the most visible part of your documentation infrastructure—what developers actually see and use.
The original and most widely recognized API documentation interface.
Swagger UI was created alongside the Swagger specification and remains the de facto standard for API exploration. It provides:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>API Documentation</title> <!-- Swagger UI CSS --> <link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css"> <style> body { margin: 0; padding: 0; } .topbar { display: none; } /* Hide default topbar */ </style></head><body> <div id="swagger-ui"></div> <!-- Swagger UI JavaScript --> <script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js"></script> <script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-standalone-preset.js"></script> <script> window.onload = function() { SwaggerUIBundle({ // OpenAPI specification URL or inline spec url: "/openapi.json", // DOM element to render into dom_id: '#swagger-ui', // Enable deep linking to operations deepLinking: true, // Presets for layout and functionality presets: [ SwaggerUIBundle.presets.apis, SwaggerUIStandalonePreset ], // Layout configuration layout: "StandaloneLayout", // Enable "Try it out" by default tryItOutEnabled: true, // Show request duration displayRequestDuration: true, // Syntax highlighting theme syntaxHighlight: { activate: true, theme: "monokai" }, // Persist authorization across page reloads persistAuthorization: true, // Custom request interceptor (e.g., add auth headers) requestInterceptor: (request) => { // Add custom header request.headers['X-Custom-Header'] = 'value'; return request; } }); }; </script></body></html>Clean, three-panel documentation optimized for reading.
ReDoc provides a different philosophy from Swagger UI: prioritizing clean presentation over interactivity. It excels at:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>API Documentation - ReDoc</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- ReDoc styling customization --> <style> body { margin: 0; padding: 0; } </style></head><body> <!-- ReDoc element with configuration --> <redoc spec-url="/openapi.json" hide-hostname="true" expand-responses="200,201" path-in-middle-panel="true" hide-loading="false" native-scrollbars="true" theme='{ "colors": { "primary": { "main": "#6366f1" } }, "typography": { "fontSize": "16px", "fontFamily": "Inter, system-ui, sans-serif", "headings": { "fontFamily": "Inter, system-ui, sans-serif", "fontWeight": "600" }, "code": { "fontSize": "14px", "fontFamily": "JetBrains Mono, monospace" } }, "sidebar": { "width": "280px", "backgroundColor": "#1e1e2e" }, "rightPanel": { "backgroundColor": "#1e1e2e", "width": "40%" } }' > </redoc> <!-- ReDoc JavaScript --> <script src="https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js"></script></body></html>Modern, customizable React components for embedding documentation.
Stoplight Elements provides embeddable React components that can be integrated into existing applications, offering maximum customization:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051
import React from 'react';import { API } from '@stoplight/elements';import '@stoplight/elements/styles.min.css'; // Full API documentation componentfunction APIDocumentation() { return ( <API apiDescriptionUrl="/openapi.json" // Layout: 'sidebar' | 'stacked' layout="sidebar" // Router: 'hash' | 'memory' | 'history' router="history" // Base path for routing basePath="/docs" // Hide "Try It" panel hideTryIt={false} // Hide mocking toggle hideMocking={true} // Custom logo logo="/logo.svg" /> );} // Embed in existing React appfunction DocsPage() { return ( <div className="docs-container"> <header className="custom-header"> <nav>{/* Your navigation */}</nav> </header> <main className="docs-content"> <APIDocumentation /> </main> <footer className="custom-footer"> {/* Your footer */} </footer> </div> );} export default DocsPage;| Feature | Swagger UI | ReDoc | Stoplight Elements |
|---|---|---|---|
| Interactive "Try It" | ✓ Full support | ✗ No (read-only) | ✓ Full support |
| Layout Style | Single panel, expandable | Three-panel, fixed | Sidebar or stacked |
| Customization | CSS + config options | CSS + theme object | Full React component control |
| Performance | Moderate (larger bundle) | Fast (optimized) | Moderate (React overhead) |
| Static Generation | Limited | ✓ Excellent (CLI) | ✓ Via SSR/SSG |
| Framework Integration | Framework-agnostic | Framework-agnostic | React-focused |
| Best For | API exploration & testing | Reference documentation | Embedded in existing apps |
Good documentation starts with good specifications. Editors and linters help authors write correct, consistent, and complete OpenAPI documents.
Swagger Editor (Browser-Based)
The original OpenAPI editor, providing split-pane editing with live preview:
Stoplight Studio (Desktop/Web)
A visual editor for non-developers and power users alike:
VS Code with Extensions
For developers who prefer their existing IDE:
1234567891011121314151617181920212223242526272829
{ // OpenAPI extension configuration "openapi.defaultEditor": "yaml", "openapi.showPreview": true, // YAML extension with OpenAPI schema "yaml.schemas": { "https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/schemas/v3.1/schema.json": [ "openapi.yaml", "openapi.yml", "**/openapi/*.yaml", "**/*.openapi.yaml" ] }, // Enable YAML formatting "yaml.format.enable": true, "yaml.format.singleQuote": false, "yaml.format.bracketSpacing": true, // Spectral linting "spectral.rulesetFile": ".spectral.yaml", "spectral.run": "onType", // Custom file associations "[yaml]": { "editor.defaultFormatter": "redhat.vscode-yaml" }}Spectral is the industry-standard linter for OpenAPI specifications. It enforces style guides, catches common errors, and ensures consistency across your API.
Spectral comes with OpenAPI rulesets covering:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081
# .spectral.yaml - Comprehensive API linting configuration # Extend the official OpenAPI rulesetextends: - spectral:oas # Override severity levelsrules: # Enforce operation IDs on all operations operation-operationId: severity: error # Require descriptions on operations operation-description: severity: warn # Enforce valid operation IDs (no spaces, special chars) operation-operationId-valid-in-url: severity: error # Require at least one tag per operation operation-tags: severity: warn # Require response examples oas3-valid-media-example: severity: warn # Ensure info contact is present info-contact: severity: warn # Ensure license is specified info-license: severity: hint # Custom rules for your organizationoverrides: - files: - "**/*.yaml" - "**/*.json" rules: # Custom: Enforce camelCase for property names camelCase-properties: given: "$.paths..properties[*]~" then: function: casing functionOptions: type: camel # Custom: Require x-internal extension for internal APIs internal-api-marked: given: "$.paths[*][*]" then: field: x-internal function: truthy severity: off # Enable for internal APIs # Custom: Prevent exposing sensitive fields no-sensitive-data-in-examples: given: "$..*[?(@.example)]" then: - function: pattern functionOptions: notMatch: "(password|secret|token|apikey)" severity: error # Custom: Require pagination for list endpoints list-operations-paginated: given: "$.paths[*].get" then: - function: schema functionOptions: schema: properties: parameters: contains: properties: name: enum: ["limit", "offset", "page", "cursor"] severity: hint12345678910111213141516171819202122232425
# Install Spectral CLInpm install -g @stoplight/spectral-cli # Lint a single filespectral lint openapi.yaml # Lint with custom rulesetspectral lint openapi.yaml --ruleset .spectral.yaml # Output as JSON (for CI/CD parsing)spectral lint openapi.yaml --format json > lint-results.json # Fail on warnings (strict mode for CI)spectral lint openapi.yaml --fail-severity warn # Lint multiple filesspectral lint "specs/**/*.yaml" # Example output# /api/openapi.yaml# 15:9 warning operation-description Operation "getUsers" should have a description.# 23:11 error operation-operationId-valid operationId "list-users" is not a valid C/C++ identifier.# 45:5 warning info-contact Info object should have "contact" object.## ✖ 3 problems (1 error, 2 warnings, 0 hints)Commit your .spectral.yaml to your repository and run Spectral in CI/CD. This ensures every API change is validated against your organization's standards before merge. Consider creating a shared ruleset package that all teams can depend on for org-wide consistency.
Code generators transform OpenAPI specifications into usable code: client SDKs for API consumers, server stubs for implementers, and types for type-safe development.
The most comprehensive code generation project, supporting 50+ languages and frameworks:
12345678910111213141516171819202122232425262728293031
# Install OpenAPI Generator CLInpm install -g @openapitools/openapi-generator-cli # List available generatorsopenapi-generator-cli list # Generate TypeScript Axios clientopenapi-generator-cli generate \ --input-spec openapi.yaml \ --generator-name typescript-axios \ --output ./sdk/typescript \ --additional-properties=npmName=@acme/api-client \ --additional-properties=npmVersion=1.0.0 \ --additional-properties=supportsES6=true # Generate Python clientopenapi-generator-cli generate \ --input-spec openapi.yaml \ --generator-name python \ --output ./sdk/python \ --additional-properties=packageName=acme_api \ --additional-properties=projectName=acme-api-client # Generate NestJS server stubsopenapi-generator-cli generate \ --input-spec openapi.yaml \ --generator-name typescript-nestjs \ --output ./server/generated # Use config file for reproducible generationopenapi-generator-cli generate -c openapi-config.yaml12345678910111213141516171819202122232425262728293031323334353637
# openapi-generator configuration fileinputSpec: ./openapi.yamloutputDir: ./sdk/typescript-fetchgeneratorName: typescript-fetch globalProperties: # Generate API classes apis: true # Generate model classes models: true # Generate supporting files supportingFiles: true additionalProperties: # NPM package name npmName: "@acme/api-client" # NPM version npmVersion: "1.0.0" # Use ES6+ features supportsES6: true # Use single request parameter object useSingleRequestParameter: true # Generate with interfaces (not classes) withInterfaces: true # Prefix for interfaces modelPropertyNaming: camelCase # Type mappings for custom handlingtypeMappings: DateTime: string # Import mappingsimportMappings: UUID: string # Template directory for customizationtemplateDir: ./templates/typescript-fetchOrval is purpose-built for modern frontend development, generating type-safe API clients with React Query, SWR, or vanilla fetch integration:
123456789101112131415161718192021222324252627282930313233343536373839404142434445
// orval.config.tsimport { defineConfig } from 'orval'; export default defineConfig({ api: { input: { target: './openapi.yaml', validation: true, }, output: { target: './src/api/generated.ts', mode: 'split', // or 'single', 'tags' client: 'react-query', // React Query v5 configuration override: { mutator: { path: './src/api/custom-fetch.ts', name: 'customFetch', }, query: { useQuery: true, useMutation: true, useInfinite: true, useInfiniteQueryParam: 'cursor', signal: true, }, }, }, hooks: { afterAllFilesWrite: 'prettier --write', }, }, // Generate mock service worker handlers mocks: { input: { target: './openapi.yaml', }, output: { target: './src/mocks/handlers.ts', client: 'msw', }, },});123456789101112131415161718192021222324252627282930313233343536373839404142434445
// Using Orval-generated React Query hooksimport { useGetUsers, useCreateUser, useGetUserById } from '../api/generated'; function UserList() { // Type-safe query with automatic caching const { data: users, isLoading, error } = useGetUsers({ limit: 20, offset: 0, }); // Type-safe mutation const createUser = useCreateUser(); const handleCreate = async () => { await createUser.mutateAsync({ data: { email: 'new@example.com', name: 'New User', }, }); }; if (isLoading) return <Loading />; if (error) return <Error error={error} />; return ( <div> {/* users is fully typed: User[] */} {users?.data.map(user => ( <UserCard key={user.id} user={user} /> ))} <button onClick={handleCreate}> Add User </button> </div> );} function UserDetail({ userId }: { userId: string }) { // Path parameters are type-safe const { data: user } = useGetUserById(userId); // user is typed as User | undefined return user ? <Profile user={user} /> : null;}Not all code generators produce production-quality output. Before committing to a generator: (1) Evaluate the generated code manually; (2) Check for active maintenance and community support; (3) Test edge cases in your spec; (4) Consider customizing templates for your coding standards.
Mock servers simulate API responses based on your OpenAPI specification, enabling frontend development before the backend is complete and providing predictable test fixtures.
Prism is the most feature-rich OpenAPI mock server, providing:
12345678910111213141516171819202122232425
# Install Prismnpm install -g @stoplight/prism-cli # Start mock serverprism mock openapi.yaml # Start with specific portprism mock openapi.yaml --port 4010 # Enable dynamic mode (generates random data from schemas)prism mock openapi.yaml --dynamic # Enable request validation (rejects invalid requests)prism mock openapi.yaml --errors # Proxy mode: mock undefined endpoints, forward others to real APIprism proxy openapi.yaml https://api.example.com # Example output# [CLI] ... INFO HTTP server listening on http://127.0.0.1:4010# [HTTP] ... INFO GET /users -> 200 # Make requests to the mock servercurl http://localhost:4010/users# Returns mocked response based on spec examples or generated dataMSW runs mocks in the browser or Node.js, intercepting network requests without a separate server process. This is ideal for testing and Storybook:
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859
// handlers.ts - MSW request handlers generated from OpenAPI (via Orval)import { http, HttpResponse } from 'msw'; // User fixturesconst users = [ { id: '1', email: 'alice@example.com', name: 'Alice Smith' }, { id: '2', email: 'bob@example.com', name: 'Bob Jones' },]; export const handlers = [ // GET /users http.get('/api/users', ({ request }) => { const url = new URL(request.url); const limit = parseInt(url.searchParams.get('limit') || '20'); const offset = parseInt(url.searchParams.get('offset') || '0'); return HttpResponse.json({ data: users.slice(offset, offset + limit), pagination: { total: users.length, limit, offset, hasMore: offset + limit < users.length, }, }); }), // GET /users/:id http.get('/api/users/:id', ({ params }) => { const user = users.find(u => u.id === params.id); if (!user) { return HttpResponse.json( { code: 'NOT_FOUND', message: 'User not found' }, { status: 404 } ); } return HttpResponse.json({ data: user }); }), // POST /users http.post('/api/users', async ({ request }) => { const body = await request.json() as { email: string; name: string }; const newUser = { id: String(users.length + 1), email: body.email, name: body.name, }; users.push(newUser); return HttpResponse.json( { data: newUser }, { status: 201 } ); }),];1234567891011121314151617181920212223242526272829303132333435363738394041
// Browser setup (src/mocks/browser.ts)import { setupWorker } from 'msw/browser';import { handlers } from './handlers'; export const worker = setupWorker(...handlers); // Start in development mode (main.tsx or App.tsx)if (process.env.NODE_ENV === 'development') { const { worker } = await import('./mocks/browser'); await worker.start({ onUnhandledRequest: 'bypass', // Don't mock unhandled requests });} // Node.js setup for testing (src/mocks/server.ts)import { setupServer } from 'msw/node';import { handlers } from './handlers'; export const server = setupServer(...handlers); // Jest/Vitest setupbeforeAll(() => server.listen({ onUnhandledRequest: 'error' }));afterEach(() => server.resetHandlers());afterAll(() => server.close()); // Override handlers for specific testsimport { server } from './mocks/server';import { http, HttpResponse } from 'msw'; test('handles server error', async () => { server.use( http.get('/api/users', () => { return HttpResponse.json( { code: 'SERVER_ERROR', message: 'Database unavailable' }, { status: 500 } ); }) ); // Test error handling...});Documentation is only valuable if it accurately reflects the API. Testing and validation tools ensure your specification and implementation stay in sync.
Dredd tests your API implementation against your specification, ensuring every documented endpoint works as described:
123456789101112131415161718192021222324252627282930313233343536373839404142
# dredd.yml - Dredd configurationdry-run: falsehookfiles: - ./dredd/hooks.js # OpenAPI specificationblueprint: ./openapi.yaml # API server endpointendpoint: http://localhost:3000 # Output formatreporter: - cli - html # HTML report outputoutput: - ./dredd-report.html # Only test specific methods/pathsonly: - 'User > /users > Get all users > 200' - 'User > /users/{id} > Get user by ID > 200' # Skip specific transactionsskip: - 'Admin > /admin/* > *' # Skip admin endpoints # Custom headers for all requestsheader: - 'Authorization: Bearer test-token' - 'Accept: application/json' # Sort transactions by pathsorted: true # Fail on first errorbail: false # Color outputcolor: true123456789101112131415161718192021222324252627282930313233343536373839404142434445
// dredd/hooks.js - Custom test setup and teardownconst hooks = require('hooks');const db = require('../test/db'); // Global setup before all testshooks.beforeAll((transactions, done) => { // Seed test database db.seed().then(() => done());}); // Global teardownhooks.afterAll((transactions, done) => { db.cleanup().then(() => done());}); // Setup specific test data before a transactionhooks.before('User > /users/{id} > Get user by ID > 200', (transaction, done) => { // Create the user that will be fetched db.createUser({ id: '123', name: 'Test User' }) .then(() => { // Update path parameter transaction.fullPath = '/users/123'; done(); });}); // Modify request before sendinghooks.beforeEach((transaction, done) => { // Skip deprecated endpoints if (transaction.name.includes('deprecated')) { transaction.skip = true; } // Add dynamic auth token transaction.request.headers['Authorization'] = `Bearer ${getTestToken()}`; done();}); // Validate response beyond schemahooks.afterEach((transaction, done) => { if (transaction.test.status !== 'pass') { console.error('Transaction failed:', transaction.test.message); } done();});Schemathesis uses property-based testing to discover edge cases and security vulnerabilities by generating thousands of valid and invalid requests:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849
# Install Schemathesispip install schemathesis # Basic test runschemathesis run http://localhost:3000/openapi.json # Run with specific checksschemathesis run http://localhost:3000/openapi.json \ --checks all # Run all validation checks # Stateful testing (tests sequences of requests)schemathesis run http://localhost:3000/openapi.json \ --stateful=links # Authenticationschemathesis run http://localhost:3000/openapi.json \ --auth-header "Authorization: Bearer test-token" # Limit to specific endpointsschemathesis run http://localhost:3000/openapi.json \ --endpoint "/users" "/products" # Exclude specific methodsschemathesis run http://localhost:3000/openapi.json \ --method GET POST # Only test GET and POST # Generate reproducible test casesschemathesis run http://localhost:3000/openapi.json \ --hypothesis-seed=42 # Output JUnit XML for CIschemathesis run http://localhost:3000/openapi.json \ --junit-xml=test-results.xml # Example output:# ================ Schemathesis test session starts ================# Schema location: http://localhost:3000/openapi.json# Base URL: http://localhost:3000# Operations: 15# Hypothesis settings: max_examples=100, database=None## GET /users ............................. [200/200]# POST /users ............................ [200/200]# GET /users/{id} F [100/100]# -> status_code_conformance: Response status code 500 not in schema# -> content_type_conformance: Response content type not application/json## ================ FAILURES ================# 1 failed, 14 passed in 45.23sTools like Schemathesis generate many requests, including edge cases with malformed data. Always run against a staging or test environment, never production. Ensure your test environment can handle the load and has isolated data.
The true power of documentation tooling emerges when integrated into your CI/CD pipeline. Automated checks ensure specification quality, generated documentation stays current, and implementation matches documentation.
┌─────────────────────────────────────────────────────────┐
│ PR / Commit │
└───────────────────────────┬─────────────────────────────┘
│
┌───────────────────┼───────────────────┐
│ │ │
v v v
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ Lint Spec │ │ Generate Docs │ │ Type Check │
│ (Spectral) │ │ (ReDoc CLI) │ │ (TypeScript) │
└───────────────┘ └───────────────┘ └───────────────┘
│ │ │
└───────────────────┼───────────────────┘
│
v
┌───────────────┐
│ Contract Test │
│ (Dredd) │
└───────────────┘
│
v
┌───────────────┐
│ Deploy Docs │
│ (Pages) │
└───────────────┘
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
name: API Documentation Pipeline on: push: branches: [main] paths: - 'openapi/**' - 'src/api/**' pull_request: paths: - 'openapi/**' - 'src/api/**' jobs: lint: name: Lint OpenAPI Specification runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install Spectral run: npm install -g @stoplight/spectral-cli - name: Lint OpenAPI spec run: spectral lint openapi/openapi.yaml --fail-severity warn - name: Upload lint results if: failure() uses: actions/upload-artifact@v3 with: name: spectral-results path: spectral-report.json generate-types: name: Generate TypeScript Types runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20' cache: 'npm' - name: Install dependencies run: npm ci - name: Generate types run: npx openapi-typescript openapi/openapi.yaml -o src/types/api.ts - name: Type check run: npx tsc --noEmit - name: Check for uncommitted changes run: | if [ -n "$(git status --porcelain src/types/api.ts)" ]; then echo "Generated types are out of sync. Run 'npm run generate:types' and commit." exit 1 fi contract-test: name: Contract Testing needs: [lint, generate-types] runs-on: ubuntu-latest services: api: image: ghcr.io/${{ github.repository }}/api:test ports: - 3000:3000 steps: - uses: actions/checkout@v4 - name: Install Dredd run: npm install -g dredd - name: Run contract tests run: dredd openapi/openapi.yaml http://localhost:3000 - name: Upload test results if: always() uses: actions/upload-artifact@v3 with: name: dredd-results path: dredd-report.html build-docs: name: Build Documentation needs: [lint] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Build ReDoc static HTML run: | npx @redocly/cli build-docs openapi/openapi.yaml \ --output docs/index.html \ --title "API Documentation" - name: Upload docs artifact uses: actions/upload-artifact@v3 with: name: documentation path: docs/ deploy-docs: name: Deploy Documentation needs: [build-docs, contract-test] if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest permissions: pages: write id-token: write environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} steps: - name: Download docs artifact uses: actions/download-artifact@v3 with: name: documentation path: docs/ - name: Upload to Pages uses: actions/upload-pages-artifact@v2 with: path: docs/ - name: Deploy to Pages id: deployment uses: actions/deploy-pages@v2With so many tools available, selection can be overwhelming. Use these decision frameworks to choose the right tools for your context.
| Team Size | Recommended Stack | Rationale |
|---|---|---|
| Solo / Early Startup | Swagger Editor + Swagger UI + Spectral | Zero cost, minimal setup, covers basics |
| Small Team (2-10) | VS Code + ReDoc + Prism + Spectral + Dredd | Local development focus, moderate CI/CD |
| Medium Team (10-50) | Stoplight Studio + Elements + OpenAPI Generator + Full CI/CD | Collaboration features, code generation, automation |
| Enterprise (50+) | SwaggerHub / Stoplight / ReadMe + Custom tooling | Governance, access control, analytics, support |
| Use Case | Priority Tools | Why |
|---|---|---|
| Public API documentation | ReDoc, ReadMe, Redocly | Clean presentation, SEO, branding |
| Internal developer portal | Swagger UI, Backstage | Interactive testing, authentication integration |
| SDK generation | OpenAPI Generator, Orval | Multi-language support, customization |
| Design-first workflow | Stoplight Studio, Insomnia Designer | Visual editing, mock server integration |
| Testing focus | Dredd, Schemathesis, Portman | Contract validation, edge case discovery |
| Frontend development | Orval + MSW, Prism | Type-safe clients, local mocking |
Don't over-engineer your documentation toolchain initially. Start with Spectral for linting and Swagger UI or ReDoc for rendering. Add code generation, mocking, and contract testing as your needs grow. Every tool adds maintenance burden—add tools when the problem they solve becomes painful.
We've surveyed the complete API documentation tooling ecosystem. Here's how to think about assembling your pipeline:
You now have a comprehensive understanding of API documentation tooling. In the final page, we'll explore developer experience (DX)—how to create documentation that developers actually want to use, that accelerates integration, and that builds trust in your API.