OpenAPI Specifications
OpenAPI Specifications serve as the single source of truth for API contracts, enabling code generation, validation, and documentation.
Overview
OpenAPI (formerly Swagger) is the industry-standard specification for REST APIs. We use OpenAPI 3.x specifications as contracts between frontend and backend teams, enabling independent development and automated validation.
Schema-First Development
Define OpenAPI specs before implementation. This enables:
- Early API design feedback
- Automated code generation for clients
- Contract validation in tests
- Interactive API documentation
- Consistent error responses across all endpoints
Core Principles
- Schema-First: Define API contracts before implementation
- Version Control: Store OpenAPI specs alongside code
- Single Source of Truth: Generate code and documentation from specs
- Semantic Versioning: Version APIs appropriately (v1, v2)
- Validation: Validate requests/responses against specs in tests
OpenAPI 3.x Specification Structure
Complete Payment API Example
openapi: 3.0.3
info:
title: Payment API
version: 1.0.0
description: |
Payment processing API.
## Authentication
All endpoints require OAuth 2.0 Bearer token authentication.
## Rate Limiting
- 1000 requests per hour per user
- 10000 requests per hour per organization
## Error Handling
All errors follow RFC 7807 Problem Details format.
contact:
name: API Support
email: api-[email protected]
url: https://developer.bank.com
license:
name: Proprietary
url: https://bank.com/licenses
servers:
- url: https://api.bank.com/v1
description: Production server
- url: https://api-staging.bank.com/v1
description: Staging server
- url: http://localhost:8080/v1
description: Local development server
tags:
- name: payments
description: Payment operations
- name: accounts
description: Account management
- name: audit
description: Audit trail access
paths:
/payments:
post:
summary: Create a new payment
description: |
Creates a new payment transaction. Payments under $1000 are processed immediately.
Payments over $1000 require additional approval.
## Audit Logging
All payment creations are logged for compliance purposes.
operationId: createPayment
tags:
- payments
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/PaymentRequest'
examples:
small-payment:
summary: Small payment example
value:
amount: 100.00
currency: USD
recipient: John Doe
reference: Invoice #12345
large-payment:
summary: Large payment requiring approval
value:
amount: 50000.00
currency: EUR
recipient: Acme Corp
reference: Contract payment
responses:
'201':
description: Payment created successfully
headers:
Location:
schema:
type: string
format: uri
description: URL of the created payment resource
example: /v1/payments/123e4567-e89b-12d3-a456-426614174000
X-Request-ID:
schema:
type: string
format: uuid
description: Request correlation ID for tracing
content:
application/json:
schema:
$ref: '#/components/schemas/PaymentResponse'
examples:
completed-payment:
$ref: '#/components/examples/CompletedPaymentResponse'
pending-approval:
$ref: '#/components/examples/PendingApprovalResponse'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'422':
$ref: '#/components/responses/UnprocessableEntity'
'429':
$ref: '#/components/responses/TooManyRequests'
'500':
$ref: '#/components/responses/InternalServerError'
get:
summary: List payments
description: Retrieve a paginated list of payments
operationId: listPayments
tags:
- payments
security:
- bearerAuth: []
parameters:
- $ref: '#/components/parameters/PageNumber'
- $ref: '#/components/parameters/PageSize'
- $ref: '#/components/parameters/SortBy'
- name: status
in: query
description: Filter by payment status
schema:
type: array
items:
$ref: '#/components/schemas/PaymentStatus'
style: form
explode: true
- name: fromDate
in: query
description: Filter payments from this date
schema:
type: string
format: date
example: "2025-01-01"
- name: toDate
in: query
description: Filter payments until this date
schema:
type: string
format: date
example: "2025-01-31"
responses:
'200':
description: List of payments
content:
application/json:
schema:
$ref: '#/components/schemas/PaymentListResponse'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
/payments/{paymentId}:
parameters:
- $ref: '#/components/parameters/PaymentId'
get:
summary: Get payment by ID
description: Retrieve detailed information about a specific payment
operationId: getPayment
tags:
- payments
security:
- bearerAuth: []
responses:
'200':
description: Payment details
content:
application/json:
schema:
$ref: '#/components/schemas/PaymentResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'404':
$ref: '#/components/responses/NotFound'
patch:
summary: Update payment
description: Update specific fields of a payment (only allowed for PENDING payments)
operationId: updatePayment
tags:
- payments
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/PaymentUpdateRequest'
responses:
'200':
description: Payment updated successfully
content:
application/json:
schema:
$ref: '#/components/schemas/PaymentResponse'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'404':
$ref: '#/components/responses/NotFound'
'409':
description: Payment cannot be updated in current status
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
delete:
summary: Cancel payment
description: Cancel a pending payment (only PENDING payments can be cancelled)
operationId: cancelPayment
tags:
- payments
security:
- bearerAuth: []
responses:
'204':
description: Payment cancelled successfully
'401':
$ref: '#/components/responses/Unauthorized'
'404':
$ref: '#/components/responses/NotFound'
'409':
description: Payment cannot be cancelled in current status
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
description: OAuth 2.0 Bearer token
parameters:
PaymentId:
name: paymentId
in: path
required: true
description: Unique payment identifier
schema:
type: string
format: uuid
example: 123e4567-e89b-12d3-a456-426614174000
PageNumber:
name: page
in: query
description: Page number (0-indexed)
schema:
type: integer
minimum: 0
default: 0
example: 0
PageSize:
name: size
in: query
description: Number of items per page
schema:
type: integer
minimum: 1
maximum: 100
default: 20
example: 20
SortBy:
name: sort
in: query
description: Sort field and direction (field,direction)
schema:
type: array
items:
type: string
style: form
explode: true
example: ["createdAt,desc", "amount,asc"]
schemas:
PaymentRequest:
type: object
required:
- amount
- currency
- recipient
properties:
amount:
type: number
format: decimal
minimum: 0.01
maximum: 1000000.00
example: 100.00
description: Payment amount (must be positive)
currency:
type: string
enum: [USD, EUR, GBP, JPY, CAD]
example: USD
description: Payment currency code (ISO 4217)
recipient:
type: string
minLength: 1
maxLength: 255
example: John Doe
description: Payment recipient name
reference:
type: string
maxLength: 500
example: Invoice #12345
description: Optional payment reference or description
scheduledDate:
type: string
format: date
example: "2025-02-15"
description: Optional future date for scheduled payment
PaymentUpdateRequest:
type: object
properties:
reference:
type: string
maxLength: 500
scheduledDate:
type: string
format: date
PaymentResponse:
type: object
required:
- transactionId
- amount
- currency
- status
- createdAt
properties:
transactionId:
type: string
format: uuid
example: 123e4567-e89b-12d3-a456-426614174000
description: Unique transaction identifier
amount:
type: number
format: decimal
example: 100.00
currency:
type: string
example: USD
recipient:
type: string
example: John Doe
reference:
type: string
example: Invoice #12345
status:
$ref: '#/components/schemas/PaymentStatus'
createdAt:
type: string
format: date-time
example: "2025-01-15T10:30:00Z"
updatedAt:
type: string
format: date-time
example: "2025-01-15T10:31:00Z"
processedAt:
type: string
format: date-time
example: "2025-01-15T10:30:05Z"
description: When payment was processed (null if not yet processed)
PaymentStatus:
type: string
enum:
- PENDING
- REQUIRES_APPROVAL
- APPROVED
- PROCESSING
- COMPLETED
- FAILED
- CANCELLED
description: |
Payment status lifecycle:
- PENDING: Initial state, awaiting processing
- REQUIRES_APPROVAL: Payment requires manual approval (>$1000)
- APPROVED: Payment approved, awaiting processing
- PROCESSING: Payment currently being processed
- COMPLETED: Payment successfully completed
- FAILED: Payment processing failed
- CANCELLED: Payment cancelled by user
PaymentListResponse:
type: object
required:
- content
- page
- totalElements
- totalPages
properties:
content:
type: array
items:
$ref: '#/components/schemas/PaymentResponse'
page:
type: integer
example: 0
size:
type: integer
example: 20
totalElements:
type: integer
example: 150
totalPages:
type: integer
example: 8
ErrorResponse:
type: object
required:
- type
- title
- status
- detail
- instance
properties:
type:
type: string
format: uri
example: https://api.bank.com/errors/payment-not-found
description: URI reference identifying the error type
title:
type: string
example: Payment Not Found
description: Short, human-readable summary
status:
type: integer
example: 404
description: HTTP status code
detail:
type: string
example: No payment found with ID 123e4567-e89b-12d3-a456-426614174000
description: Human-readable explanation
instance:
type: string
format: uri
example: /v1/payments/123e4567-e89b-12d3-a456-426614174000
description: URI reference to specific occurrence
timestamp:
type: string
format: date-time
example: "2025-01-15T10:30:00Z"
requestId:
type: string
format: uuid
example: 987fcdeb-51a2-43f7-b123-456789abcdef
description: Request correlation ID for tracing
ValidationErrorResponse:
allOf:
- $ref: '#/components/schemas/ErrorResponse'
- type: object
required:
- errors
properties:
errors:
type: array
items:
$ref: '#/components/schemas/ValidationError'
ValidationError:
type: object
required:
- field
- message
properties:
field:
type: string
example: amount
description: Field that failed validation
message:
type: string
example: Amount must be positive
description: Validation error message
rejectedValue:
example: -100.00
description: Value that was rejected
responses:
BadRequest:
description: Bad request - invalid input
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
Unauthorized:
description: Unauthorized - missing or invalid authentication
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
Forbidden:
description: Forbidden - insufficient permissions
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
NotFound:
description: Resource not found
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
UnprocessableEntity:
description: Unprocessable entity - validation errors
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationErrorResponse'
TooManyRequests:
description: Too many requests - rate limit exceeded
headers:
X-Rate-Limit-Limit:
schema:
type: integer
description: Request limit per hour
X-Rate-Limit-Remaining:
schema:
type: integer
description: Remaining requests in current window
X-Rate-Limit-Reset:
schema:
type: integer
description: Time when rate limit resets (Unix timestamp)
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
InternalServerError:
description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
examples:
CompletedPaymentResponse:
summary: Completed payment response
value:
transactionId: 123e4567-e89b-12d3-a456-426614174000
amount: 100.00
currency: USD
recipient: John Doe
reference: Invoice #12345
status: COMPLETED
createdAt: "2025-01-15T10:30:00Z"
processedAt: "2025-01-15T10:30:05Z"
PendingApprovalResponse:
summary: Payment requiring approval
value:
transactionId: 123e4567-e89b-12d3-a456-426614174000
amount: 50000.00
currency: EUR
recipient: Acme Corp
reference: Contract payment
status: REQUIRES_APPROVAL
createdAt: "2025-01-15T10:30:00Z"
processedAt: null
security:
- bearerAuth: []
Best Practices
Use Descriptive Names
# Bad: Cryptic operation ID
operationId: getP
# Good: Clear operation ID
operationId: getPaymentById
Provide Examples
properties:
amount:
type: number
format: decimal
minimum: 0.01
example: 100.00 # Always provide examples
description: Payment amount in specified currency
Use Reusable Components
# Define once, reference many times
components:
schemas:
PaymentStatus:
type: string
enum: [PENDING, COMPLETED, FAILED]
# Reference in multiple places
properties:
status:
$ref: '#/components/schemas/PaymentStatus'
Document Error Responses
responses:
'400':
description: Bad request
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
example:
type: https://api.bank.com/errors/invalid-amount
title: Invalid Amount
status: 400
detail: Amount must be positive
instance: /v1/payments
Version Your APIs
servers:
- url: https://api.bank.com/v1 # Version in URL
description: Production API v1
- url: https://api.bank.com/v2 # New version
description: Production API v2
Tools and Validation
Validate OpenAPI Specs
# Using Swagger CLI
npm install -g @apidevtools/swagger-cli
swagger-cli validate api/payment-api.yaml
# Using OpenAPI Generator
openapi-generator-cli validate -i api/payment-api.yaml
Generate Documentation
# Generate static HTML documentation
npx @redocly/cli build-docs api/payment-api.yaml -o docs/api.html
# Serve interactive documentation
npx @redocly/cli preview-docs api/payment-api.yaml
Versioning Strategy
When to Create a New Version
Create new API version (v2, v3) when:
- Breaking changes to existing endpoints
- Removing fields from responses
- Changing field types
- Modifying validation rules
Maintain Backward Compatibility
Within a version, you can:
- Add new endpoints
- Add optional request fields
- Add fields to responses
- Relax validation (make fields optional)
# v1: Original
properties:
amount:
type: number
currency:
type: string
# v1: Compatible addition (new optional field)
properties:
amount:
type: number
currency:
type: string
reference:
type: string # NEW optional field - compatible
# v2: Breaking change (different structure)
properties:
payment:
type: object
properties:
amount:
type: number
currency:
type: string
Further Reading
- Contract Testing - Validate OpenAPI specs in tests
- API Integration (Backend) - Spring Boot OpenAPI integration
- API Integration (Frontend) - TypeScript client generation
- REST Fundamentals - REST API design fundamentals
- API Contracts - Request/response models and validation
- API Patterns - Pagination, versioning, error handling
External Resources:
Summary
Key Takeaways:
- Schema-First: Define OpenAPI specs before implementation
- Single Source of Truth: Generate code, docs, and validation from specs
- Comprehensive Documentation: Include descriptions, examples, error responses
- Reusable Components: Use components for schemas, parameters, responses
- Semantic Versioning: Version APIs appropriately, maintain backward compatibility
- Validation: Validate specs with tools before implementation
- RFC 7807 Errors: Use Problem Details format for consistent error responses
- Store in Version Control: OpenAPI specs live alongside code