What are the best practices for defining error responses in OpenAPI? #
Error responses are a critical part of any API contract. A consumer encountering an error needs to understand what went wrong, why it happened, and ideally what to do about it. Yet error responses are often the most neglected part of an OpenAPI document — frequently undocumented, inconsistently structured, or documented only for the happy path. This article covers best practices for defining error responses in OpenAPI that give consumers the information they need to build resilient integrations.
Why Error Documentation Matters #
When error responses are poorly documented:
- Client developers guess at how to handle errors, leading to brittle error-handling code that breaks when new error formats appear.
- Support teams spend more time investigating integration issues that would be self-service if the error format were well-documented.
- SDKs and code generators produce incomplete error handling because the OpenAPI document lacks error response schemas.
- API governance tools cannot audit error coverage because there’s nothing to audit.
Comprehensive error documentation in OpenAPI is an investment in the developer experience of everyone who consumes the API.
Document All Relevant HTTP Status Codes #
Every OpenAPI operation should document every HTTP status code it can realistically return. The responses object uses status codes as keys:
paths:
/users/{id}:
get:
summary: Get a user by ID
parameters:
- name: id
in: path
required: true
schema:
type: string
responses:
"200":
description: The user was found
content:
application/json:
schema:
$ref: '#/components/schemas/User'
"400":
description: Invalid user ID format
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
"401":
description: Authentication required
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
"403":
description: Insufficient permissions to access this user
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
"404":
description: User not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
"429":
description: Rate limit exceeded
headers:
Retry-After:
schema:
type: integer
description: Seconds to wait before retrying
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
"500":
description: Unexpected server error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
A common mistake is documenting only 200 and 404. Operations should document at minimum: 400 (bad request), 401 (unauthenticated), 403 (unauthorized), 404 (not found where applicable), 429 (rate limited), and 500 (server error).
Use a Consistent Error Schema #
All error responses should use the same (or structurally similar) schema. Inconsistent error formats across endpoints force consumers to implement different error-handling logic for each operation — a major developer experience failure.
Minimal Error Schema #
components:
schemas:
Error:
type: object
required: [code, message]
properties:
code:
type: string
description: A machine-readable error code
example: "USER_NOT_FOUND"
message:
type: string
description: A human-readable description of the error
example: "No user found with the provided ID"
Rich Error Schema with Details #
For APIs that need to communicate validation errors across multiple fields:
components:
schemas:
Error:
type: object
required: [code, message]
properties:
code:
type: string
example: "VALIDATION_ERROR"
message:
type: string
example: "The request body contains invalid fields"
details:
type: array
items:
$ref: '#/components/schemas/ErrorDetail'
requestId:
type: string
description: Unique identifier for this request, for support reference
example: "req_4e8f9a2b"
ErrorDetail:
type: object
required: [field, message]
properties:
field:
type: string
description: The field that caused the error
example: "email"
message:
type: string
description: Description of the validation failure
example: "Must be a valid email address"
code:
type: string
example: "INVALID_FORMAT"
Adopt RFC 9457 Problem Details #
RFC 9457 (formerly RFC 7807) defines a standard JSON format for HTTP API error responses called Problem Details. Adopting it means consumers can handle errors from any RFC 9457-compliant API using the same error model:
components:
schemas:
ProblemDetails:
type: object
properties:
type:
type: string
format: uri
description: A URI identifying the problem type
example: "https://api.example.com/errors/user-not-found"
title:
type: string
description: A short, human-readable summary of the problem type
example: "User Not Found"
status:
type: integer
description: The HTTP status code
example: 404
detail:
type: string
description: A human-readable explanation specific to this occurrence
example: "No user with ID 'abc123' exists"
instance:
type: string
format: uri
description: A URI identifying this specific occurrence of the problem
example: "/users/abc123"
The type URI is a stable, linkable identifier for the error type — ideally pointing to documentation that explains the error and how to resolve it. This makes Problem Details self-documenting in a way that opaque numeric error codes are not.
Use default for Catch-All Error Responses
#
OpenAPI supports a special default response key that matches any HTTP status code not explicitly listed. This is useful for documenting a generic server error response without listing every possible 5xx code:
responses:
"200":
description: OK
"404":
description: Not found
default:
description: An unexpected error occurred
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
The default key complements explicit status codes — it does not replace specific ones.
Define Reusable Error Responses in components
#
For APIs with many operations, defining error responses once in components/responses and referencing them with $ref keeps the document DRY and ensures consistency:
components:
responses:
BadRequest:
description: The request is malformed or contains invalid parameters
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
examples:
invalidId:
summary: Invalid ID format
value:
code: "INVALID_PARAMETER"
message: "The provided ID must be a UUID"
Unauthorized:
description: Authentication is required or the token is invalid
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
Forbidden:
description: The authenticated user lacks permission for this operation
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
NotFound:
description: The requested resource was not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
TooManyRequests:
description: Rate limit exceeded
headers:
Retry-After:
schema:
type: integer
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
InternalServerError:
description: An unexpected server error occurred
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
Operations then reference these with $ref:
responses:
"200":
...
"400":
$ref: '#/components/responses/BadRequest'
"401":
$ref: '#/components/responses/Unauthorized'
"404":
$ref: '#/components/responses/NotFound'
"429":
$ref: '#/components/responses/TooManyRequests'
default:
$ref: '#/components/responses/InternalServerError'
Include Error Examples #
Examples in error responses help consumers understand exactly what an error looks like in practice:
"404":
description: User not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
examples:
userNotFound:
summary: User not found
value:
code: "USER_NOT_FOUND"
message: "No user found with ID 'f47ac10b'"
requestId: "req_7g3h8k2m"
Conclusion #
Well-documented error responses in OpenAPI are as important as success response documentation. By defining a consistent error schema, adopting RFC 9457 Problem Details, documenting all relevant HTTP status codes, centralizing error response definitions in components/responses, and including concrete examples, API teams give consumers everything they need to build robust, resilient integrations. An API whose errors are as well-designed and documented as its success paths is an API that earns its consumers’ trust.
Last updated on April 30, 2026.