OpenAPI 3.1 in Practice: Design-First APIs That Teams Actually Use

#1OpenAPI 3.1 design-first APIs that stay current
What we tested: We evaluated the tools and techniques described in this article using real developer workflows. All browser-based tools on this site run locally, your data never leaves your machine.
OpenAPI 3.1 only helps when the spec is the thing the team actually maintains.
The practical goal is simple: keep the contract current, use it to drive tests and code, and stop the drift that makes most specs useless.
#2Why Design-First Matters
There are two ways to work with OpenAPI:
Code-first: write the API, generate the spec from annotations. Fast to start, but the spec becomes stale because it is a build artifact, not the canonical source. Developers stop trusting it.
Design-first: write the OpenAPI spec first, use it to generate server stubs and clients, maintain the spec as the source of truth. Slower to start, but the spec stays accurate because all changes go through it.
Design-first wins at scale. When you have multiple teams, multiple client apps, or external API consumers, an accurate, living spec is the difference between smooth integrations and weeks of debugging.
#2What Changed in OpenAPI 3.1
OpenAPI 3.1 (released 2021, now the dominant version in 2026) has two headline changes:
#3Full JSON Schema alignment
OpenAPI 3.0 used a modified, incompatible subset of JSON Schema. 3.1 uses JSON Schema 2020-12 directly. This means:
# ❌ OpenAPI 3.0, nullable field (custom extension)
email:
type: string
nullable: true
# ✅ OpenAPI 3.1, standard JSON Schema
email:
type: ['string', 'null']
# or
email:
oneOf:
- type: string
- type: 'null'You can now use $ref alongside other keywords:
# ❌ OpenAPI 3.0, $ref alone, cannot add description or required
$ref: '#/components/schemas/User'
# ✅ OpenAPI 3.1, $ref with sibling keywords
allOf:
- $ref: '#/components/schemas/User'
description: "The authenticated user"#3Webhooks support
openapi: '3.1.0'
webhooks:
paymentCompleted:
post:
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/PaymentEvent'
responses:
'200':
description: Webhook received#2Writing a Maintainable OpenAPI Spec
A spec that gathers dust helps no one. These patterns keep it alive:
#3Start minimal, expand iteratively
openapi: '3.1.0'
info:
title: My API
version: '1.0.0'
description: |
## Authentication
All endpoints require `Authorization: Bearer <token>` header.
## Rate Limiting
100 requests per minute per API key. Returns 429 when exceeded.
## Errors
All errors follow the RFC 7807 Problem Details format.
servers:
- url: https://api.example.com/v1
description: Production
- url: https://staging-api.example.com/v1
description: Staging
- url: http://localhost:3000/v1
description: Local development
paths:
/users:
get:
summary: List users
operationId: listUsers # use camelCase operationIds, they become function names in clients
tags: [Users]
parameters:
- name: page
in: query
schema:
type: integer
minimum: 1
default: 1
- name: limit
in: query
schema:
type: integer
minimum: 1
maximum: 100
default: 20
responses:
'200':
description: Paginated user list
content:
application/json:
schema:
$ref: '#/components/schemas/UserListResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'429':
$ref: '#/components/responses/RateLimited'#3Keep components DRY
components:
schemas:
# Base user, used in list responses
UserSummary:
type: object
required: [id, email, createdAt]
properties:
id:
type: string
format: uuid
email:
type: string
format: email
createdAt:
type: string
format: date-time
# Full user, used in single-resource responses
User:
allOf:
- $ref: '#/components/schemas/UserSummary'
- type: object
properties:
profile:
$ref: '#/components/schemas/UserProfile'
role:
type: string
enum: [admin, user, viewer]
# Paginated envelope, reused for all list responses
PaginatedResponse:
type: object
required: [data, pagination]
properties:
pagination:
type: object
required: [page, limit, total]
properties:
page: { type: integer }
limit: { type: integer }
total: { type: integer }
UserListResponse:
allOf:
- $ref: '#/components/schemas/PaginatedResponse'
- type: object
properties:
data:
type: array
items:
$ref: '#/components/schemas/UserSummary'
responses:
Unauthorized:
description: Missing or invalid authentication
content:
application/json:
schema:
$ref: '#/components/schemas/ProblemDetail'
RateLimited:
description: Rate limit exceeded
headers:
Retry-After:
schema: { type: integer }
description: Seconds until rate limit resets
content:
application/json:
schema:
$ref: '#/components/schemas/ProblemDetail'
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
security:
- bearerAuth: []#2Generating TypeScript Clients
The main payoff of a maintained spec: never write an API client by hand again.
#3With openapi-typescript (types only, no runtime)
npx openapi-typescript openapi.yaml -o src/api/types.tsThis generates TypeScript interfaces. Combine with openapi-fetch for a typed fetch client:
import createClient from 'openapi-fetch';
import type { paths } from './api/types';
const client = createClient<paths>({ baseUrl: 'https://api.example.com/v1' });
// Fully typed, params, body, and response
const { data, error } = await client.GET('/users', {
params: { query: { page: 1, limit: 20 } },
});
if (data) {
console.log(data.data[0].email); // TypeScript knows the shape
}#3With openapi-generator-cli (full SDK)
npx @openapitools/openapi-generator-cli generate \
-i openapi.yaml \
-g typescript-fetch \
-o src/api/generated \
--additional-properties=supportsES6=true#2Contract Testing
Contract tests verify that your actual running API matches the spec, catching drift before it reaches production.
#3Dredd
npm install --save-dev dredd
# Run against local server
dredd openapi.yaml http://localhost:3000/v1#3Custom Vitest contract tests
// tests/contract/spec-compliance.test.ts
import { describe, it, expect, beforeAll } from 'vitest';
import SwaggerParser from '@apidevtools/swagger-parser';
import Ajv from 'ajv';
let spec: any;
let ajv: Ajv;
beforeAll(async () => {
spec = await SwaggerParser.dereference('openapi.yaml');
ajv = new Ajv({ allErrors: true });
});
describe('GET /users contract', () => {
it('response matches schema', async () => {
const res = await fetch('http://localhost:3000/v1/users');
const data = await res.json();
const schema = spec.paths['/users'].get.responses['200']
.content['application/json'].schema;
const validate = ajv.compile(schema);
expect(validate(data)).toBe(true);
if (!validate(data)) {
console.error(ajv.errorsText(validate.errors));
}
});
});#2Best Free Tools for OpenAPI in 2026
| Tool | Best for | Free tier |
|---|---|---|
| Swagger Editor | Editing with inline validation | Full |
| Stoplight Studio | Visual design-first authoring | Free tier |
| Redoc | Beautiful API documentation | Full OSS |
| Scalar | Modern interactive docs | Full OSS |
| AllDevToolsHub API Tester | Testing endpoints from browser | Full |
#2Common Mistakes
- Using
type: string\nnullable: true(3.0 syntax) in a 3.1 spec. Validators will accept it but clients and generators will handle it incorrectly. Usetype: ['string', 'null']in 3.1. - Not using
operationId. WithoutoperationId, code generators create ugly function names from the path and method. Use camelCase operationIds likelistUsers,createUser,getUserById. - One massive YAML file. Split large specs using
$refto external files. Most validators and generators support multi-file specs. - Only documenting the happy path. Document every 4xx and 5xx response your API can return. Contract tests cannot catch undocumented errors.
- Checking the spec into the same PR as the implementation. This defeats the design-first principle. The spec PR should be reviewed and approved first; the implementation PR links to it.
#2Frequently Asked Questions
#3Should I use OpenAPI 3.0 or 3.1?
Use 3.1 for new projects. The JSON Schema alignment alone makes it worth it, you get better tooling, correct nullable handling, and accurate codegen. Only stick with 3.0 if a critical tool in your workflow (like an older AWS API Gateway version) does not support 3.1 yet.
#3How do I handle authentication in OpenAPI?
Define securitySchemes in components and apply globally with security: at the root level. Override at the operation level for endpoints that don't require auth (like POST /auth/login). See the example above.
#3Can I generate the spec from my existing Express/FastAPI code?
Yes, code-first tools like tsoa (TypeScript), FastAPI (Python), or swagger-jsdoc generate specs from annotations. This is a valid approach when you have a large existing codebase. The trade-off is that the spec is a generated artifact, so it requires discipline to keep annotations up-to-date.
#3How do I share the spec with frontend teams?
Commit openapi.yaml to the repo root. Add a CI step that validates it on every PR using npx @redocly/cli lint openapi.yaml. Set up auto-generated docs on a /docs route using Redoc or Scalar. Publish the TypeScript client as an internal npm package generated from the spec.
Written by Rahul Jalavadiya, founder of AllDevToolsHub. All tools run locally in your browser.
#2Sources / Further reading
- OpenAPI Initiative - OpenAPI Specification 3.1.0
- IETF - RFC 9110: HTTP Semantics
- IETF - RFC 7807: Problem Details for HTTP APIs
- JSON Schema - Official documentation
#2Try These Tools on AllDevToolsHub
- OpenAPI Validator — Validate OpenAPI 3.1 specs against the official schema to catch structural errors
- API Client Generator — Generate type-safe API clients from OpenAPI specs for TypeScript, Python, and more
- REST API Tester — Test API endpoints designed from your OpenAPI spec with configurable requests
All tools run entirely in your browser. No sign-up, no data upload, no server round-trips.
#2Try These Tools
Quick Summary
>- A practical guide to OpenAPI 3.1 in 2026. Covers the JSON Schema alignment improvements, writing contracts your team will maintain, generating server stubs and TypeScript clients, automated contract testing, and the best free tools for editing and validating OpenAPI specs without leaving your browser.
Key Takeaways
- Design-first API development starts with the OpenAPI specification — write the spec before writing any code, then generate servers, clients, and docs from it.
- OpenAPI 3.1 aligns with JSON Schema draft 2020-12 — this means full support for exclusiveMinimum, type arrays, and $ref across schemas.
- Code generation from OpenAPI specs ensures your implementation matches the contract — tools like openapi-generator produce type-safe clients and server stubs.
When to use it
- Designing a REST API contract before implementation — share the OpenAPI spec with frontend and backend teams for parallel development.
- Generating type-safe TypeScript clients from an OpenAPI spec for frontend consumption.
- Validating API requests and responses against the OpenAPI spec in CI/CD to catch contract violations.
Common Mistakes
- Writing the OpenAPI spec after the code — this leads to specs that drift from the actual implementation.
- Not validating the spec before code generation — errors in the spec propagate to all generated code.
- Using OpenAPI 3.0 features when 3.1 is available — 3.1 adds JSON Schema compatibility, webhook definitions, and improved $ref handling.
OpenAPI 3.1 in Practice: Design-First APIs That Teams Actually Use, Frequently Asked
What is design-first API development?
Design-first means writing the OpenAPI specification before implementing the API. The spec becomes the contract that drives code generation, documentation, testing, and client SDK creation. This ensures the API is designed for consumers first.
What is new in OpenAPI 3.1 vs 3.0?
OpenAPI 3.1 aligns with JSON Schema 2020-12 (full compatibility), adds webhook definitions, supports $ref alongside other keywords, and introduces components.pathItems for reusable path definitions.
Tools Mentioned in This Article
JS/TS Beautify/Minify
Lightweight JavaScript/TypeScript formatter and minifier.
OpenAPI / Swagger Validator
Validate OpenAPI 3.x and Swagger 2.x specifications with detailed error reporting.
JSON to TypeScript
Convert JSON objects into TypeScript interfaces instantly.
Regex Tester
Test and debug regular expressions with live matches.
Tools, tactics, and toughened-up tips, once a week
New tools, deep-dives on developer workflows, and the occasional gem we found this week. No spam, no tracking. Unsubscribe anytime.
Found an error or have feedback?
We correct errors quickly and document changes in our changelog. Report issues at support@alldevtoolshub.com.