Skip to content

Errors

Effective error communication is an important part of designing simple and intuitive APIs. Services returning standardized error responses enable API clients to construct centralized common error handling logic. This common logic simplifies API client applications and eliminates the need for cumbersome custom error handling code.

Services must clearly distinguish successful responses from error responses.

The structure of the error response must be consistent across all APIs of the service. You must use the error response structure below, which comes from the Problem Details for HTTP APIs RFC 9457.

The media-type for an error response must be application/problem+json.

An error response should contain the following fields:

NameTypeRequiredDescription
typestringYA URI reference that identifies the problem type. If the type URI is a locator (has http/https scheme), dereferencing it should provide human-readable documentation for the problem type.
statusintegerThe HTTP status code that best describes the type of problem detected.
titlestringA human-readable general description of the problem. It must not include information about a specific occurrence; that should be part of detail or extension members.
detailstringA human-readable explanation specific to this occurrence of the problem.
instancestringA unique identifier for this specific occurrence of the problem.

An error response may contain additional fields appropriate for a specific error type. See Extension Members below for details.

All human-readable error details returned by the service must use the same language.

A JSON representation of an error response might look like the following:

{
"type": "/problems/rate-limit-exceeded",
"status": 429,
"title": "Rate Limit Exceeded",
"detail": "You have exceeded the rate limit of 100 requests per minute. Please retry after 42 seconds.",
"instance": "/errors/unique-id-abc123"
}

If present, the status field must contain the numeric HTTP status code for this error (e.g., 404, 400, 500), and it must use the same status code as the actual HTTP response. This field is purely for the consumer’s convenience; it allows them to see the status code directly in the error object.

The title should be the same for all occurrences of the same problem type. The detail should describe this specific occurrence; meaning it will most likely change between occurrences of the same problem.

The error title and detail fields should help a reasonably technical user understand and resolve the issue, and should not assume that the user is an expert in your particular API. Additionally, the error title and detail must not assume that the user will know anything about its underlying implementation.

The error detail should be brief but actionable. Any extra information should be provided in additional properties. If even more information is necessary, you should provide a link where a reader can get more information or ask questions to help resolve the issue.

The error detail field

  • should describe this specific instance of the error.
  • should be a developer-facing, human-readable “debug message”.
  • should both explain the error and offer an actionable resolution to it.
  • value may significantly change over time for the same error and should not be string-matched by any clients to determine the error.

The type field is a URI that serves as a permanent identifier for that category of problem. It may be one of:

  • about:blank (default)
  • An identifier in URI format that doesn’t resolve
  • A full URL that leads to documentation

See the Common Error Types section for guidance on common types.

This indicates that the HTTP status code itself serves as the error category; it literally means “see the HTTP code”. This should be used for errors that are self-explanatory and map directly to standard HTTP codes (e.g., 401 Unauthorized, 403 Forbidden, 404 Not Found).

When using about:blank, you must rely on the title and detail fields to convey necessary information. The detail field is especially important here since it becomes the primary way to communicate what went wrong in this specific occurrence.

Use this method for domain-specific errors. These are identifiers formatted as URIs that don’t resolve (e.g., /problems/constraint-violation). The identifier must be formatted as a URI resource path in the format /problems/specific-problem. These identifiers must be stable; meaning they must not change over time. This approach is recommended because:

  • All important parts of the API must be documented using OpenAPI anyway
  • Full URLs tend to be fragile and not very stable over longer periods due to organizational and documentation changes
  • Descriptions might easily get out of sync with actual behavior

Examples:

/problems/constraint-violation
/problems/out-of-stock
/problems/insufficient-funds

If there is a benefit to providing documentation beyond what OpenAPI provides, then a full URL may be used. In this case, the URL should resolve to human-readable documentation that explains what this error means, why it occurs, how to fix it, and what the client should do in response.

Examples (you can actually visit these and read their docs):

The instance field is a URI field points to information about this specific error occurrence. It’s most useful when you have customer support scenarios where users need to report errors, and you need a way to look up exactly what happened. The instance field does not point to the resource that was requested. Instead, it identifies this particular error event.

The instance URI could be something like:

  • /errors/2024-02-03/req-abc123: a reference to this request in your logs
  • urn:uuid:f81d4fae-7dec-11d0-a765-00a0c91e6bf6: just a unique identifier (doesn’t have to be dereferenceable)
  • https://errors.example.com/incidents/a3f5b2c1: a link to this specific error in your error tracking system

Problem type definitions may extend the problem details object with additional members that are specific to that problem type.

For example, this defines two such extensions - balance and accounts to convey additional, problem-specific information.

{
"type": "https://example.com/out-of-credit",
"title": "You do not have enough credit.",
"detail": "Your current balance is 30, but that costs 50.",
"instance": "/account/12345/msgs/abc",
"balance": 30,
"accounts": ["/account/12345", "/account/67890"]
}

Clients consuming problem details must ignore any extension members that they don’t recognize. This allows problem types to evolve and include additional information in the future. Clients should avoid relying on extension members in their core error handling logic, as these members may change over time.

Validation errors should use the violations extension member. This extension must be an array that describes the details of each validation error. Each member must be an object containing:

  • field: indicates which field violated a constraint
  • message: describes the issue with that field

For example:

{
"type": "https://zalando.github.io/problem/constraint-violation",
"title": "Constraint Violation",
"status": 400,
"violations": [
{
"field": "email",
"message": "must be a valid email address"
},
{
"field": "givenName",
"message": "is required"
},
{
"field": "age",
"message": "must be a positive integer"
},
{
"field": "profile.color",
"message": "must be 'green', 'red' or 'blue'"
}
]
}

The best, actionable error detail includes dynamic segments. These variable parts of the message are specific to a particular request. Consider the following example:

The book “The Great Gatsby” is unavailable at the library “Garfield East”. It is expected to be available again on 2199-05-13.

The preceding error detail is made actionable by the context, both that originates from the request, the title of the Book and the name of the Library, and by the information that is known only by the service, i.e. the expected return date of the Book.

All dynamic variables found in error detail must also be present as additional properties of the error response. These variables should be in a field called parameters.

{
"type": "/problems/resource-unavailable",
"status": 409,
"title": "Resource Unavailable",
"detail": "The book \"The Great Gatsby\" is unavailable at the library \"Garfield East\". It is expected to be available again on 2199-05-13.",
"parameters": {
"bookTitle": "The Great Gatsby",
"library": "Garfield East",
"expectedReturnDate": "2199-05-13"
}
}

Once present for a particular error type, additional properties must continue to be included in the error response to be backwards compatible, even if the value for a particular property is empty.

The title and detail fields should be presented in English (or the service’s primary language).

Server-side localization: If the service supports multiple languages, a localized detail may be included as an extension named localizedDetail. The service should determine the language based on the Accept-Language header.

Client-side localization: For services that expect clients to handle their own localization, dynamic variables should be included in the parameters extension member. This allows clients to construct localized messages using their own i18n frameworks while ensuring all necessary data is available.

Services may support both approaches to accommodate different client capabilities.

The IANA maintains a registry of standard problem types. If a problem type exists in the IANA registry that fits your use case, you should use it instead of defining your own.

For problem types not covered by the IANA registry, the following are recommended values for common scenarios:

Type URITitleStatus
/problems/constraint-violationConstraint Violation400
/problems/business-rule-violationBusiness Rule Violation422
/problems/already-existsAlready Exists409
/problems/invalid-state-transitionInvalid State Transition409
/problems/resource-unavailableResource Unavailable409
/problems/rate-limit-exceededRate Limit Exceeded429
/problems/quota-exceededQuota Exceeded429
about:blankNot Found404
about:blankUnauthorized401
about:blankForbidden403
about:blankService Unavailable503
about:blankServer Error500

These are examples and should be adapted to fit your API’s specific needs. The important principle is that error types must be stable identifiers that categorize errors consistently.

When an API encounters multiple problems that do not share the same type, the most relevant or urgent problem should be represented in the response. While it is possible to create generic “batch” problem types that convey multiple, disparate types, they do not map well into HTTP semantics.

APIs should not support partial errors. Partial errors add significant complexity for users, because they usually sidestep the use of error codes, or move those error codes into the response message, where the user must write specialized error handling logic to address the problem.

However, occasionally partial errors are necessary, particularly in bulk operations where it would be hostile to users to fail an entire large request because of a problem with a single entry.

Methods that require partial errors should use long-running operations, and the method should put partial failure information in the metadata message. The errors themselves must still be represented as an error object as described in this AEP.

components:
schemas:
Problem:
type: object
required:
- type
properties:
type:
type: string
format: uri
description: >
A URI reference that identifies the problem type. This serves as a
permanent identifier for the category of problem. May be a URI that
doesn't resolve (e.g., /problems/constraint-violation), about:blank
for standard HTTP errors, or a URL that leads to documentation.
default: about:blank
example: /problems/resource-unavailable
status:
type: integer
format: int32
description: >
The HTTP status code for this error. This matches the status code in
the HTTP response.
minimum: 100
maximum: 600
exclusiveMaximum: true
example: 409
title:
type: string
description: >
A brief summary of the error type. This will be the same for all
occurrences of this error.
example: Resource Unavailable
detail:
type: string
description: >
A detailed explanation of what went wrong with your specific
request. This describes the problem and may suggest how to resolve
it.
example: >-
The book "The Great Gatsby" is unavailable at the library "Garfield
East". It is expected to be available again on 2199-05-13.
instance:
type: string
format: uri
description: >
A unique identifier for this specific error occurrence. Use this
when contacting support to help identify your issue in logs.
example: urn:uuid:f81d4fae-7dec-11d0-a765-00a0c91e6bf6
violations:
type: array
description: >
A list of validation errors when your request data was invalid. Each
item describes which field had an issue and what the problem was.
items:
type: object
required:
- field
- message
properties:
field:
type: string
description: The field that had a validation error
example: email
message:
type: string
description: What was wrong with this field
example: must be a valid email address
parameters:
type: object
description: >
Additional data related to this error. These values are referenced
in the detail message and provided in a structured format for
programmatic use.
additionalProperties: true
example:
bookTitle: The Great Gatsby
library: Garfield East
expectedReturnDate: '2199-05-13'

RFC 9457 provides a standardized, machine-readable format for error responses. Before this standard, every API invented its own error format, forcing clients to write custom error handling logic for each API they consumed. By adopting RFC 9457, clients can use standard libraries to parse errors across different APIs, error responses become more predictable and consistent, and tooling can understand errors without custom configuration. The alternative, inventing our own error format, would provide no benefits while creating unnecessary friction for API consumers.

Validation errors are fundamentally different from other errors because they typically involve multiple fields, each with its own specific issue. The violations array provides a structured way to communicate which field(s) failed validation, what the specific issue was with each field, and supports nested fields like profile.color. This allows clients to display field-specific error messages in forms, highlight invalid fields in the UI, and programmatically retry with corrections. Additionally, this structure is the default in Micronaut’s RFC 9457 implementation.

The parameters field serves two critical purposes: machine-readability and maintainability. When error details contain dynamic values like IDs, counts, or dates, clients need access to those values in a structured format for logging, monitoring, programmatic error handling, client-side localization, etc. By explicitly declaring which values are dynamic, we make it clear what data changes between error occurrences and enable clients to extract specific values without parsing natural language. Without parameters, clients would need to parse the detail string to extract values, which is fragile and breaks when message wording changes. While RFC 9457 allows extension members at the top level, grouping them under parameters provides better organization and clearly distinguishes them from standard problem details fields, though this may require customization for some libraries like Spring Boot.

While RFC 9457 allows type to be a full URL pointing to documentation, we recommend against this because URLs are fragile and change when organizations reorganize, documentation moves, or domains change. Keeping documentation URLs in sync with actual API behavior requires ongoing effort, and documentation can drift out of sync or become outdated. OpenAPI specifications already document error responses, including what errors mean and when they occur, so a separate documentation page adds redundant information. If we maintain a stable, versioned error registry (similar to IANA’s or SmartBear’s), full URLs can provide value, but for most teams, URI identifiers like /problems/constraint-violation provide the benefits of categorization without the maintenance burden.

Why support both localizedDetail and parameters for localization?

Section titled “Why support both localizedDetail and parameters for localization?”

Different clients have different capabilities and requirements. Simple clients like CLI tools, quick scripts, and log aggregators benefit from receiving a human-readable, localized message directly without implementing i18n frameworks. Sophisticated clients like web and mobile applications often have their own i18n infrastructure and want full control over message formatting and localization using structured data from parameters. Supporting both approaches ensures the API works well for all consumers without forcing either group to do unnecessary work.