Skip to main content

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

External Resources:


Summary

Key Takeaways:

  1. Schema-First: Define OpenAPI specs before implementation
  2. Single Source of Truth: Generate code, docs, and validation from specs
  3. Comprehensive Documentation: Include descriptions, examples, error responses
  4. Reusable Components: Use components for schemas, parameters, responses
  5. Semantic Versioning: Version APIs appropriately, maintain backward compatibility
  6. Validation: Validate specs with tools before implementation
  7. RFC 7807 Errors: Use Problem Details format for consistent error responses
  8. Store in Version Control: OpenAPI specs live alongside code