Common OpenAPI spec mistakes and how to avoid them
An OpenAPI document is only as useful as it is correct. Interactive docs, generated SDKs, mock servers, contract tests and API gateways all read the same file, and each tolerates mistakes differently. A spec that renders fine in one documentation viewer can crash a code generator or silently produce a client with missing methods. This guide covers the mistakes that appear most often in real specs and how to fix each one. Examples use OpenAPI 3.x in YAML.
1. Path parameters that are not declared
Every {placeholder} in a path must have a matching parameter with in: path and required: true:
paths:
/users/{userId}/orders/{orderId}:
get:
parameters:
- name: userId
in: path
required: true
schema: { type: string, format: uuid }
- name: orderId
in: path
required: true
schema: { type: integer }
Common variations of this bug: the placeholder and the parameter name differ in case ({userId} versus userid), required: true is missing, or a parameter is declared at the path level in one place and forgotten in another. Code generators usually fail or generate methods without the argument.
2. Broken $ref links
References like $ref: '#/components/schemas/User' must point to something that exists. Typos, renamed schemas and references copied from another spec all produce broken links. Some tools fail loudly; others render an empty schema, which is worse because nobody notices. Also note the path must be exact: #/components/schema/User (singular) is wrong. Validate every reference whenever you rename a schema.
3. Duplicate or missing operationIds
operationId becomes the method name in generated clients, such as client.listOrders(). If two operations share an ID, generators either fail or overwrite one method with the other. If IDs are missing, generators invent names from the path and method, like usersUserIdOrdersGet, which are unpleasant to use. Give every operation a unique, verb-first ID: listOrders, getOrder, createOrder, cancelOrder.
4. Only documenting the happy path
Many specs list only 200. Clients then have no idea what an error looks like, and generated SDKs cannot deserialise error bodies into typed exceptions. Document the errors each operation can return, with a shared error schema:
responses:
'201':
description: Order created
content:
application/json:
schema: { $ref: '#/components/schemas/Order' }
'422':
$ref: '#/components/responses/ValidationError'
'401':
$ref: '#/components/responses/Unauthorized'
Every response also needs a description, which is required by the specification and missing surprisingly often. Status codes must be quoted strings in YAML, or the parser may read them as integers, which some tools reject. For choosing codes, see the status code guide.
5. Getting nullable wrong between versions
How you say "this field may be null" changed between versions:
- OpenAPI 3.0 uses
nullable: truenext totype. - OpenAPI 3.1 follows JSON Schema and uses a type array:
type: [string, 'null'].nullableno longer exists.
# OpenAPI 3.0
middleName:
type: string
nullable: true
# OpenAPI 3.1
middleName:
type: [string, 'null']
Mixing the two means the keyword is ignored, and generated clients reject real responses that contain nulls. Remember also that "nullable" and "optional" are different: a field missing from required may be absent, while a nullable field may be present with a null value.
6. Forgetting the required list
In JSON Schema, properties are optional unless listed in required. Specs that never declare required generate clients where every field is optional, forcing null checks everywhere and hiding real contract violations. List the fields your API always returns and always needs.
7. Inline schemas everywhere
Defining the same object inline in several operations leads to drift: one copy gains a field and the others do not. Generators also create separate, awkwardly named classes for each inline copy, such as InlineResponse2003. Move shared shapes into components/schemas and reference them. Inline schemas are fine for genuinely one-off shapes.
8. Mismatched security definitions
A security requirement must reference a scheme defined in components/securitySchemes, by exactly the same name. A typo here means documentation tools show no auth at all and generated clients never send credentials. If most operations require auth, set a global security block and override it with security: [] on public endpoints like health checks.
9. Examples that do not match the schema
Examples are what developers copy first, and mock servers return them as responses. An example with a field the schema does not have, a string where the schema says integer, or a stale enum value teaches clients the wrong contract. Validate examples against their schemas as part of linting.
10. Version confusion
The openapi field (3.0.3, 3.1.0) declares the specification version and controls how tools parse the document. The info.version field is the version of your API. Mixing them up, or using 3.1 syntax under a 3.0 header, produces confusing validation errors. Swagger 2.0 documents use a swagger: "2.0" field and a different structure entirely, with definitions instead of components/schemas.
Unused components
Schemas that nothing references are not errors, but they accumulate as APIs evolve, confuse readers and bloat generated code. Treat them as warnings and clean them up periodically.
Make validation automatic
Most of these mistakes are invisible when reading YAML, but trivial for a tool to detect. Validate the spec on every pull request in CI with a linter such as Redocly CLI (npx @redocly/cli lint openapi.yaml) or Spectral (npx @stoplight/spectral-cli lint openapi.yaml, with a .spectral.yaml containing extends: ["spectral:oas"]), and use the same checks locally while editing. For a quick check without installing anything, paste your spec into the OpenAPI Validator; it reports broken references, undeclared path parameters, duplicate operationIds, missing responses and unused schemas with their location. If a tool only accepts JSON, convert the spec with YAML ⇄ JSON first.