OpenAPI JSON Schema Specification: paths, $ref, requestBody & Code Generation

Last updated:

OpenAPI 3.1 is a JSON (or YAML) document that describes an HTTP API — the paths object maps URL patterns to operations, components/schemas defines reusable JSON Schema objects, and $ref links them together without duplication. OpenAPI 3.1 adopts JSON Schema draft 2020-12 fully, resolving the dialect divergence from OpenAPI 3.0 where schema objects were a non-standard subset incompatible with mainstream validators. A minimal valid OpenAPI 3.1 document requires three top-level fields: openapi: "3.1.0", info: { title, version }, and paths: {}. This guide covers the top-level structure, paths and HTTP operation objects, JSON Schema in components/schemas with $ref, requestBody and responses with content negotiation, security schemes (Bearer, API key, OAuth 2.0), and code generation with openapi-generator-cli.

OpenAPI 3.1 Document Structure: openapi, info, paths

Every OpenAPI 3.1 document is a single JSON object with a fixed set of top-level fields. The three required fields are openapi (the spec version string "3.1.0"), info (metadata including title and version), and paths (the endpoint map). Optional top-level fields include servers (base URLs for the API), components (reusable definitions), security (global authentication requirements), tags (grouping labels for operations), externalDocs, and webhooks (new in 3.1, for describing async push events). The document must be self-consistent: every $ref must resolve, every operationId must be unique, and every scheme referenced in a security array must be declared in components/securitySchemes.

{
  "openapi": "3.1.0",
  "info": {
    "title": "User Management API",
    "version": "1.0.0",
    "description": "CRUD operations for users",
    "contact": {
      "name": "API Support",
      "email": "api@example.com",
      "url": "https://example.com/support"
    },
    "license": {
      "name": "MIT",
      "url": "https://opensource.org/licenses/MIT"
    }
  },
  "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": {},
    "/users/{id}": {}
  },
  "components": {
    "schemas": {},
    "securitySchemes": {}
  },
  "security": [
    { "BearerAuth": [] }
  ],
  "tags": [
    { "name": "users", "description": "User management operations" }
  ]
}

The servers array is evaluated top-to-bottom — documentation tools use the first entry as the default server. Server URLs can contain template variables: {"https://{environment}.api.example.com/v1"} with a companion variables object defining valid values and defaults. The info.version field is the API version (e.g., "1.0.0"), not the OpenAPI spec version — use semantic versioning. The openapi field must be exactly "3.1.0" — tooling uses this string to select the correct parsing and validation logic, so an incorrect value silently degrades tool behavior. The webhooks top-level field (new in 3.1) describes push callbacks the server sends to registered client URLs — structurally identical to paths but semantically outbound.

Path Items and HTTP Operations: GET, POST, Parameters

The paths object maps URL path templates to Path Item Objects. A path template uses curly braces for variable segments: /users/{id}. Each Path Item Object contains HTTP method keys (get, post, put, patch, delete, head, options, trace), each holding an Operation Object. The Operation Object defines summary, description, operationId, tags, parameters, requestBody, responses, and security. Parameters describe query strings, path variables, headers, and cookies — each with a name, in location, schema, and required flag.

{
  "paths": {
    "/users": {
      "get": {
        "operationId": "listUsers",
        "summary": "List all users",
        "tags": ["users"],
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": { "type": "integer", "minimum": 1, "default": 1 }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 20 }
          },
          {
            "name": "X-Request-ID",
            "in": "header",
            "required": false,
            "schema": { "type": "string", "format": "uuid" }
          }
        ],
        "responses": {
          "200": {
            "description": "A paginated list of users",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/UserListResponse" }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createUser",
        "summary": "Create a new user",
        "tags": ["users"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/CreateUserRequest" }
            }
          }
        },
        "responses": {
          "201": {
            "description": "User created",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/User" }
              }
            }
          },
          "422": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ValidationError" }
              }
            }
          }
        }
      }
    },
    "/users/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": { "type": "integer", "minimum": 1 }
        }
      ],
      "get": {
        "operationId": "getUserById",
        "summary": "Get a user by ID",
        "tags": ["users"],
        "responses": {
          "200": {
            "description": "User found",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/User" }
              }
            }
          },
          "404": { "description": "User not found" }
        }
      }
    }
  }
}

Parameters defined at the Path Item level (outside any HTTP method) apply to all operations on that path — use this for path parameters like id shared by GET, PUT, PATCH, and DELETE on /users/{id}. Operation-level parameters override path-level parameters with the same name and in combination. The operationId becomes the function name in generated client code — use camelCase descriptive verbs: listUsers, createUser, getUserById, updateUser, deleteUser. Avoid abbreviations and generic names; getUserById is better than getUser when IDs, emails, and slugs are all lookup keys.

JSON Schema in components/schemas and $ref

The components/schemas object is the schema library of an OpenAPI document — define every reusable data shape here and reference it with $ref throughout paths. Because OpenAPI 3.1 schemas are full JSON Schema 2020-12, you can use any JSON Schema keyword: type, properties, required, additionalProperties, allOf, anyOf, oneOf, not, if/then/else, $defs, enum, format, pattern, minLength, minimum, and more. The $ref value is a JSON Pointer: "#/components/schemas/User" resolves to the User key under components.schemas in the same document.

{
  "components": {
    "schemas": {

      "User": {
        "type": "object",
        "required": ["id", "name", "email"],
        "properties": {
          "id":        { "type": "integer", "readOnly": true },
          "name":      { "type": "string", "minLength": 1, "maxLength": 100 },
          "email":     { "type": "string", "format": "email" },
          "role":      { "$ref": "#/components/schemas/UserRole" },
          "createdAt": { "type": "string", "format": "date-time", "readOnly": true }
        },
        "additionalProperties": false
      },

      "UserRole": {
        "type": "string",
        "enum": ["admin", "editor", "viewer"]
      },

      "CreateUserRequest": {
        "type": "object",
        "required": ["name", "email"],
        "properties": {
          "name":  { "type": "string", "minLength": 1, "maxLength": 100 },
          "email": { "type": "string", "format": "email" },
          "role":  { "$ref": "#/components/schemas/UserRole" }
        },
        "additionalProperties": false
      },

      "UserListResponse": {
        "type": "object",
        "required": ["data", "meta"],
        "properties": {
          "data": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/User" }
          },
          "meta": { "$ref": "#/components/schemas/PaginationMeta" }
        }
      },

      "PaginationMeta": {
        "type": "object",
        "required": ["page", "limit", "total"],
        "properties": {
          "page":  { "type": "integer", "minimum": 1 },
          "limit": { "type": "integer", "minimum": 1 },
          "total": { "type": "integer", "minimum": 0 }
        }
      },

      "NullableString": {
        "type": ["string", "null"]
      }
    }
  }
}

In OpenAPI 3.1, nullable fields use {"type: ["string", "null"]"} — the OpenAPI 3.0 nullable: true extension is removed. The readOnly: true and writeOnly: true keywords mark fields that should only appear in responses or requests respectively — code generators use these to create separate request/response types, so a User response type includes id and createdAt while a CreateUserRequest does not. Use additionalProperties: false to reject unknown fields at the server validation layer; omit it to allow forward-compatible extensions. Compose schemas with allOf for inheritance: an AdminUser schema can extend User with additional permissions without repeating the base properties. See also the JSON Schema guide for the full keyword reference.

requestBody: Defining JSON Request Payloads

The requestBody object appears at the operation level for POST, PUT, and PATCH operations. It wraps a content map that keys MIME types to Media Type Objects, each containing a schema and optional examples. The indirection through a media type key is intentional — an operation can accept multiple content types simultaneously (JSON, form data, multipart). Always set required: true for operations that fail without a body. The schema inside content["application/json"].schema is a full JSON Schema object or a $ref. Scoping the schema inside the media type key — rather than at the requestBody level — allows each content type to have a different schema for the same operation.

{
  "paths": {
    "/users": {
      "post": {
        "operationId": "createUser",
        "requestBody": {
          "required": true,
          "description": "User creation payload",
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/CreateUserRequest" },
              "examples": {
                "admin-user": {
                  "summary": "Create an admin user",
                  "value": {
                    "name": "Alice Admin",
                    "email": "alice@example.com",
                    "role": "admin"
                  }
                },
                "viewer-user": {
                  "summary": "Create a read-only viewer",
                  "value": {
                    "name": "Bob Viewer",
                    "email": "bob@example.com",
                    "role": "viewer"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/users/{id}/avatar": {
      "put": {
        "operationId": "uploadAvatar",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": ["file"],
                "properties": {
                  "file": { "type": "string", "format": "binary" },
                  "alt":  { "type": "string" }
                }
              }
            },
            "application/octet-stream": {
              "schema": { "type": "string", "format": "binary" }
            }
          }
        }
      }
    },
    "/users/{id}": {
      "patch": {
        "operationId": "patchUser",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name":  { "type": "string", "minLength": 1 },
                  "email": { "type": "string", "format": "email" }
                },
                "minProperties": 1,
                "additionalProperties": false
              }
            }
          }
        }
      }
    }
  }
}

The examples field (plural) at the Media Type Object level takes precedence over the example field (singular) on the schema — use examples in new specs as it allows multiple named examples with summaries. Each named example can also use {"$ref": "#/components/examples/AdminUser"} to reference a reusable example from components/examples. For PATCH operations, use minProperties: 1 to require at least one field while leaving all properties optional — this is the correct pattern for partial update semantics without a separate schema per patchable combination. See JSON API design for REST conventions around PATCH vs PUT.

responses: Status Codes, Headers, and Content

The responses object maps HTTP status codes (as strings) to Response Objects. Every operation must define at least one response. Each Response Object has a required description, an optional content map (same structure as requestBody), and an optional headers map for documenting response headers. Use the default key to define a catch-all response for status codes not explicitly listed — typically an error envelope. Response schemas follow the same JSON Schema rules as request schemas; $ref works identically. Content negotiation is declared by adding multiple MIME type keys under content.

{
  "responses": {
    "200": {
      "description": "User retrieved successfully",
      "headers": {
        "X-Request-ID": {
          "description": "Unique identifier for request tracing",
          "schema": { "type": "string", "format": "uuid" }
        },
        "X-RateLimit-Remaining": {
          "description": "Requests remaining in the current window",
          "schema": { "type": "integer", "minimum": 0 }
        }
      },
      "content": {
        "application/json": {
          "schema": { "$ref": "#/components/schemas/User" }
        }
      }
    },

    "201": {
      "description": "Resource created",
      "headers": {
        "Location": {
          "description": "URL of the newly created resource",
          "schema": { "type": "string", "format": "uri" }
        }
      },
      "content": {
        "application/json": {
          "schema": { "$ref": "#/components/schemas/User" }
        }
      }
    },

    "204": {
      "description": "Deleted successfully — no content returned"
    },

    "400": {
      "description": "Bad Request — malformed JSON or invalid field types",
      "content": {
        "application/json": {
          "schema": { "$ref": "#/components/schemas/ValidationError" }
        }
      }
    },

    "404": {
      "description": "Not Found — the resource does not exist",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "error": { "type": "string" },
              "code":  { "type": "string" }
            }
          }
        }
      }
    },

    "default": {
      "description": "Unexpected server error",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "required": ["error"],
            "properties": {
              "error":   { "type": "string" },
              "traceId": { "type": "string" }
            }
          }
        }
      }
    }
  }
}

The 204 No Content response has no content key at all — omit it entirely rather than defining an empty object. Response headers use a Header Object (no in or name field, since those are implied by context) rather than a Parameter Object. The Location header on 201 responses is the standard HTTP convention for pointing to the new resource URL — always document it explicitly so generated clients and API consumers know where to find the created resource. See JSON API error handling for structured error response conventions.

Security Schemes: Bearer, API Key, and OAuth 2.0

Security in OpenAPI uses two layers: Security Scheme Objects declared in components/securitySchemes define how authentication works, and Security Requirement Objects (the security array at document or operation level) apply those mechanisms. A security requirement is an array of objects — multiple keys within one object are an AND condition (all listed schemes must be satisfied), while multiple objects in the outer array are OR conditions (any one suffices). An empty security: [] at the operation level overrides the global default, marking that endpoint as public without authentication.

{
  "components": {
    "securitySchemes": {

      "BearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "JWT obtained from POST /auth/token"
      },

      "ApiKeyHeader": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key"
      },

      "ApiKeyQuery": {
        "type": "apiKey",
        "in": "query",
        "name": "api_key",
        "description": "Less secure — prefer header-based API keys"
      },

      "BasicAuth": {
        "type": "http",
        "scheme": "basic"
      },

      "OAuth2": {
        "type": "oauth2",
        "flows": {
          "authorizationCode": {
            "authorizationUrl": "https://auth.example.com/oauth/authorize",
            "tokenUrl": "https://auth.example.com/oauth/token",
            "refreshUrl": "https://auth.example.com/oauth/refresh",
            "scopes": {
              "read:users":   "Read user profiles",
              "write:users":  "Create and update users",
              "delete:users": "Delete users"
            }
          },
          "clientCredentials": {
            "tokenUrl": "https://auth.example.com/oauth/token",
            "scopes": {
              "read:users": "Read user profiles"
            }
          }
        }
      },

      "OpenIDConnect": {
        "type": "openIdConnect",
        "openIdConnectUrl": "https://auth.example.com/.well-known/openid-configuration"
      }
    }
  },

  "security": [
    { "BearerAuth": [] }
  ],

  "paths": {
    "/public/health": {
      "get": {
        "operationId": "healthCheck",
        "security": [],
        "responses": { "200": { "description": "Service is healthy" } }
      }
    },
    "/users": {
      "get": {
        "operationId": "listUsers",
        "security": [
          { "OAuth2": ["read:users"] },
          { "BearerAuth": [] }
        ],
        "responses": { "200": { "description": "User list" } }
      }
    }
  }
}

The bearerFormat field on HTTP Bearer schemes is informational only — it documents the token format (JWT, opaque token, etc.) for human readers but does not affect validation. For OAuth 2.0 scopes, list the minimum required scopes in the security requirement array: {"{ "OAuth2": ["read:users"] }"} means the token must include the read:users scope. The clientCredentials flow is the correct choice for machine-to-machine (M2M) API access where there is no human user in the loop. The openIdConnect scheme type points to a discovery document from which all OAuth 2.0 endpoints and scopes are auto-discovered by tooling. See JSON OAuth for token handling patterns.

Code Generation with openapi-generator-cli

openapi-generator-cli is the reference code generation tool for OpenAPI specifications — it reads a spec file (JSON or YAML) and generates client SDKs, server stubs, documentation, and configuration files for 50+ languages and frameworks. The CLI wraps the Java-based openapi-generator with a Node.js layer that manages Java invocation and version pinning. Code generators use operationId for function names, schema names from components/schemas for type names, and server URLs for base URL configuration. A clean spec with operationIds, $ref-based schemas, and proper responses produces idiomatic generated code; a spec with inline schemas and missing operationIds produces verbose, inconsistent output.

# Install openapi-generator-cli globally
npm install -g @openapitools/openapi-generator-cli

# Pin the generator version for reproducible CI builds
npx @openapitools/openapi-generator-cli version-manager set 7.4.0

# List all available generators
openapi-generator-cli list

# Validate a spec before generating
openapi-generator-cli validate -i spec.json

# Generate a TypeScript Axios client
openapi-generator-cli generate \
  -i spec.json \
  -g typescript-axios \
  -o ./src/api-client \
  --additional-properties=supportsES6=true,npmName=my-api,npmVersion=1.0.0

# Generate a TypeScript Fetch client (no Axios dependency)
openapi-generator-cli generate \
  -i https://api.example.com/openapi.json \
  -g typescript-fetch \
  -o ./src/api-client

# Generate a Python client
openapi-generator-cli generate \
  -i spec.json \
  -g python \
  -o ./python-client \
  --additional-properties=packageName=my_api,projectName=my-api

# Generate a Go client
openapi-generator-cli generate \
  -i spec.json \
  -g go \
  -o ./go-client \
  --additional-properties=packageName=myapi,moduleName=github.com/myorg/myapi

# Generate a Node.js Express server stub
openapi-generator-cli generate \
  -i spec.json \
  -g nodejs-express-server \
  -o ./server-stub

# Use a config file for complex options
# openapi-generator-config.json:
# {
#   "generatorName": "typescript-axios",
#   "inputSpec": "spec.json",
#   "outputDir": "./src/api-client",
#   "additionalProperties": {
#     "supportsES6": true,
#     "withSeparateModelsAndApi": true,
#     "modelPackage": "models",
#     "apiPackage": "api"
#   }
# }
openapi-generator-cli generate -c openapi-generator-config.json

# CI/CD: detect spec drift — fail if client is out of date
openapi-generator-cli generate -i spec.json -g typescript-axios -o ./src/api-client
git diff --exit-code ./src/api-client

The --additional-properties flag passes generator-specific options — run openapi-generator-cli config-help -g typescript-axios to see all options for a given generator. For production use, commit the generated client to your repository and regenerate in CI on spec changes — this makes generated code reviewable and prevents silent drift. Alternatives: Kiota (Microsoft) produces better TypeScript types with tree-shaking; Speakeasy (commercial) generates SDKs with retries, pagination, and webhook handling built in; oapi-codegen is the idiomatic choice for Go. For FastAPI OpenAPI, the spec is auto-generated from Python type hints and Pydantic models.

Key Terms

OpenAPI 3.1
The current major version of the OpenAPI Specification (OAS), released in February 2021. An OpenAPI 3.1 document is a JSON or YAML file describing an HTTP API's endpoints, request/response shapes, authentication, and servers. The three required top-level fields are openapi: "3.1.0", info (with title and version), and paths. OpenAPI 3.1's most significant change over 3.0 is full alignment with JSON Schema draft 2020-12 — schema objects in an OpenAPI 3.1 document are valid JSON Schema documents, eliminating the need for a separate OpenAPI-specific schema validator and enabling direct use with Ajv, jsonschema, and other standard validators.
paths object
The top-level paths object maps URL path templates to Path Item Objects. Each key is a path template beginning with a forward slash, optionally containing curly-brace variable segments like /users/{id}. Each value is a Path Item Object containing HTTP method keys (get, post, put, patch, delete, etc.), each holding an Operation Object that defines operationId, parameters, requestBody, responses, and optional security. Parameters defined at the Path Item level (outside any HTTP method) apply to all operations on that path.
components/schemas
The components.schemas section of an OpenAPI document is a named collection of reusable JSON Schema objects. Every schema defined here can be referenced elsewhere using $ref: "#/components/schemas/User". This eliminates copy-paste of identical schema definitions across multiple operations and provides a single source of truth for each data type. In OpenAPI 3.1, these schemas are full JSON Schema 2020-12 documents supporting any keyword including if/then/else, unevaluatedProperties, prefixItems, and $defs. Common schemas: domain models (User, Product), request shapes (CreateUserRequest), response wrappers (PaginatedList, ApiError), and shared primitives.
$ref
A JSON Reference — a JSON object containing a single $ref key whose value is a URI or URI fragment. In OpenAPI, the most common form is an internal JSON Pointer: {""$ref": "#/components/schemas/User""}, where # is the document root and /components/schemas/User is the path to the target. Tools resolve $ref by replacing the reference object with the referenced value. External references (./schemas/user.json) and full URL references are also valid. In OpenAPI 3.1, $ref objects can include sibling keywords like description that override parts of the referenced schema — a feature not available in OpenAPI 3.0.
operationId
A string field on Operation Objects that uniquely identifies the operation within the entire OpenAPI document. It must be unique across all operations — duplicate operationId values are a validation error. The primary use is in code generation: the value becomes the function or method name in generated client SDKs. Use camelCase descriptive names: listUsers, createUser, getUserById, updateUser, deleteUser. Documentation tools use operationId as URL anchors in generated docs. If omitted, code generators fall back to a path+method combination, often producing verbose or inconsistent names that are harder to use.
content negotiation
The HTTP mechanism by which a client and server agree on the format of a request or response body. In OpenAPI, content negotiation is declared through the content map in requestBody and Response Objects — each key is a MIME type string and each value is a Media Type Object with a schema. A single operation can accept multiple request content types (e.g., application/json and multipart/form-data) and return multiple response content types (e.g., application/json and application/xml). The Accept request header and Content-Type response header implement negotiation at runtime; OpenAPI documents the available options statically.

FAQ

What is OpenAPI and how does it relate to JSON?

OpenAPI (formerly Swagger) is a specification for describing HTTP REST APIs in machine-readable format. An OpenAPI document is written in JSON or YAML — JSON is the canonical wire format since the spec itself is defined with JSON Schema. The document describes every endpoint, the shape of request/response payloads using JSON Schema objects, authentication requirements, and server metadata. Tools consume it to generate interactive documentation (Swagger UI, Redoc), typed client SDKs, server stubs, and mock servers. OpenAPI 3.1 aligns its schema dialect with JSON Schema draft 2020-12, making every schema object in an OpenAPI document a valid JSON Schema usable with any compliant tool.

What is the difference between OpenAPI 3.0 and 3.1?

The most significant change in OpenAPI 3.1 is full JSON Schema 2020-12 compatibility. OpenAPI 3.0 used a custom dialect derived from JSON Schema draft 04 — it removed and added keywords, creating two divergent dialects requiring separate validators. OpenAPI 3.1 eliminates this: schema objects are complete JSON Schema 2020-12 documents. Practical changes: nullable: true is replaced by adding "null" to the type array (e.g., {"type: ["string", "null"]"}); example (singular) is deprecated in favor of examples (plural); exclusiveMinimum/exclusiveMaximum now take numeric values instead of booleans; and a new webhooks top-level field replaces the x-webhooks extension pattern from 3.0.

How do I define a JSON request body in OpenAPI?

Use the requestBody object at the operation level with a content map keyed by MIME type. For JSON: { "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateUserRequest" } } } } }. Set required: true for operations that fail without a body. Define the schema inline or reference a reusable schema from components/schemas via $ref. To document multiple content types simultaneously (JSON and multipart), add multiple keys under content — each with its own schema. The examples map (plural) provides named example payloads for documentation tools; each can be inline or referenced from components/examples.

How do I reuse schemas with $ref in OpenAPI?

$ref is a JSON Reference — a JSON object with a single $ref key whose value is a URI. The most common internal form is {""$ref": "#/components/schemas/User""} where # refers to the document root and /components/schemas/User is a JSON Pointer to the schema. Tools resolve it by substituting the referenced schema in place of the $ref object. External file references (./schemas/user.json) are also valid. To extend a referenced schema, wrap it in allOf: {"{ "allOf": [{ "$ref": "#/components/schemas/Base" }, { "properties": { "extra": { "type": "string" } } }] }"}. In OpenAPI 3.1, $ref objects can have sibling keywords like description that override parts of the referenced schema. See JSON Schema guide.

How do I define authentication in OpenAPI?

Authentication uses two objects: security schemes in components/securitySchemes (define how auth works) and security arrays at document or operation level (apply schemes). Bearer JWT: { "BearerAuth": { "type": "http", "scheme": "bearer", "bearerFormat": "JWT" } }. API key in header: { "ApiKeyAuth": { "type": "apiKey", "in": "header", "name": "X-API-Key" } }. OAuth 2.0 with authorization code flow requires authorizationUrl, tokenUrl, and a scopes map. Apply globally with {""security": [{ "BearerAuth": [] }]"} then override individual operations — set security: [] to mark a specific endpoint as public, overriding the global requirement.

How do I generate API clients from an OpenAPI spec?

The most widely used tool is openapi-generator-cli. Install: npm install -g @openapitools/openapi-generator-cli. Generate a TypeScript Axios client: openapi-generator-cli generate -i spec.json -g typescript-axios -o ./src/api-client. The -i flag specifies the input spec (file path or URL), -g selects the generator, and -o sets the output directory. Supported generators include typescript-axios, typescript-fetch, python, java, go, ruby, swift5, kotlin, csharp, php, and 50+ more. Pin the generator version for CI reproducibility: npx @openapitools/openapi-generator-cli version-manager set 7.4.0. Alternatives: Kiota (Microsoft) for strongly-typed TypeScript clients, Speakeasy for production-grade SDKs.

What is the difference between OpenAPI and JSON Schema?

JSON Schema is a vocabulary for describing the structure and constraints of JSON data — it validates whether a JSON value conforms to a defined shape. OpenAPI is an API description format that uses JSON Schema as its data modeling language. An OpenAPI document describes the full HTTP API contract: URL paths, HTTP methods, parameters, request/response bodies (described with JSON Schema), authentication, and metadata. Think of JSON Schema as the type system and OpenAPI as the complete API specification. In OpenAPI 3.1, every schema object inside requestBody, responses, or components/schemas is a complete, standalone JSON Schema 2020-12 document — you can validate it with Ajv directly. See JSON schema validation.

How do I validate a JSON payload against an OpenAPI schema?

Since OpenAPI 3.1 schemas are valid JSON Schema 2020-12, extract the schema from components/schemas and validate with Ajv: const ajv = new Ajv2020(); ajv.addSchema(spec); const validate = ajv.getSchema("#/components/schemas/User"); const valid = validate(payload);. For full OpenAPI request/response validation including parameters and headers, use express-openapi-validator — Node.js middleware that auto-validates all requests against your spec and returns structured 400 errors automatically. For Python, use openapi-core or connexion. The example and examples fields in OpenAPI schemas are documentation-only — they are not executed as test cases automatically. See JSON Schema guide for validator setup.

Further reading and primary sources