API documentation that drifts from the actual API is worse than no documentation — it creates false confidence and wastes engineering hours debugging. The tools in this guide all start from your OpenAPI spec (the source of truth), rendering documentation that’s always accurate and shareable without manual updates.
OpenAPI Spec as the Foundation
Every tool in this guide consumes an OpenAPI spec. Write one if you don’t have one:
# openapi.yaml
openapi: "3.1.0"
info:
title: Payments API
version: "2.0.0"
description: |
Process payments, manage subscriptions, and handle refunds.
## Authentication
All endpoints require `Bearer` token authentication.
servers:
- url: https://api.yourcompany.com/v2
description: Production
- url: https://staging-api.yourcompany.com/v2
description: Staging
security:
- bearerAuth: []
paths:
/payments:
post:
operationId: createPayment
summary: Create a payment
tags: [Payments]
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreatePaymentRequest'
examples:
basic:
summary: Basic card payment
value:
amount: 2999
currency: usd
source: tok_visa
responses:
"201":
description: Payment created
content:
application/json:
schema:
$ref: '#/components/schemas/Payment'
"402":
description: Payment failed
content:
application/json:
schema:
$ref: '#/components/schemas/PaymentError'
components:
schemas:
CreatePaymentRequest:
type: object
required: [amount, currency, source]
properties:
amount:
type: integer
description: Amount in smallest currency unit (cents for USD)
example: 2999
currency:
type: string
enum: [usd, eur, gbp]
example: usd
source:
type: string
description: Stripe payment token or customer ID
example: tok_visa
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
Stoplight (Collaboration-First)
Stoplight Studio is the most complete API design and documentation platform. Teams edit the OpenAPI spec in a visual editor, publish docs to a branded portal, and mock the API from the same spec.
Host the published docs as a static site:
# Install Stoplight CLI
npm install -g @stoplight/cli
# Validate your spec
stoplight lint openapi.yaml
# Push to Stoplight Platform
stoplight push --ci-token $STOPLIGHT_CI_TOKEN openapi.yaml
# Build static docs site
stoplight export --format html --output ./docs/api
Serve the static output:
server {
listen 443 ssl http2;
server_name docs.yourcompany.com;
root /var/www/api-docs;
index index.html;
location / { try_files $uri $uri/ /index.html; }
}
Integrate into CI to publish on every spec change:
# .github/workflows/docs.yml
name: Publish API Docs
on:
push:
paths:
- 'openapi.yaml'
branches: [main]
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Stoplight CLI
run: npm install -g @stoplight/cli
- name: Publish docs
run: stoplight push --ci-token ${{ secrets.STOPLIGHT_TOKEN }} openapi.yaml
Scalar (Modern, Self-Hosted)
Scalar is an open-source API reference documentation UI with a clean, modern design and built-in API client (send requests directly from the docs). It replaces Swagger UI for teams who want something better looking.
Serve as a Docker container:
# docker-compose.yml
services:
api-docs:
image: scalar/api-reference:latest
ports:
- "3000:3000"
volumes:
- ./openapi.yaml:/app/openapi.yaml:ro
environment:
- SCALAR_CONFIG_FILE=/app/openapi.yaml
- SCALAR_BASE_PATH=/
Or embed in your existing API server (Node.js/Express):
// server.js
import express from 'express';
import { apiReference } from '@scalar/express-api-reference';
import { readFileSync } from 'fs';
const app = express();
const spec = readFileSync('./openapi.yaml', 'utf-8');
app.use('/docs', apiReference({
spec: { content: spec },
theme: 'default',
// Show "Try It" button using your staging server
defaultHttpClient: { targetKey: 'node', clientKey: 'axios' },
servers: [{ url: 'https://staging-api.yourcompany.com/v2' }],
}));
Embed in Go with chi:
import (
"github.com/scalar/scalar/packages/go-scalar"
"github.com/go-chi/chi/v5"
)
r := chi.NewRouter()
r.Get("/docs", scalar.Handler(scalar.Options{
SpecURL: "/openapi.yaml",
Title: "Payments API Reference",
}))
r.Get("/openapi.yaml", func(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "./openapi.yaml")
})
Redoc (Embeddable, Clean)
Redoc is the standard self-hosted OpenAPI renderer. It’s fast, well-maintained, and integrates into any HTML page.
Static HTML deployment:
<!-- docs/index.html -->
<!DOCTYPE html>
<html>
<head>
<title>API Reference</title>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
<style>
body { margin: 0; padding: 0; }
</style>
</head>
<body>
<redoc spec-url='./openapi.yaml'></redoc>
<script src="https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js"></script>
</body>
</html>
Build a static bundle with redocly CLI:
npm install -g @redocly/cli
# Validate spec
redocly lint openapi.yaml
# Bundle into single HTML file (no CDN dependency)
redocly build-docs openapi.yaml --output docs/index.html
# Preview locally
redocly preview-docs openapi.yaml
Split large specs into multiple files (better for team editing):
# openapi.yaml can reference other files
paths:
/payments:
$ref: './paths/payments.yaml'
/users:
$ref: './paths/users.yaml'
# Bundle into one file
redocly bundle openapi.yaml --output openapi-bundled.yaml
Keep Docs in Sync with Code
Generate OpenAPI specs from code annotations to prevent drift:
Go (swaggo):
// @Summary Create a payment
// @Tags Payments
// @Accept json
// @Produce json
// @Param request body CreatePaymentRequest true "Payment details"
// @Success 201 {object} Payment
// @Failure 402 {object} PaymentError
// @Router /payments [post]
func CreatePayment(w http.ResponseWriter, r *http.Request) {
// ...
}
swag init -g cmd/api/main.go --output ./docs/swagger
Python FastAPI (auto-generated):
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI(title="Payments API", version="2.0.0")
class PaymentRequest(BaseModel):
amount: int
currency: str
source: str
@app.post("/payments", status_code=201)
async def create_payment(request: PaymentRequest) -> Payment:
"""Create a new payment."""
# FastAPI generates OpenAPI spec automatically
...
# Docs available at /docs and /redoc
# Export spec: GET /openapi.json
Tool Comparison
| Tool | Hosting | Try It API | Cost | Best For |
|---|---|---|---|---|
| Stoplight | SaaS/self | Via Stoplight | Free tier + paid | Design-first teams |
| Scalar | Self-hosted | Built-in | Free (OSS) | Modern UI, embedded |
| Redoc | Self-hosted | No | Free (OSS) | Clean, lightweight |
| Swagger UI | Self-hosted | Built-in | Free (OSS) | Maximum compatibility |
For most remote teams: use Scalar for external developer-facing docs, Redoc for internal API references, and Stoplight when your team does API-first design.
Related Reading
- Best Tools for Remote Team API Mocking
- Best Tools for Remote Team Load Testing
- Remote Team Code Review Checklist Template
- Example OpenAPI specification snippet
Related Articles
- Remote Team Documentation Culture
- How to Manage Remote Team Documentation Debt: Complete Guide
- Example OpenAPI specification snippet
- Best Tools for Remote Team Documentation Reviews 2026
- How to Create Onboarding Documentation for Remote Teams
Built by theluckystrike — More at zovo.one