Complete OpenAPI Specification Guide

If you've ever integrated with a poorly documented API, you know the pain. Hours spent guessing what fields are required, what data types to expect, and what error codes mean. OpenAPI exists to solve that problem — it's a standard way to describe REST APIs so both humans and machines can

TRY NANO BANANA FOR FREE

Complete OpenAPI Specification Guide

TRY NANO BANANA FOR FREE
Contents

If you've ever integrated with a poorly documented API, you know the pain. Hours spent guessing what fields are required, what data types to expect, and what error codes mean. OpenAPI exists to solve that problem — it's a standard way to describe REST APIs so both humans and machines can understand them.

OpenAPI 3.1 is the latest version, and it's a significant improvement over 3.0. Let's walk through everything you need to know to write specs that are actually useful.

What Is OpenAPI?

OpenAPI (formerly Swagger) is a specification for describing REST APIs. An OpenAPI document is a JSON or YAML file that describes:

  • What endpoints your API has
  • What parameters each endpoint accepts
  • What request bodies look like
  • What responses to expect
  • How authentication works

Here's the simplest possible OpenAPI document:

openapi: 3.1.0
info:
  title: PetStore API
  version: 1.0.0
paths:
  /pets:
    get:
      summary: List all pets
      responses:
        '200':
          description: A list of pets

That's valid OpenAPI. Now let's build something more complete.

Document Structure

An OpenAPI 3.1 document has these top-level fields:

openapi: 3.1.0          # Required: spec version
info: ...               # Required: API metadata
servers: ...            # Optional: server URLs
paths: ...              # Required: API endpoints
components: ...         # Optional: reusable definitions
security: ...           # Optional: global security
tags: ...               # Optional: endpoint grouping
externalDocs: ...       # Optional: external documentation

The info Object

info:
  title: PetStore API
  description: |
    A sample API for managing a pet store.

    ## Authentication
    All endpoints require a valid API key passed in the `X-API-Key` header.

    ## Rate Limiting
    Requests are limited to 100 per minute per API key.
  version: 2.1.0
  contact:
    name: PetStore Support
    email: support@petstore.com
    url: https://petstore.com/support
  license:
    name: MIT
    url: https://opensource.org/licenses/MIT
  termsOfService: https://petstore.com/terms

The servers Object

servers:
  - url: https://api.petstore.com/v2
    description: Production server
  - url: https://staging-api.petstore.com/v2
    description: Staging server
  - url: http://localhost:3000/v2
    description: Local development

You can also use server variables for dynamic URLs:

servers:
  - url: https://{region}.api.petstore.com/v2
    description: Regional API server
    variables:
      region:
        default: us-east
        enum:
          - us-east
          - us-west
          - eu-central
        description: The geographic region for the API server

Defining Paths

Paths are the core of your OpenAPI spec. Each path maps to an endpoint, and each endpoint can have multiple HTTP methods.

paths:
  /pets:
    get:
      operationId: listPets
      summary: List all pets
      description: Returns a paginated list of all pets in the store.
      tags:
        - Pets
      parameters:
        - name: limit
          in: query
          description: Maximum number of pets to return
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
        - name: status
          in: query
          description: Filter by pet status
          schema:
            type: string
            enum:
              - available
              - pending
              - sold
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PetList'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'
      security:
        - ApiKeyAuth: []

    post:
      operationId: createPet
      summary: Create a new pet
      tags:
        - Pets
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreatePetRequest'
            examples:
              dog:
                summary: A dog example
                value:
                  name: Rex
                  species: dog
                  breed: German Shepherd
                  age: 3
                  status: available
              cat:
                summary: A cat example
                value:
                  name: Whiskers
                  species: cat
                  age: 5
                  status: available
      responses:
        '201':
          description: Pet created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Pet'
          headers:
            Location:
              description: URL of the newly created pet
              schema:
                type: string
                format: uri
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'

  /pets/{petId}:
    parameters:
      - name: petId
        in: path
        required: true
        description: The unique identifier of the pet
        schema:
          type: integer
          format: int64
          minimum: 1

    get:
      operationId: getPet
      summary: Get a pet by ID
      tags:
        - Pets
      responses:
        '200':
          description: Pet found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Pet'
        '404':
          $ref: '#/components/responses/NotFound'

    put:
      operationId: updatePet
      summary: Update a pet
      tags:
        - Pets
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdatePetRequest'
      responses:
        '200':
          description: Pet updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Pet'
        '404':
          $ref: '#/components/responses/NotFound'

    delete:
      operationId: deletePet
      summary: Delete a pet
      tags:
        - Pets
      responses:
        '204':
          description: Pet deleted successfully
        '404':
          $ref: '#/components/responses/NotFound'

Components: Reusable Definitions

The components section is where you define reusable pieces. This keeps your spec DRY and easier to maintain.

Schemas

Schemas define the shape of your data. OpenAPI 3.1 uses JSON Schema draft 2020-12, which gives you a lot of power.

components:
  schemas:
    Pet:
      type: object
      required:
        - id
        - name
        - species
        - status
      properties:
        id:
          type: integer
          format: int64
          readOnly: true
          description: Unique identifier for the pet
          examples:
            - 42
        name:
          type: string
          minLength: 1
          maxLength: 100
          description: The pet's name
          examples:
            - Rex
        species:
          type: string
          enum:
            - dog
            - cat
            - bird
            - fish
            - reptile
            - other
          description: The type of animal
        breed:
          type: string
          nullable: true
          description: The breed (if applicable)
        age:
          type: integer
          minimum: 0
          maximum: 100
          description: Age in years
        weight:
          type: number
          format: float
          minimum: 0
          description: Weight in kilograms
        status:
          $ref: '#/components/schemas/PetStatus'
        tags:
          type: array
          items:
            type: string
          uniqueItems: true
          description: Searchable tags for the pet
        photos:
          type: array
          items:
            $ref: '#/components/schemas/Photo'
        createdAt:
          type: string
          format: date-time
          readOnly: true
        updatedAt:
          type: string
          format: date-time
          readOnly: true

    PetStatus:
      type: string
      enum:
        - available
        - pending
        - sold
      description: The current availability status of the pet

    CreatePetRequest:
      type: object
      required:
        - name
        - species
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 100
        species:
          type: string
          enum:
            - dog
            - cat
            - bird
            - fish
            - reptile
            - other
        breed:
          type: string
        age:
          type: integer
          minimum: 0
        weight:
          type: number
          minimum: 0
        status:
          $ref: '#/components/schemas/PetStatus'
          default: available
        tags:
          type: array
          items:
            type: string

    UpdatePetRequest:
      type: object
      minProperties: 1
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 100
        breed:
          type: string
        age:
          type: integer
          minimum: 0
        weight:
          type: number
          minimum: 0
        status:
          $ref: '#/components/schemas/PetStatus'
        tags:
          type: array
          items:
            type: string

    PetList:
      type: object
      required:
        - data
        - pagination
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/Pet'
        pagination:
          $ref: '#/components/schemas/Pagination'

    Pagination:
      type: object
      required:
        - total
        - page
        - pageSize
        - totalPages
      properties:
        total:
          type: integer
          description: Total number of items
        page:
          type: integer
          description: Current page number
        pageSize:
          type: integer
          description: Number of items per page
        totalPages:
          type: integer
          description: Total number of pages
        nextPage:
          type: string
          format: uri
          nullable: true
          description: URL for the next page
        prevPage:
          type: string
          format: uri
          nullable: true
          description: URL for the previous page

    Photo:
      type: object
      required:
        - url
      properties:
        url:
          type: string
          format: uri
        caption:
          type: string
        isPrimary:
          type: boolean
          default: false

    Error:
      type: object
      required:
        - code
        - message
      properties:
        code:
          type: string
          description: Machine-readable error code
        message:
          type: string
          description: Human-readable error message
        details:
          type: array
          items:
            type: object
            properties:
              field:
                type: string
              message:
                type: string
        requestId:
          type: string
          description: Request ID for debugging

Using Composition with allOf, oneOf, anyOf

OpenAPI 3.1 supports JSON Schema composition keywords:

schemas:
  # allOf: must match ALL schemas
  PremiumPet:
    allOf:
      - $ref: '#/components/schemas/Pet'
      - type: object
        properties:
          premiumFeatures:
            type: array
            items:
              type: string
          insurancePolicy:
            type: string

  # oneOf: must match EXACTLY ONE schema
  AnimalIdentifier:
    oneOf:
      - type: object
        required:
          - petId
        properties:
          petId:
            type: integer
      - type: object
        required:
          - microchipId
        properties:
          microchipId:
            type: string

  # anyOf: must match AT LEAST ONE schema
  SearchFilter:
    anyOf:
      - type: object
        properties:
          species:
            type: string
      - type: object
        properties:
          ageRange:
            type: object
            properties:
              min:
                type: integer
              max:
                type: integer

Reusable Responses

  responses:
    BadRequest:
      description: Invalid request parameters
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            code: VALIDATION_ERROR
            message: Request validation failed
            details:
              - field: name
                message: Name is required
            requestId: req_abc123

    Unauthorized:
      description: Authentication required
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            code: UNAUTHORIZED
            message: Valid API key required

    NotFound:
      description: Resource not found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            code: NOT_FOUND
            message: Pet with ID 999 not found

    RateLimited:
      description: Too many requests
      headers:
        X-RateLimit-Limit:
          schema:
            type: integer
          description: Request limit per minute
        X-RateLimit-Remaining:
          schema:
            type: integer
          description: Remaining requests in current window
        X-RateLimit-Reset:
          schema:
            type: integer
          description: Unix timestamp when the window resets
        Retry-After:
          schema:
            type: integer
          description: Seconds to wait before retrying
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            code: RATE_LIMITED
            message: Too many requests. Please slow down.

Reusable Parameters

  parameters:
    PetIdParam:
      name: petId
      in: path
      required: true
      schema:
        type: integer
        format: int64
        minimum: 1
      description: The unique identifier of the pet

    LimitParam:
      name: limit
      in: query
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 20
      description: Maximum number of results to return

    PageParam:
      name: page
      in: query
      schema:
        type: integer
        minimum: 1
        default: 1
      description: Page number for pagination

Security Schemes

OpenAPI 3.1 supports several authentication types.

API Key Authentication

  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
      description: |
        API key for authentication. Get your key at https://petstore.com/dashboard.

        Example: `X-API-Key: pk_live_abc123`

Bearer Token (JWT)

    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: |
        JWT token obtained from the /auth/token endpoint.

        Example: `Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...`

OAuth 2.0

    OAuth2:
      type: oauth2
      flows:
        authorizationCode:
          authorizationUrl: https://auth.petstore.com/oauth/authorize
          tokenUrl: https://auth.petstore.com/oauth/token
          refreshUrl: https://auth.petstore.com/oauth/refresh
          scopes:
            pets:read: Read pet information
            pets:write: Create and update pets
            pets:delete: Delete pets
            orders:read: Read order information
            orders:write: Create and manage orders
        clientCredentials:
          tokenUrl: https://auth.petstore.com/oauth/token
          scopes:
            pets:read: Read pet information
            pets:write: Create and update pets

Apply security globally or per-endpoint:

# Global security (applies to all endpoints)
security:
  - ApiKeyAuth: []

# Override per endpoint
paths:
  /public/pets:
    get:
      security: []  # No auth required for this endpoint

  /pets:
    post:
      security:
        - OAuth2:
            - pets:write  # Requires specific OAuth scope

Writing Good OpenAPI Specs

Use operationId Consistently

Every operation should have a unique operationId. Use camelCase and follow a consistent naming pattern:

# Good
operationId: listPets
operationId: createPet
operationId: getPetById
operationId: updatePet
operationId: deletePet

# Bad
operationId: GET_pets
operationId: pets_post
operationId: operation1

Add Meaningful Examples

Examples make your spec much more useful:

requestBody:
  content:
    application/json:
      schema:
        $ref: '#/components/schemas/CreatePetRequest'
      examples:
        minimal:
          summary: Minimal required fields
          value:
            name: Buddy
            species: dog
        complete:
          summary: All fields provided
          value:
            name: Buddy
            species: dog
            breed: Labrador Retriever
            age: 2
            weight: 28.5
            status: available
            tags:
              - friendly
              - trained
              - vaccinated

Document Error Responses Thoroughly

Don't just list 200 responses. Document every error your API can return:

responses:
  '200':
    description: Success
  '400':
    description: |
      Bad request. Common causes:
      - Missing required fields
      - Invalid field values
      - Malformed JSON
  '401':
    description: Missing or invalid API key
  '403':
    description: Valid API key but insufficient permissions
  '404':
    description: Pet not found
  '409':
    description: Conflict — pet with this name already exists
  '422':
    description: Validation error — request is well-formed but semantically invalid
  '429':
    description: Rate limit exceeded
  '500':
    description: Internal server error — please contact support

Use $ref to Avoid Repetition

If you find yourself copying the same schema in multiple places, extract it into components:

# Instead of this (repeated in every endpoint):
responses:
  '401':
    description: Unauthorized
    content:
      application/json:
        schema:
          type: object
          properties:
            code:
              type: string
            message:
              type: string

# Do this:
responses:
  '401':
    $ref: '#/components/responses/Unauthorized'

Validation Tools

Spectral

Spectral is a powerful linter for OpenAPI specs. It catches common mistakes and enforces style rules.

Install and run:

npm install -g @stoplight/spectral-cli
spectral lint openapi.yaml

Create a custom ruleset:

# .spectral.yaml
extends: spectral:oas
rules:
  operation-operationId: error
  operation-summary: error
  operation-description: warn
  info-contact: warn

  # Custom rule: all responses must have examples
  response-examples:
    description: All responses should have examples
    given: "$.paths[*][*].responses[*].content[*]"
    severity: warn
    then:
      field: examples
      function: truthy

openapi-validator

IBM's OpenAPI validator is thorough and catches issues Spectral misses:

npm install -g ibm-openapi-validator
lint-openapi openapi.yaml

Redocly CLI

Redocly offers both linting and bundling:

npm install -g @redocly/cli
redocly lint openapi.yaml
redocly bundle openapi.yaml --output bundled.yaml

Swagger Editor

For a visual editing experience, use the Swagger Editor online at editor.swagger.io or run it locally:

docker run -p 8080:8080 swaggerapi/swagger-editor

Generating Code from OpenAPI

One of the best things about OpenAPI is that you can generate client SDKs and server stubs automatically.

Generate a TypeScript client

npx @openapitools/openapi-generator-cli generate \
  -i openapi.yaml \
  -g typescript-fetch \
  -o ./src/generated/api-client

Generate a Python client

npx @openapitools/openapi-generator-cli generate \
  -i openapi.yaml \
  -g python \
  -o ./clients/python

Generate server stubs

# Express.js server stub
npx @openapitools/openapi-generator-cli generate \
  -i openapi.yaml \
  -g nodejs-express-server \
  -o ./server-stub

Keeping Your Spec in Sync

The biggest challenge with OpenAPI is keeping the spec in sync with your actual API. Here are some strategies:

Code-first with annotations: Generate the spec from code comments

/**
 * @openapi
 * /pets:
 *   get:
 *     summary: List all pets
 *     responses:
 *       200:
 *         description: Success
 */
app.get('/pets', listPets);

Spec-first: Write the spec first, then implement it (covered in the API-First Development article)

Contract testing: Use tools like Dredd or Schemathesis to verify your implementation matches the spec

# Test your API against the spec
schemathesis run openapi.yaml --url http://localhost:3000

Wrapping Up

A well-written OpenAPI spec is one of the most valuable things you can create for your API. It serves as documentation, enables code generation, powers mock servers, and makes testing easier.

The key is to be thorough without being verbose. Document every parameter, every response, every error. Use $ref to keep things DRY. Add examples that show real-world usage. And validate your spec with tools like Spectral to catch mistakes early.

Start with the basics — paths, schemas, and responses — and add more detail over time. Even an incomplete spec is better than no spec at all.