OpenAPI & Swagger Guide: Document Your API Like a Pro
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:
| Feature | 2.0 (Swagger) | 3.0 | 3.1 (current) |
|---|---|---|---|
| Schema reuse | definitions | components/schemas | components/schemas |
| Request body | in: body parameter | requestBody object | requestBody object |
| Nullable types | Not supported natively | nullable: true | type: [T, "null"] |
| JSON Schema compat | Subset only | Draft 7 subset | Full 2019-09 compat |
| Webhooks | No | No | Native webhooks object |
| Paths required | Yes | Yes | No (webhook-only specs valid) |
| Tooling support | Universal (legacy) | Universal | Mature (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: '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 foundKey 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.
| Tool | GitHub Stars | Try-It-Out | Layout | OAS 3.1 | Best For |
|---|---|---|---|---|---|
| Swagger UI | 28,700+ | Yes (built-in) | Single-column | v5+ (full) | Internal dev portals, recognizable UX |
| Redoc | 25,600+ | No (read-only) | 3-panel | Full | Public-facing docs, polished design |
| Scalar | ~10,000+ | Yes + codegen | 3-panel + sidebar | Full | New projects, best DX |
| Stoplight Elements | ~1,600+ | Yes | 3-panel + mock server | Full | Embedded 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.
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.jsonin development. - NestJS (TypeScript):
@nestjs/swaggerreads decorators and TypeScript types to produce the spec. - Spring Boot (Java):
springdoc-openapigenerates from@Operationannotations.
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.
# 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
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 mergeSpectral: 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:
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.yamlBreaking 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/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 failsBreaking 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:
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.yamlAPI 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:
# 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 --dynamicPrism 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 →