OpenAPI Specification Rules
1. Core Principles
Specification Structure
- Use OpenAPI 3.0+ (prefer 3.1 for full JSON Schema compatibility)
- Write specs in YAML for readability — convert to JSON for tooling if needed
- Organize large specs using
$refto external files - Keep the spec as the single source of truth for your API contract
File Organization
openapi/
openapi.yaml # Root specification file
paths/
users.yaml # /users endpoints
orders.yaml # /orders endpoints
schemas/
User.yaml # User schema
Order.yaml # Order schema
common/
Pagination.yaml # Shared pagination schema
Error.yaml # Shared error schema
parameters/
common.yaml # Shared parameters (page, limit, etc.)
responses/
errors.yaml # Shared error responses
examples/
users.yaml # Example payloads
Root Document Structure
openapi: 3.1.0
info:
title: Order Management API
description: >-
API for managing orders, products, and customer information.
version: 1.2.0
contact:
name: API Team
email: api-team@example.com
servers:
- url: https://api.example.com/v1
description: Production
- url: https://staging-api.example.com/v1
description: Staging
tags:
- name: Users
- name: Orders
paths:
/users:
$ref: './paths/users.yaml'
/orders:
$ref: './paths/orders.yaml'
2. Schema Design
Schema Best Practices
- Define all schemas in
components/schemasand reference them - Use
requiredarrays explicitly — do not rely on defaults - Add
descriptionto every property - Set
formatfor strings (date-time, email, uri, uuid) - Use
examplevalues that are realistic and self-explanatory
components:
schemas:
User:
type: object
required:
- id
- email
- name
- role
properties:
id:
type: string
format: uuid
description: Unique user identifier
example: "550e8400-e29b-41d4-a716-446655440000"
email:
type: string
format: email
description: User's email address
example: "john.doe@example.com"
name:
type: string
minLength: 1
maxLength: 100
description: User's display name
example: "John Doe"
role:
$ref: '#/components/schemas/UserRole'
createdAt:
type: string
format: date-time
description: Account creation timestamp
readOnly: true
example: "2024-01-15T10:30:00Z"
UserRole:
type: string
enum:
- admin
- user
- viewer
description: User permission level
Request/Response Separation
- Define separate schemas for create, update, and response
- Use
readOnlyfor server-generated fields (id, createdAt) - Use
writeOnlyfor sensitive fields (password) - Never reuse the same schema for both request and response when they differ
schemas:
CreateUserRequest:
type: object
required:
- email
- name
- password
properties:
email:
type: string
format: email
name:
type: string
minLength: 1
password:
type: string
format: password
minLength: 8
writeOnly: true
UserResponse:
type: object
required:
- id
- email
- name
- role
- createdAt
properties:
id:
type: string
format: uuid
readOnly: true
email:
type: string
format: email
name:
type: string
role:
$ref: '#/components/schemas/UserRole'
createdAt:
type: string
format: date-time
readOnly: true
Composition Patterns
- Use
allOffor inheritance/extension - Use
oneOfwith discriminator for polymorphic types (see references/reusable-components.md for example) - Use
$reffor shared components — do not duplicate schemas
3. Path and Operation Design
Operation Definition
paths:
/users:
get:
operationId: listUsers
summary: List all users
description: >-
Returns a paginated list of users. Supports filtering
by role and status.
tags:
- Users
parameters:
- $ref: '#/components/parameters/PageParam'
- $ref: '#/components/parameters/LimitParam'
- name: role
in: query
schema:
$ref: '#/components/schemas/UserRole'
description: Filter by user role
responses:
'200':
description: Successful response
content:
application/json:
schema:
$ref: '#/components/schemas/UserListResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'500':
$ref: '#/components/responses/InternalError'
security:
- bearerAuth: []
post:
operationId: createUser
summary: Create a new user
tags:
- Users
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateUserRequest'
examples:
admin:
summary: Create admin user
value:
email: "admin@example.com"
name: "Admin User"
password: "secureP@ss123"
responses:
'201':
description: User created successfully
content:
application/json:
schema:
$ref: '#/components/schemas/UserResponse'
'400':
$ref: '#/components/responses/BadRequest'
'409':
$ref: '#/components/responses/Conflict'
Naming Rules
operationId: camelCase, verb+noun (listUsers,createOrder,getUserById)summary: Short description (under 80 characters)description: Detailed explanation including business rulestags: Group related endpoints together
4. Reusable Components
See references/reusable-components.md for shared parameters, shared responses, security schemes, and composition pattern YAML examples.
Component Rules
- Define shared parameters (
PageParam,LimitParam) incomponents/parameters - Define shared error responses (
BadRequest,Unauthorized,NotFound,Conflict,InternalError) incomponents/responses - Define security schemes (
bearerAuth,apiKeyAuth) incomponents/securitySchemes - Apply global security and override per operation when needed
6. Validation and Linting
Validation Tools
- Spectral: Configurable OpenAPI linter with custom rules
- openapi-generator validate: Syntax and structure validation
- Redocly CLI: Validation, bundling, and preview
Spectral Configuration
# .spectral.yaml
extends:
- spectral:oas
rules:
operation-operationId: error
operation-description: warn
operation-tags: error
info-contact: warn
oas3-api-servers: error
no-$ref-siblings: error
# Custom rules
path-must-use-kebab-case:
given: "$.paths[*]~"
then:
function: pattern
functionOptions:
match: "^(/[a-z][a-z0-9-]*)+$"
severity: error
message: "Path segments must use kebab-case"
CI Integration
lint-openapi:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Lint OpenAPI spec
uses: stoplightio/spectral-action@latest
with:
file_glob: 'openapi/openapi.yaml'
- name: Validate spec
run: npx @redocly/cli lint openapi/openapi.yaml
7. Documentation Generation
Tool Selection
| Tool | Output | Use Case |
|---|---|---|
| Redoc | Static HTML | Public API documentation |
| Swagger UI | Interactive | Developer portal/testing |
| Stoplight | Hosted docs | Team collaboration |
| openapi-generator | Code SDKs | Client library generation |
Documentation Rules
- Every operation must have
summaryanddescription - Every parameter must have
description - Every schema property must have
descriptionandexample - Use
externalDocsfor linking to guides and tutorials - Include realistic examples for all request/response bodies
8. Code Generation
Generator Rules
- Generate client SDKs and server stubs from the spec, not the other way around
- Treat generated code as build artifacts — do not manually edit
- Configure the generator to match your project's code style
- Run generation in CI to keep code and spec in sync
# Generate TypeScript client
npx @openapitools/openapi-generator-cli generate \
-i openapi/openapi.yaml \
-g typescript-axios \
-o src/generated/api
9. Anti-Patterns
- Writing code first and generating the spec from code
- Using
additionalProperties: trueby default - Defining inline schemas instead of reusable components
- Missing error responses — document all possible error codes
- Using generic descriptions like "Returns data" or "Request body"
- Forgetting
requiredarrays — all required fields must be listed - Duplicating schemas instead of using
$ref - Mixing API versions in a single spec file
- Skipping validation in CI — spec drift causes integration failures
- Not versioning the spec alongside the codebase
10. Related Skills
- api-design: REST API design principles and patterns
- code-quality: General code quality for generated code review
- ci-cd: CI pipeline integration for spec validation
11. Additional References
- OpenAPI Specification — Official OpenAPI specification
- Swagger Editor — Browser-based spec editor
- Spectral — OpenAPI linting tool
- Redocly CLI — OpenAPI validation and bundling