What OpenAPI Is and Why It Matters
| Without OpenAPI | With OpenAPI |
|---|---|
| Documentation written manually, drifts from code | Docs auto-generated from single source of truth |
| Client types written by hand, break on API changes | TypeScript/Java types generated from spec |
| Integration tests catch schema mismatches late | Contract tests validate every request/response |
| API changes break clients without warning | Breaking changes detected by spec diff tools |
| Mock servers require custom implementation | Mock server generated from spec with realistic data |
OpenAPI 3.1 and JSON Schema Alignment
OpenAPI 3.1 fully adopts JSON Schema Draft 2020-12. This means every schema keyword you know from JSON Schema works directly in your OpenAPI spec:
| Feature | OpenAPI 3.0 | OpenAPI 3.1 |
|---|---|---|
| JSON Schema alignment | Partial (extended subset) | Full Draft 2020-12 |
| nullable | nullable: true keyword | type: ["string", "null"] (standard JSON Schema) |
| if/then/else | Not supported | Fully supported |
| $dynamicRef | Not supported | Supported |
| examples | Single example per schema | examples array (JSON Schema standard) |
| Content types | Limited | Supports webhooks, JSON/YAML/binary |
Anatomy of an OpenAPI Spec
Minimal OpenAPI 3.1 specificationyaml
1openapi: 3.1.02info:3 title: User API4 version: 1.0.05 description: Manage user accounts67servers:8 - url: https://api.example.com/v19 description: Production1011paths:12 /users:13 get:14 operationId: listUsers15 summary: List all users16 parameters:17 - name: page18 in: query19 schema:20 type: integer21 default: 122 - name: per_page23 in: query24 schema:25 type: integer26 default: 2027 maximum: 10028 responses:29 "200":30 description: A paginated list of users31 content:32 application/json:33 schema:34 $ref: "#/components/schemas/UserListResponse"3536 post:37 operationId: createUser38 summary: Create a new user39 requestBody:40 required: true41 content:42 application/json:43 schema:44 $ref: "#/components/schemas/CreateUserRequest"45 responses:46 "201":47 description: User created48 content:49 application/json:50 schema:51 $ref: "#/components/schemas/User"52 "422":53 description: Validation error54 content:55 application/json:56 schema:57 $ref: "#/components/schemas/ErrorResponse"5859components:60 schemas:61 User:62 type: object63 required: [id, name, email]64 properties:65 id:66 type: string67 format: uuid68 name:69 type: string70 minLength: 171 email:72 type: string73 format: email74 plan:75 type: string76 enum: [free, pro, enterprise]77 default: free78 created_at:79 type: string80 format: date-time8182 CreateUserRequest:83 type: object84 required: [name, email]85 properties:86 name:87 type: string88 minLength: 189 maxLength: 10090 email:91 type: string92 format: email93 plan:94 type: string95 enum: [free, pro, enterprise]9697 UserListResponse:98 type: object99 properties:100 data:101 type: array102 items:103 $ref: "#/components/schemas/User"104 total:105 type: integer106 page:107 type: integer108 per_page:109 type: integer110111 ErrorResponse:112 type: object113 properties:114 error:115 type: string116 details:117 type: array118 items:119 type: object120 properties:121 field:122 type: string123 message:124 type: stringPolymorphism: oneOf, anyOf, discriminator
Discriminated union with discriminatoryaml
1components:2 schemas:3 Event:4 oneOf:5 - $ref: "#/components/schemas/UserCreated"6 - $ref: "#/components/schemas/OrderPlaced"7 discriminator:8 propertyName: event_type9 mapping:10 user.created: "#/components/schemas/UserCreated"11 order.placed: "#/components/schemas/OrderPlaced"1213 UserCreated:14 type: object15 required: [event_type, user_id, email]16 properties:17 event_type:18 type: string19 const: user.created20 user_id:21 type: string22 email:23 type: string24 format: email2526 OrderPlaced:27 type: object28 required: [event_type, order_id, total]29 properties:30 event_type:31 type: string32 const: order.placed33 order_id:34 type: string35 total:36 type: numberWhen to Use discriminator
Use
discriminator when your API returns different shapes based on a type field. Code generators use it to produce proper tagged unions (TypeScript discriminated unions, Java sealed classes, C# pattern matching).Code Generation
TypeScript Types
Generate TypeScript types from OpenAPIbash
1# Install openapi-typescript2npm install -D openapi-typescript34# Generate types from a local spec5npx openapi-typescript ./openapi.yaml -o ./src/api-types.ts67# Generate from a remote URL8npx openapi-typescript https://api.example.com/openapi.json -o ./src/api-types.tsGenerated types (example output)typescript
1// Auto-generated from openapi.yaml2export interface components {3 schemas: {4 User: {5 id: string;6 name: string;7 email: string;8 plan?: "free" | "pro" | "enterprise";9 created_at?: string;10 };11 CreateUserRequest: {12 name: string;13 email: string;14 plan?: "free" | "pro" | "enterprise";15 };16 };17}1819// Use in your fetch calls with full type safety20type User = components["schemas"]["User"];Other Languages
| Language | Tool | Command |
|---|---|---|
| TypeScript | openapi-typescript | npx openapi-typescript spec.yaml -o types.ts |
| Java | openapi-generator | npx @openapitools/openapi-generator-cli generate -i spec.yaml -g java |
| C# | NSwag | nswag openapi2csclient /input:spec.yaml /output:Client.cs |
| Python | openapi-python-client | openapi-python-client generate --path spec.yaml |
| Go | oapi-codegen | oapi-codegen -generate types spec.yaml > types.go |
Contract Testing
Mock Server with Prism
Run a mock server from your specbash
1# Install Prism2npm install -g @stoplight/prism-cli34# Start mock server on port 40105prism mock openapi.yaml67# Test against the mock8curl http://localhost:4010/users9# Returns realistic sample data based on your schemaValidation Proxy
Validate real API responses against the specbash
1# Run Prism as a validation proxy in front of your real API2prism proxy openapi.yaml http://localhost:300034# Send requests through the proxy (port 4010)5curl http://localhost:4010/users67# Prism validates:8# - Request parameters match the spec9# - Request body matches the schema10# - Response status code is defined in the spec11# - Response body matches the schema12# Violations are logged with detailsAutomated Testing with Schemathesis
Property-based API testingbash
1# Install Schemathesis2pip install schemathesis34# Run automated tests against your API5schemathesis run http://localhost:3000/openapi.json67# Schemathesis generates hundreds of valid and edge-case8# requests based on your schema, testing for:9# - 500 errors on valid inputs10# - Schema violations in responses11# - Content-type mismatches12# - Undocumented status codesDocumentation Generation
| Tool | Style | Features |
|---|---|---|
| Swagger UI | Interactive | Try-it-out console, parameter forms, built-in auth |
| Redoc | Three-panel | Clean read-only docs, nested schema display, search |
| Stoplight | Full platform | Visual editor, hosted docs, style guides, mock server |
| Scalar | Modern | Beautiful UI, dark mode, code samples in 20+ languages |
Versioning Your API Spec
| Change Type | Example | Breaking? | Strategy |
|---|---|---|---|
| Add optional field | New "avatar_url" property | No | Safe to add without version bump |
| Add new endpoint | POST /users/invite | No | Safe to add without version bump |
| Remove a field | Drop "legacy_id" property | Yes | Deprecate first, remove in next major |
| Change field type | price: string -> number | Yes | New major version or new field name |
| Make optional field required | email becomes required | Yes | New major version |
| Rename an endpoint | /users -> /accounts | Yes | Keep old endpoint, add redirect |
Note
Use tools like
oasdiff or optic to automatically detect breaking changes in CI. Run them on pull requests to prevent accidental contract breakage.Try These Tools
Continue Learning
Frequently Asked Questions
What is OpenAPI?
OpenAPI (formerly Swagger) is a specification for describing REST APIs as a machine-readable JSON or YAML document. It defines endpoints, request/response schemas, authentication, and error formats. The specification is maintained by the OpenAPI Initiative and is the industry standard for API documentation and contract design.
How does OpenAPI relate to JSON Schema?
OpenAPI 3.1 fully aligns with JSON Schema Draft 2020-12. This means every schema in an OpenAPI document is a valid JSON Schema, and you can use all JSON Schema keywords (if/then/else, $dynamicRef, etc.) directly. Earlier versions (3.0 and below) used a subset of JSON Schema with some incompatibilities.
What is contract-first API design?
Contract-first means you write the OpenAPI specification before writing any code. The spec becomes the source of truth: server stubs, client SDKs, documentation, and tests are all generated from it. This ensures consistency between what the API promises and what it delivers.
Can I generate TypeScript types from an OpenAPI spec?
Yes. Tools like openapi-typescript generate TypeScript types directly from an OpenAPI 3.x spec. The generated types include all request parameters, response bodies, and error shapes, giving you end-to-end type safety from spec to client code.
What is Prism and how does it help with contract testing?
Prism (by Stoplight) is a mock server and validation proxy that reads your OpenAPI spec. As a mock server, it returns realistic responses based on schema examples. As a validation proxy, it sits between client and server and flags any request or response that violates the spec.
Should I write OpenAPI specs in JSON or YAML?
YAML is the most common authoring format because it is more readable and supports comments. JSON is used for programmatic consumption and storage. Most OpenAPI tools accept both. Write in YAML, distribute in JSON if needed.