BytePane

OpenAPI & Swagger Guide: Document Your API Like a Pro

API20 min read

The Monday Morning Scenario Every Backend Developer Recognizes

A frontend engineer files a ticket: "The /users endpoint is returning 422 — what are the required fields?" The backend engineer who built it left the company. The Notion page is six months stale. The Postman collection in Slack has an expired auth token. Now someone spends two hours reading source code that should have been documented in two minutes.

This scenario plays out in teams everywhere — and it is precisely the problem the OpenAPI Specification was designed to eliminate. According to the SmartBear State of Software Quality: API 2023 report (surveying 1,100+ professionals across 17 industries), 71% of API teams use a standardized API design approach, and 62% use a dedicated API design and documentation tool. The ones who don't are spending that Monday morning in Slack threads.

This guide covers everything that matters for practical OpenAPI usage: the version landscape, spec authoring, the documentation renderer decision (Swagger UI vs Redoc vs Scalar), client SDK generation, validation in CI/CD, and the design-first workflow that teams with mature APIs actually use.

Key Takeaways

  • OpenAPI ≠ Swagger: OpenAPI is the Linux Foundation specification; Swagger is SmartBear's tooling brand. Both names are used interchangeably, but the distinction matters for license and governance decisions.
  • Use OpenAPI 3.1 for new specs: Full JSON Schema 2019-09 compatibility, native webhooks, and cleaner nullable syntax. Tooling support is now mature — Swagger UI 5+, Redoc, and Scalar all support it fully.
  • Design-first beats code-first for API quality. Write the spec first, generate server stubs, then implement — the contract is the source of truth, not the code comments.
  • Validate in CI: Use Redocly CLI or Spectral to lint your spec on every pull request. Broken $ref links and missing operation IDs are caught before they reach reviewers.
  • The global Open API market was $4.53B in 2024, growing at 23.83% CAGR per Straits Research — documenting your API is now a commercial expectation, not an internal nicety.

OpenAPI vs. Swagger: Clearing Up the Name Confusion

The terms are used interchangeably in the industry, which causes genuine confusion. Here is the precise history:

  • 2010–2016: Reverb Technologies (later acquired by SmartBear) created and maintained the "Swagger Specification" for describing REST APIs. The spec and the tooling shared the Swagger name.
  • 2016: SmartBear donated Swagger 2.0 to the OpenAPI Initiative (OAI), a Linux Foundation project. Members include Google, Microsoft, IBM, Salesforce, and PayPal. The specification was renamed "OpenAPI Specification (OAS)."
  • 2017–present: "Swagger" now refers exclusively to SmartBear's commercial tooling suite — Swagger UI, Swagger Editor, and SwaggerHub. The specification is OpenAPI.

In practice: when someone says "write a Swagger spec," they mean an OpenAPI YAML/JSON file. When someone says "render it in Swagger," they mean Swagger UI. The governance body, the specification itself, and the versioning are all under the OpenAPI Initiative at openapis.org.

The GitHub repositories tell the adoption story: the OAI/OpenAPI-Specification repo has 30,900+ stars and the swagger-api/swagger-ui renderer has 28,700+ stars, making them both among the most starred API-tooling repos on GitHub as of April 2026.

OpenAPI Version Comparison: 2.0 vs 3.0 vs 3.1

Three major versions are in active use. Each broke backward compatibility significantly. Here is what matters for a migration decision:

Feature2.0 (Swagger)3.03.1 (current)
Schema reusedefinitionscomponents/schemascomponents/schemas
Request bodyin: body parameterrequestBody objectrequestBody object
Nullable typesNot supported nativelynullable: truetype: [T, "null"]
JSON Schema compatSubset onlyDraft 7 subsetFull 2019-09 compat
WebhooksNoNoNative webhooks object
Paths requiredYesYesNo (webhook-only specs valid)
Tooling supportUniversal (legacy)UniversalMature (2025+)

The migration advice: If your spec is still on 2.0, move to 3.1 directly — there is no reason to stop at 3.0. The swagger2openapi npm package handles the 2.0→3.0 mechanical transformation. The 3.0→3.1 jump mainly involves replacing nullable: true with the JSON Schema union syntax — automatable with a codemod.

The 3.1 "full JSON Schema compatibility" headline is significant in practice: in OpenAPI 3.0, the schema object was a subset of JSON Schema with OpenAPI-specific extensions. You could not use arbitrary JSON Schema validators on OpenAPI 3.0 schemas. In 3.1, OpenAPI schemas are valid JSON Schema — you can use any JSON Schema validator directly, which dramatically simplifies request validation tooling.

Anatomy of an OpenAPI 3.1 Spec

An OpenAPI spec is a YAML or JSON document. YAML is overwhelmingly preferred for hand-authored specs (less punctuation noise, supports comments). Here is a minimal but production-realistic example covering the key structural elements:

openapi.yaml — production-realistic minimal spec
openapi: '3.1.0'

info:
  title: Payments API
  description: |
    REST API for processing payments and managing subscriptions.
    All monetary values are in cents (integer) to avoid floating-point issues.
  version: '2.4.1'
  contact:
    name: API Support
    email: [email protected]

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

security:
  - bearerAuth: []  # Global: all operations require auth unless overridden

paths:
  /payments:
    post:
      operationId: createPayment
      summary: Create a new payment
      tags: [Payments]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreatePaymentRequest'
            examples:
              card_payment:
                summary: Credit card payment
                value:
                  amount: 4999
                  currency: USD
                  method: card
                  card_token: tok_visa
      responses:
        '201':
          description: Payment created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Payment'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'

  /payments/{id}:
    get:
      operationId: getPayment
      summary: Retrieve a payment by ID
      tags: [Payments]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Payment found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Payment'
        '404':
          $ref: '#/components/responses/NotFound'

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT

  schemas:
    CreatePaymentRequest:
      type: object
      required: [amount, currency, method]
      properties:
        amount:
          type: integer
          minimum: 1
          description: Amount in cents. Must be a positive integer.
          example: 4999
        currency:
          type: string
          enum: [USD, EUR, GBP]
        method:
          type: string
          enum: [card, bank_transfer, wallet]
        card_token:
          type: ['string', 'null']  # OpenAPI 3.1 nullable syntax
          description: Required when method is 'card'. Token from tokenization service.

    Payment:
      type: object
      properties:
        id:
          type: string
          format: uuid
          readOnly: true
        amount:
          type: integer
        currency:
          type: string
        status:
          type: string
          enum: [pending, succeeded, failed, refunded]
        created_at:
          type: string
          format: date-time
          readOnly: true

    ValidationError:
      type: object
      properties:
        message:
          type: string
        errors:
          type: array
          items:
            type: object
            properties:
              field:
                type: string
              code:
                type: string
              message:
                type: string

  responses:
    NotFound:
      description: Resource not found
      content:
        application/json:
          schema:
            type: object
            properties:
              message:
                type: string
                example: Payment not found

Key patterns in this example worth noting: operationId on every operation (required for code generation — it becomes the function name in generated clients), $ref everywhere (DRY principle applied to schemas and responses), examples with realistic values (not just string placeholders), and the 3.1 nullable syntax type: ['string', 'null'] instead of the 3.0 nullable: true.

Swagger UI vs Redoc vs Scalar: Which Renderer?

This is one of the most discussed decisions in API tooling. All three render the same OpenAPI spec; they differ significantly in aesthetics, interactivity, and hosting model.

ToolGitHub StarsTry-It-OutLayoutOAS 3.1Best For
Swagger UI28,700+Yes (built-in)Single-columnv5+ (full)Internal dev portals, recognizable UX
Redoc25,600+No (read-only)3-panelFullPublic-facing docs, polished design
Scalar~10,000+Yes + codegen3-panel + sidebarFullNew projects, best DX
Stoplight Elements~1,600+Yes3-panel + mock serverFullEmbedded in existing docs sites

Swagger UI: The Default Choice

Swagger UI is what most developers picture when they hear "API documentation." Its familiar accordion UI, the green POST / blue GET / red DELETE color coding, and the "Try it out" button that lets you send live requests directly from the docs page — these are industry-standard UX patterns. If your docs are internal-facing or your audience is developers who will recognize the interface immediately, Swagger UI is a safe default.

The weakness: it is visually dated. The single-column layout does not scale well to specs with 50+ endpoints. The theming API is limited. For public-facing documentation that competes with Stripe or Twilio's developer portals, it looks under-resourced.

Redoc: Polish Without Interactivity

Redoc's three-panel layout (navigation sidebar, main content, code samples) is the design pattern that modern developer portals have converged on — for good reason. It works well for long specs, handles deep navigation cleanly, and auto-generates code samples in multiple languages. The trade-off: no built-in try-it-out console. For teams that use Postman or Insomnia for API exploration, this may not matter.

Scalar: The Emerging Standard for New Projects

Scalar combines Redoc's layout quality with Swagger UI's interactivity, adds code generation in 15+ languages, and ships with framework-specific integrations for FastAPI, Express, NestJS, and others. For teams starting a new API project in 2025/2026, Scalar is the strongest default. The ecosystem is smaller than Swagger UI's, but growing rapidly.

Embedding Scalar in a Node.js Express app
import express from 'express'
import { apiReference } from '@scalar/express-api-reference'
import { readFileSync } from 'fs'

const app = express()

// Serve the OpenAPI spec
app.get('/openapi.yaml', (req, res) => {
  res.type('yaml').send(readFileSync('./openapi.yaml', 'utf8'))
})

// Mount Scalar at /docs
app.use('/docs', apiReference({
  spec: { url: '/openapi.yaml' },
  theme: 'default',
}))

app.listen(3000, () => console.log('Docs at http://localhost:3000/docs'))

Design-First vs Code-First: The Practical Difference

The "design-first" debate is really a question of what is the canonical source of truth for your API contract:

Code-First (Generate Spec From Code)

You write server code with annotations, then a library generates the OpenAPI spec automatically. Popular implementations:

  • FastAPI (Python): Auto-generates OpenAPI 3.1 from type annotations and Pydantic models — zero configuration. The spec is available at /openapi.json in development.
  • NestJS (TypeScript): @nestjs/swagger reads decorators and TypeScript types to produce the spec.
  • Spring Boot (Java): springdoc-openapi generates from @Operation annotations.

Code-first is faster to bootstrap and automatically stays in sync with the implementation. The problem: generated specs tend to be mechanically correct but poorly documented. Auto-generated descriptions are often empty or copied from variable names. Examples are missing or trivial. The spec reflects what the code does, not what users need to understand.

Design-First (Generate Code From Spec)

You author the OpenAPI spec manually (or with a visual tool like Stoplight Studio), then generate server stubs and client SDKs from it. The spec is the contract; the code implements the contract. Changes go through the spec first.

Design-first workflow with OpenAPI Generator
# Step 1: Validate the spec first
npx @redocly/cli lint openapi.yaml

# Step 2: Generate TypeScript server types (express-validator compatible)
npx @openapitools/openapi-generator-cli generate   -i openapi.yaml   -g typescript-express-server   -o ./generated/server

# Step 3: Generate TypeScript fetch client
npx @openapitools/openapi-generator-cli generate   -i openapi.yaml   -g typescript-fetch   -o ./generated/client   --additional-properties=supportsES6=true,withInterfaces=true

# Step 4: Generate Python client (for internal tooling)
npx @openapitools/openapi-generator-cli generate   -i openapi.yaml   -g python   -o ./generated/python-client

# Regenerate after spec changes (add to Makefile or npm script)
# never edit generated files directly — changes get overwritten

OpenAPI Generator supports 50+ client languages and 40+ server frameworks. For production-quality SDKs — with retries, pagination helpers, auth token refresh, and idiomatic typing — commercial tools like Speakeasy and Fern are worth evaluating. Stripe and Anthropic both use Speakeasy for their official client SDKs.

For reference, here's where OpenAPI fits in the broader REST API best practices workflow — the spec is the contract layer between design and implementation.

Spec Validation in CI/CD with Redocly CLI and Spectral

An OpenAPI spec is source code. It deserves the same quality gates as application code: linting, schema validation, and breaking change detection in CI.

Redocly CLI: The Full-Stack Linter

.redocly.yaml — configuring linting rules
# .redocly.yaml
apis:
  main:
    root: openapi.yaml

rules:
  operation-operationId: error          # operationId required on all operations
  operation-summary: error              # Summary required
  operation-description: warn           # Description recommended
  no-unused-components: warn            # Flag orphan schemas
  tag-description: warn                 # Tags need descriptions
  security-defined: error              # All ops must have security defined
  response-contains-header:
    name: X-Request-ID                  # All 2xx responses must include trace header
    severity: warn

# Run: npx @redocly/cli lint openapi.yaml
# Exit code 1 on any 'error' severity violations — blocks CI merge

Spectral: Rule-Based Governance at Scale

Spectral (by Stoplight) is more flexible than Redocly CLI — it uses a custom rule language that lets you enforce API style guides across an entire organization. The @stoplight/spectral-formats package includes the OpenAPI 3.x rulesets, and you can extend them with custom rules:

.spectral.yaml — enforcing your API style guide
extends:
  - spectral:oas

rules:
  # Custom: all path parameters must have descriptions
  path-params-must-have-description:
    description: Path parameters must have a description field.
    given: "$.paths.*.*.parameters[?(@.in=='path')]"
    severity: error
    then:
      field: description
      function: truthy

  # Custom: responses must include examples for 2xx
  response-2xx-must-have-examples:
    description: Successful responses must include content examples.
    given: "$.paths.*.*.responses[?(/^2/.test(@property))].content.*.examples"
    severity: warn
    then:
      function: truthy

# Run: npx @stoplight/spectral-cli lint openapi.yaml

Breaking Change Detection with OpenAPI Diff

Adding linting catches spec quality issues. Breaking change detection prevents you from accidentally shipping a spec that breaks existing client SDKs. oasdiff (Go-based, fast) compares two versions and categorizes changes by severity:

GitHub Actions step for breaking change detection
# .github/workflows/openapi.yml
- name: Check for breaking changes
  run: |
    # Compare current branch spec against main branch spec
    docker run --rm       -v ${{ github.workspace }}:/work       tufin/oasdiff breaking       /work/openapi-main.yaml       /work/openapi.yaml       --format text
  # Breaking changes = non-zero exit → CI fails

Breaking changes that oasdiff detects: removing paths, removing required request fields (adding is safe), changing response schema types, removing enum values (adding is safe), changing operationId (breaks generated clients immediately).

Five Common OpenAPI Mistakes (and How to Fix Them)

1. Missing operationId on Every Operation

operationId is technically optional in the spec, but practically mandatory if you use code generation. It becomes the function name in every generated client: client.payments.createPayment(). Without it, generators fall back to auto-generated names like postPaymentsUsingPost — unusable in real code. Lint for this: operation-operationId: error in Redocly.

2. Inlining Schemas Instead of Using $ref

Inlining a schema definition directly in a response body works but creates duplication that is painful to maintain. Every shared object — error responses, pagination envelopes, common entity shapes — should live in components/schemas and be referenced with $ref. Code generators also produce better type names from named schemas than from anonymous inline objects.

3. Documenting Only Happy Path Responses

Specs that only document 200 responses are misleading. Document at minimum: the primary success code (200, 201, 204 as appropriate), 400/422 for validation errors, 401 for authentication failures, 404 for not-found, and 500 for server errors. Use a shared components/responses section for common responses like NotFound to avoid repeating them across 50 operations.

4. No Examples on Request/Response Bodies

A spec without examples forces consumers to reverse-engineer valid payloads from schema type definitions. Add examples on request bodies and key responses with realistic values — not string: "string" placeholders. Mock servers (Prism, Wiretap) use examples to generate realistic mock responses, enabling frontend development before the backend is complete.

5. One Giant openapi.yaml File

A real API spec grows to thousands of lines. Maintain it as multiple files using $ref to link them, then bundle into a single file for rendering and code generation:

Multi-file OpenAPI structure — bundle before serving
openapi/
  openapi.yaml              # Root file — references everything else
  paths/
    payments.yaml           # Payment operation definitions
    subscriptions.yaml      # Subscription operation definitions
    webhooks.yaml           # Webhook definitions
  schemas/
    Payment.yaml            # $ref: './schemas/Payment.yaml'
    Subscription.yaml
    Error.yaml

# Bundle into a single file for rendering and codegen
npx @redocly/cli bundle openapi.yaml -o dist/openapi.bundle.yaml

API Mocking from OpenAPI Specs with Prism

One of OpenAPI's most underused capabilities: generating a fully functional mock server from the spec. Frontend teams can develop against the mock before the backend is built. Prism (by Stoplight) is the standard tool:

Starting a Prism mock server
# Install and start the mock server
npx @stoplight/prism-cli mock openapi.yaml

# Prism reads your spec and serves all defined endpoints:
# [CLI] POST http://127.0.0.1:4010/payments
# [CLI] GET  http://127.0.0.1:4010/payments/{id}

# Test it — Prism returns the first example from the spec
curl -X POST http://127.0.0.1:4010/payments   -H 'Content-Type: application/json'   -d '{"amount": 4999, "currency": "USD", "method": "card"}'

# Prism also validates your requests against the spec schema
# and returns 422 with validation details if the request is malformed

# Use --dynamic flag to generate random valid data instead of static examples
npx @stoplight/prism-cli mock openapi.yaml --dynamic

Prism validates both request and response formats against the spec schema. This means you can use it in tests to verify that your client is sending correctly-shaped requests — a useful contract testing layer that complements your API authentication testing.

Frequently Asked Questions

What is the difference between OpenAPI and Swagger?

Swagger was the original name for both the specification and the tooling created by Reverb Technologies. In 2016, Reverb donated version 2.0 to the OpenAPI Initiative (a Linux Foundation project), which renamed the specification "OpenAPI Specification." Swagger is now a brand name for SmartBear's commercial tooling (Swagger UI, Swagger Editor, SwaggerHub). The specification is OpenAPI; the tools are Swagger.

Should I use OpenAPI 3.0 or 3.1?

Use OpenAPI 3.1 for new projects. It provides full JSON Schema 2019-09 compatibility, native webhook support, and cleaner nullable type syntax. As of 2025, major tools (Swagger UI 5+, Redoc, Scalar, Speakeasy) support 3.1 fully. Check your toolchain before migrating existing 3.0 specs — the nullable: true → type: [T, "null"] migration requires automated codemods.

What is the best tool for rendering OpenAPI documentation?

It depends on your priority. Swagger UI (28,700+ GitHub stars) maximizes interactivity. Redoc (25,600+ stars) offers a polished three-panel layout better suited for public-facing docs. Scalar is the emerging choice for teams that want both — it combines Redoc's design quality with Swagger's interactivity, plus code generation in 15+ languages.

Can I generate an OpenAPI spec from existing code automatically?

Yes. FastAPI auto-generates OpenAPI 3.1 from type annotations; NestJS has @nestjs/swagger decorators; Spring Boot has springdoc-openapi. Code-first specs often have worse descriptions and examples than hand-authored design-first specs. The approaches are complementary: generate a baseline, then refine manually.

How do I validate an OpenAPI spec file?

Use Redocly CLI: npx @redocly/cli lint openapi.yaml. It validates against the OpenAPI 3.x schema, checks for broken $ref references, and enforces configurable style rules. Spectral (Stoplight) is the alternative with a broader plugin ecosystem. Both integrate into CI/CD via GitHub Actions.

How do I generate client SDKs from an OpenAPI spec?

OpenAPI Generator (openapi-generator.jar) supports 50+ client languages. Run: npx @openapitools/openapi-generator-cli generate -i openapi.yaml -g typescript-fetch -o ./client. For production-quality SDKs, Speakeasy and Fern generate idiomatic, typed clients that handle pagination, retries, and auth — used by Stripe and Anthropic for their official SDKs.

Is OpenAPI only for REST APIs?

OpenAPI is designed specifically for HTTP APIs, which includes REST and REST-like designs. It is not suited for GraphQL (which has its own SDL), gRPC (which uses .proto files), or WebSockets beyond simple upgrade scenarios. OpenAPI 3.1 added webhooks support for event-driven HTTP callbacks.

Validate & Format Your API Spec

Paste your OpenAPI YAML or JSON and get instant formatting and structure validation. Runs in your browser — no uploads required.

Open JSON Formatter →