Problem Details (RFC 9457): Getting Hands-On with API Error Handling
Welcome back! Let’s dive right into the world of API error handling with the Problem Details standard. In Part I, we unpacked several challenges arising from inconsistent error reporting—challenges that amplify as the digital ecosystem expands through the production of more APIs. From the criticality of delivering actionable error messages to the pitfalls of custom error methods and security vulnerabilities, we saw how RFC 9457’s Problem Details provides a robust framework to standardize API error responses.
We highlighted the evolution from RFC 7807 to RFC 9457, including significant advancements in the articulation of API errors, turning error handling from a developer’s nightmare into a streamlined, informative, and actionable process. This standard not only makes error handling more intuitive for developers, but also enhances security and consistency across digital platforms.
Now, with a solid understanding of the standard’s benefits and framework, let’s transition into practical implementations. This segment will guide you through leveraging Problem Details within your APIs, using real-world examples and resources to ensure your systems deliver ‘bad news’ in the best possible way.
Getting Started with Problem Details
With the introduction of any standard into a team or organization, getting started is often the hardest step. Start by familiarizing yourself with RFC 9457 [1].
Don’t Start from Scratch
To avoid common pitfalls and accelerate the adoption of Problem Details take advantage of the following resources and artifacts which will bootstrap your adoption and set you on a course to deliver consistent error handling with your teams.
Registries
As mentioned in Part I of the series, the new RFC 9457 introduces the concept of a registry of common problem type URIs. The formal registry hosted at IANA [2] as well as the SmartBear Problems Registry [3] act as valuable resources when determining what problem types are relevant for your APIs.
- IANA Registry: The formal registry holds standardized problem type URIs that you can use out-of-the-box or as references when defining your own problem types.
- SmartBear Problems Registry: This registry offers a catalog of common problem types specific to various API scenarios curated by the SmartBear Team. In future, some may migrate to the IANA registry.
Objects, Schemas, and Extensibility
The benefits of standards are we can circumvent much of the bike-shedding associated with the shape of error details. The RFC provides the following non-normative JSON Schema for HTTP Problem Details which guarantees the base shape of errors.
In situations where you need to provide more information than covered by the JSON Schema above, rest assured the built-in extensibility is a powerful mechanism allowing you to mold the standard to meet the needs of your team(s).
Extensibility brings its own challenges. Therefore, it’s good practice to clearly define your extension points and communicate to clients consuming problem details that they MUST ignore extensions they do not recognize. To make the process predictable for those implementing (and indeed those consuming the responses), I would also recommend crafting a JSON Schema including your extensions.
Here’s a JSON Schema [5] including the errors and code extensions we use for problem details within new APIs at SmartBear:
{
"$schema": "https://json-schema.org/draft/2019-09/schema",
"type": "object",
"properties": {
"type": {
"type": "string",
"description": "A URI reference that identifies the problem type.",
"format": "uri",
"maxLength": 1024
},
"status": {
"type": "integer",
"description": "The HTTP status code generated by the origin server for this occurrence of the problem.",
"format": "int32",
"minimum": 100,
"maximum": 599
},
"title": {
"type": "string",
"description": "A short, human-readable summary of the problem type. It should not change from occurrence to occurrence of the problem, except for purposes of localization.",
"maxLength": 1024
},
"detail": {
"type": "string",
"description": "A human-readable explanation specific to this occurrence of the problem.",
"maxLength": 4096
},
"instance": {
"type": "string",
"description": "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced.",
"maxLength": 1024
},
"code": {
"type": "string",
"description": "An API specific error code aiding the provider team understand the error based on their own potential taxonomy or registry.",
"maxLength": 50
},
"errors": {
"type": "array",
"description": "An array of error details to accompany a problem details response.",
"maxItems": 1000,
"items": {
"type": "object",
"description": "An object to provide explicit details on a problem towards an API consumer.",
"properties": {
"detail": {
"type": "string",
"description": "A granular description on the specific error related to a body property, query parameter, path parameters, and/or header.",
"maxLength": 4096
},
"pointer": {
"type": "string",
"description": "A JSON Pointer to a specific request body property that is the source of error.",
"maxLength": 1024
},
"parameter": {
"type": "string",
"description": "The name of the query or path parameter that is the source of error.",
"maxLength": 1024
},
"header": {
"type": "string",
"description": "The name of the header that is the source of error.",
"maxLength": 1024
},
"code": {
"type": "string",
"description": "A string containing additional provider specific codes to identify the error context.",
"maxLength": 50
}
},
"required": [
"detail"
]
}
}
},
"required": [
"detail"
]
}
This gives a powerful and detailed ability to describe the occurrence of an error related to parameters or a request body.
Let’s run through a few examples based on the above JSON Schema
- For an issue with a missing request parameter (e.g. a query parameter), we can leverage the
errorsextension to provide explicit information on the missing parameter via thedetailsandparameterproperties
{
"type": "https://problems-registry.smartbear.com/missing-request-parameter",
"status": 400,
"title": "Missing request parameter",
"detail": "The request is missing an expected query or path parameter.",
"code": "400-03",
"errors": [
{
"detail": "The query parameter {name} is required.",
"parameter": "name"
}
]
}
- For an issue with a malformed request body property, we can leverage the
errorsextension to provide explicit information on the issue as well as the property location via thedetailsandpointer(which specifies a JSON Pointer to the location of the property) properties
{
"type": "https://problems-registry.smartbear.com/invalid-body-property-format",
"status": 400,
"title": "Invalid Body Property Format",
"detail": "The request body contains a malformed property.",
"code": "400-04",
"errors": [
{
"detail": "Must be a positive integer",
"pointer": "/quantity"
}
]
}
- If we find multiple errors, and want to return all violations to the client rather than forcing an over chatty type of engagement, we can leverage the
errorsarray extension to include details on all applicable errors for the associated problem type
{
"type": "https://problems-registry.smartbear.com/business-rule-violation",
"status": 422,
"title": "Business Rule Violation",
"detail": "The request body is invalid and not meeting business rules.",
"code": "422-01",
"errors": [
{
"detail": "Maximum quantity allowed in 999",
"pointer": "/quantity"
},
{
"detail": "We do not offer `next-day` delivery to non-EU addresses",
"pointer": "/shippingAddress/country"
},
{
"detail": "We do not offer `next-day` delivery to non-EU addresses",
"pointer": "/shippingOption"
}
]
}
Accelerate with a Ready to Use Domain for OpenAPI
To make it even easier to adopt problem details into your next API project, I’ve wrapped up the assets above into a ready to use SwaggerHub domain [4], which can be referenced from various parts of your OpenAPI descriptions.
The domain comprises of:
- Schemas: the full and extended (a.k.a. opinionated) schemas for HTTP Problem Details
- Examples: A rich list of representative response examples for the supported problem types
- Responses: A ready to reference list of OpenAPI compatible responses for the supported problem types
I’ll walk through an example of leveraging the free and public domain within an OpenAPI description in the section below.
Using Problem Details with OpenAPI
Leveraging Problem Details within an OpenAPI description is easier than you think, and it’s made event simpler by leveraging some of the above artifacts. Let’s create an OpenAPI description for a simple Bookstore API to showcase can help improve the error responses within an API design.
In the first pass through authoring the OpenAPI description, I’ll setup the base objects (Info, Tags, Servers), two resources for retrieving books and placing a book order, and the relevant schemas for the books and orders resources.
openapi: 3.0.3 info: title: Bookstore API version: 0.0.1 description: | The **Books API** - allows searching of books from the book catalog as well as retrieving the specific details on a selected book. Once you find the book you are looking for, you can make an order. termsOfService: http://swagger.io/terms/ contact: name: DevRel at SmartBear email: info@smartbear.com license: name: Apache 2.0 url: http://www.apache.org/licenses/LICENSE-2.0.html tags: - name: Bookstore description: APIs for our fictional bookstore servers: # Added by API Auto Mocking Plugin - description: SwaggerHub API Auto Mocking url: https://virtserver.swaggerhub.com/frank-kilcommins/Bookstore-API/1.0.0 paths: /books: get: summary: Get a list of books based on the provided criteria description: | This API method supports searching the book catalog based on book title or author name operationId: getBooks tags: - Bookstore parameters: - name: title description: The title (or partial title) of a book in: query required: false schema: type: string maxLength: 200 format: string - name: author description: The author’s name (or partial author name) in: query required: false schema: type: string maxLength: 150 format: string - name: limit description: The maximum number of books to return in: query required: false schema: type: integer format: int64 minimum: 1 maximum: 1000 default: 10 responses: '200': $ref: '#/components/responses/books' '400': description: 400 Bad Request '401': description: 401 Unauthorized '500': description: 500 Internal Server Error content: application/json: schema: type: object properties: code: type: integer format: int32 example: 500 message: type: string example: "Internal Server Error" details: type: string example: "An unexpected error occurred" /orders: post: summary: Place book order description: | This API method allows placing an order for one or more books operationId: createOrder tags: - Bookstore requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/Order' responses: '200': description: Successful Response content: application/json: schema: $ref: '#/components/schemas/OrderDetails' '401': description: 401 Unauthorized '422': description: Validation Error '500': description: Internal Server Error components: schemas: BookOrder: type: object properties: bookId: type: string description: The book identifier format: uuid maxLength: 36 example: 87da4501-4b52-4ea2-a2be-7dda8650f7eb quantity: type: integer format: int64 minimum: 1 maximum: 10000 Order: properties: books: type: array maxItems: 100 items: $ref: '#/components/schemas/BookOrder' deliveryAddress: type: string minLength: 10 maxLength: 500 type: object required: - books - deliveryAddress additionalProperties: false OrderDetails: properties: books: type: array description: The books that are part of the order maxItems: 1000 items: $ref: '#/components/schemas/BookOrder' deliveryAddress: type: string description: The address to deliver the order to maxLength: 1000 id: type: string description: The order identifier format: uuid maxLength: 36 example: 87da4501-4b52-4ea2-a2be-7dda8650f7eb createdAt: type: string description: When the order was created format: date-time pattern: '^[0-9]{4}-[0-9]{2}-[0-9]{2}T[012][0-9]:[0-5][0-9]:[0-5][0-9]Z$' maxLength: 250 updatedAt: type: string description: When the order was updated format: date-time pattern: '^[0-9]{4}-[0-9]{2}-[0-9]{2}T[012][0-9]:[0-5][0-9]:[0-5][0-9]Z$' maxLength: 250 status: $ref: '#/components/schemas/OrderStatusEnum' type: object required: - books - deliveryAddress - id - createdAt - updatedAt OrderStatusEnum: type: string enum: - placed - paid - delivered Book: description: The schema object for a Book type: object additionalProperties: false properties: id: description: the identifier for a book type: string format: uuid maxLength: 36 example: 87da4501-4b52-4ea2-a2be-7dda8650f7eb title: type: string description: The book title maxLength: 1000 example: "Designing APIs with Swagger and OpenAPI" authors: type: array description: A list of book authors maxItems: 1000 items: type: string description: A string containing an author's name maxLength: 250 minItems: 1 maxItems: 1000 example: "[Joshua S. Ponelat, Lukas L. Rosenstock]" published: type: string format: date pattern: "^[0-9]{4}-[0-9]{2}-[0-9]{2}$" maxLength: 250 example: "2022-05-01" responses: books: description: List of books content: application/json: schema: type: array maxItems: 1000 items: $ref: '#/components/schemas/Book' securitySchemes: ApiKeyAuth: type: apiKey in: header name: api_key security: - ApiKeyAuth: []
This simple OpenAPI description gives me the following interactive API documentation which one might consider robust as it covers multiple errors responses. However, it lacks critical elements to help surface the potential errors that can occur with the API.
How can I catch inconsistent errors?
To help ensure that we do not forget to apply the HTTP Problem Details standard within our APIs, I would recommend adding the following Spectral rules to your governance style guides. These two rules will provide feedback if you define errors without any content or if you use an unexpected format to serve the error details:
# Author: Frank Kilcommins (https://github.com/frankkilcommins) no-errors-without-content: message: Error responses MUST describe the error description: Error responses should describe the error that occurred. This is useful for the API consumer to understand what went wrong and how to fix it. Please provide a description of the error in the response. given: $.paths[*]..responses[?(@property.match(/^(4|5)/))] then: field: content function: truthy formats: [oas3] severity: warn # Author: Phil Sturgeon (https://github.com/philsturgeon) no-unknown-error-format: message: Error response should use a standard error format. description: Error responses can be unique snowflakes, different to every API, but standards exist to make them consistent, which reduces surprises and increase interoperability. Please use either RFC 7807 (https://tools.ietf.org/html/rfc7807) or the JSON:API Error format (https://jsonapi.org/format/#error-objects). given: $.paths[*]..responses[?(@property.match(/^(4|5)/))].content.*~ then: function: enumeration functionOptions: values: - application/vnd.api+json - application/problem+json - application/problem+xml formats: [oas3] severity: warn
With these rules in place, I get the following opinionated feedback on my 0.0.1 version of the Bookstore API:
63:15 warning no-errors-without-content Error responses MUST describe the error paths./books.get.responses[400] 65:15 warning no-errors-without-content Error responses MUST describe the error paths./books.get.responses[401] 70:30 warning no-unknown-error-format Error response should use a standard error format. paths./books.get.responses[500].content.application/json 105:15 warning no-errors-without-content Error responses MUST describe the error paths./orders.post.responses[401] 107:15 warning no-errors-without-content Error responses MUST describe the error paths./orders.post.responses[422] 109:15 warning no-errors-without-content Error responses MUST describe the error paths./orders.post.responses[500]
How can I improve the error responses?
Now that I have a handle on where the shortcomings are in my initial design, how can I improve the error responses? As the SwaggerHub Problem Details domain is published publicly, I can take advantage to easily improve the error responses in my Bookstore API.
In many scenarios, it’s possible to just leverage the direct responses and examples are they generically represented the structure. This is what I do for the POST /orders resource:
/orders: post: summary: Place book order description: | This API method allows placing an order for one or more books operationId: createOrder tags: - Bookstore requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/Order' responses: '200': description: Successful Response content: application/json: schema: $ref: '#/components/schemas/OrderDetails' '400': $ref: 'https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/BadRequest' '401': $ref: 'https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/Unauthorized' '422': $ref: 'https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/ValidationError' '500': $ref: 'https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/ServerError' '503': $ref: 'https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/ServiceUnavailable'
This results in a much richer and explicit representation of the errors:
In other situations, perhaps you want to tailor the error response as only certain examples might be applicable (or maybe you want to craft your own example). This is still possible while leveraging the exposed schemas, and it’s what I need for the GET /books path as examples related to a request body are not applicable. Below, I set the content and encoding for the response while still leveraging the schema exposed by the reusable domain. I also explicitly reference the examples that are applicable for the path.
responses: '200': $ref: '#/components/responses/books' '400': description: | The request was malformed or could not be processed. Examples of `Bad Request` problem detail responses: - [Missing request parameter](https://problems-registry.smartbear.com/missing-request-parameter/) - [Invalid request parameter format](https://problems-registry.smartbear.com/invalid-request-parameter-format/) - [Invalid request parameter value](https://problems-registry.smartbear.com/invalid-request-parameter-value/) content: application/problem+json: schema: $ref: 'https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/schemas/ProblemDetails' examples: missingRequestParameterWithErrors: $ref: 'https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/examples/missing-request-parameter-with-errors' invalidRequestParameterFormatWithErrors: $ref: 'https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/examples/invalid-request-parameter-format-with-errors' invalidRequestParameterValueWithErrors: $ref: 'https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/examples/invalid-request-parameter-value-with-errors' '401': $ref: 'https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/Unauthorized' '500': $ref: 'https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/ServerError' '503': $ref: 'https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/ServiceUnavailable'
The improved Bookstore API can be viewed directly in SwaggerHub [7].
Examples in the Wild
It’s encouraging to see the adoption of the standard by many API providers, tooling vendors, and programming frameworks.
Here’s some SmartBear APIs that have already adopted the standard:
- SwaggerHub Portal APIs: https://smartbear.portal.swaggerhub.com/portal/default/getting-started
- PactFlow APIs: https://smartbear.portal.swaggerhub.com/pactflow/default/errors
Conclusion
With the resources and examples provided, you’re well-equipped to start implementing Problem Details in your APIs. Don’t hesitate to leverage the public domains and registries to accelerate your journey, reduce initial overheads, and ensure that your team doesn’t have to reinvent the wheel. More importantly, these tools help maintain consistency across your API landscape, improving the error handling experience for end-users and developers alike.
| Ref | Description | URL |
|---|---|---|
| [1] | RFC 9457 Standard Document | https://www.rfc-editor.org/info/rfc9457 |
| [2] | IANA Registry of Problem Types | https://www.iana.org/assignments/http-problem-types |
| [3] | SmartBear Problems Registry | https://problems-registry.smartbear.com/ |
| [4] | SwaggerHub Domain for Problem Details | https://app.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0 |
| [5] | JSON Schema Draft 2019-09 | https://json-schema.org/draft/2019-09/schema |
| [6] | GitHub Repository for SmartBear Problems Registry | https://github.com/SmartBear-DevRel/problems-registry |
| [7] | Bookstore API | https://app.swaggerhub.com/apis-docs/frank-kilcommins/Bookstore-API/1.0.0 |