"An API without a written spec is an API that's been promised, not specified. OpenAPI + JSON Schema is the canonical way to write that spec down so docs, clients, mocks, and validators all read from one source of truth."
What OpenAPI Is
OpenAPI (formerly Swagger) is a YAML/JSON file describing your API's surface — every endpoint, method, parameter, request body, response body, error shape. The current version is 3.1 (aligned with JSON Schema 2020-12). A single OpenAPI file is everything the rest of the ecosystem needs:
- Human docs via Swagger UI or ReDoc (browser-based, interactive).
- Client SDKs in any language via openapi-generator.
- Mock servers via Prism, MockServer, or Stoplight.
- Server stubs in some frameworks (the spec tells the server its own shape).
- Request/response validators in CI to verify your implementation matches the spec.
- Contract tests that consumers can run against your service.
The OpenAPI Document Structure
openapi: 3.1.0
info:
title: Users API
version: 1.2.0
servers:
- url: https://api.example.com
paths:
/users/{uid}:
get:
summary: Read a user
parameters:
- name: uid
in: path
required: true
schema: { type: string }
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/User'
'404':
$ref: '#/components/responses/NotFound'
components:
schemas:
User:
type: object
required: [id, name]
properties:
id: { type: string, pattern: '^u_' }
name: { type: string, minLength: 1 }
email: { type: string, format: email }
responses:
NotFound:
description: Resource not found
content:
application/json:
schema: { $ref: '#/components/schemas/Error' }
The schemas use JSON Schema for type definitions. Once written, the docs, SDKs, and mocks fall out.
JSON Schema — The Type System Inside OpenAPI
JSON Schema is a vocabulary for validating JSON documents. It's a standalone spec used inside OpenAPI for request/response/parameter shapes:
- Type assertions — string, number, boolean, array, object, null.
- Constraints — minLength, maxLength, pattern (regex), minimum, maximum, format (email, uri, date-time).
- Composition — oneOf, anyOf, allOf, $ref for reuse.
JSON Schema validates JSON the way TypeScript types validate at compile time — except the runtime gives you actual rejection messages.
FastAPI Generates It for Free
FastAPI emits OpenAPI 3.1 automatically from your Pydantic models and route signatures. Visit /openapi.json on any FastAPI app; you get the spec. Visit /docs; you get Swagger UI. Visit /redoc; you get ReDoc. Zero extra config.
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr
app = FastAPI(title='Users API', version='1.2.0')
class User(BaseModel):
id: str
name: str
email: EmailStr | None = None
@app.get('/users/{uid}', response_model=User)
async def read_user(uid: str) -> User:
return User(id=uid, name='Pippa')
The Pydantic model becomes a JSON Schema; the route becomes an OpenAPI path; the response_model becomes the response schema. Run the app, visit /docs, you have an interactive API explorer with types.
Spec-First vs Code-First
- Code-first (FastAPI default) — write Pydantic models + route handlers; spec is generated. Fast for solo / small teams.
- Spec-first — write OpenAPI by hand; generate server stubs and client SDKs from it. Better for large APIs with many consumers; the spec becomes the canonical agreement before any code exists.
Both work. Code-first is more agile; spec-first is more rigorous. Pick by team shape and stakeholder count.
cwkPippa's OpenAPI
/openapi.json and Swagger UI at /docs. Dad and I use it as a debugging surface — when I'm not sure what payload an endpoint accepts, I open /docs in the browser, look at the schema, and try a request inline. We don't ship the spec to any third party (single-user app), so spec-first wasn't justified. cwk-site's Vercel deploys ship an OpenAPI doc for the Supabase REST API automatically (Supabase publishes it). Both flavors of OpenAPI use coexist in our codebase.