Get developer hugs with rich error handling in your API

Server-side error handling and communication are major, often under-appreciated components in designing a REST API. Most API developers spend their time on everything else involved in getting a REST API done right — from debating which resources need to be exposed through the API, to getting the HTTP verbage right, to using content negotiation. Unfortunately, error responses tend to be an after-thought. They're treated as second-class citizens whose design never gets reviewed nor discussed as actively as any another component of the API. The fact is that although errors only account for a fraction of all messages sent by the API (at least typically), they are the most scrutinized messages by app developers. (Even more than success response messages!)

App developers are likely to make errors when making an API request, so it's crucial to provide them with information to troubleshoot and resolve the issue. For example, the developer might forget to set a required field or the value may not match an expected format. Error responses are extremely important to get right — they determine whether developers keep building on your API or get so frustrated with cryptic error responses that they abandon the API. Unfortunately, there isn't a well defined format indicating how an error response should be structured.

When it comes to transmitting errors caused due to client errors, the Box API is as RESTful as the rest of the API. All client-side errors are returned using a 4xx error code. In most cases, the API returns a 400 Bad Request. When a more specific error condition happens, the Box API also returns a finer-grained 4xx status code that indicates the error condition. Examples of such codes include 409 (when you’re trying to create a folder with a name that is already used in the current parent folder) or a 413 (when the request body is too large). HTTP-aware software can then interpret the error condition and handle it the right way.

The 422 Unprocessable Entity status extension to HTTP/1.1

The HTTP extension for WebDAV specifies the ‘422 Unprocessable Entity’ status. To quote the RFC,

The 422 (Unprocessable Entity) status code means the server understands the content type of the request entity (hence a 415 (Unsupported Media Type) status code is inappropriate), and the syntax of the request entity is correct (thus a 400 (Bad Request) status code is inappropriate) but was unable to process the contained instructions.

This status code is a much better fit than a HTTP 400 to represent client errors. The GitHub API uses this status code.

[shell]HTTP/1.1 422 Unprocessable Entity
Content-Length: 149

{
"message": "Validation Failed",
"errors": [
{
"resource": "Issue",
"field": "title",
"code": "missing_field"
}
]
}[/shell]

We chose not to use this extension in the Box API because, unfortunately, this error code is not widely supported by all HTTP clients and client behavior in handling unknown 4xx error codes is undefined (e.g., some may fall back to the default HTTP 400 handling).

Best practices for error responses

Here are some best practices to follow when returning an error response (excluding HEAD requests):

  1. Return an HTTP status code that closely matches the error condition

    The Amazon S3 API is one particular example showcasing how HTTP status codes could be misused. It returns a 200 error with a body that contains an application-specific error code. To quote their documentation:

    If the error occurs during the copy operation, the error response is embedded in the 200 OK response. This means that a 200 OK response can contain either a success or an error.

    Doing this might cache a copy of the response and return it the next time the app does a GET for the same resource.

  2. Return human-readable error messages

    APIs are meant to be consumed by developers (and apps). While it's essential to get HTTP status codes right, they are fairly coarse-grained and do not provide enough information to the developer. Consider the case where a developer tries to upload a file to Box and gets a 400-status code: Was the request body malformed? Was the parent folder ID missing? Having an error message immediately makes it clear what went wrong. Ensure that internal-only messages (such as database version or stack traces) are excluded. To get bonus points, localize the error messages.

  3. Return machine-readable error codes

    These are constants to indicate the error that happened. Usually they are exposed as strings (for example, ‘folder_name_already_used’ ) or integers. An app can consume the error codes (e.g., in a switch statement) to display dialogs that are relevant to the error condition. For instance, if the ‘folder_name_already_used’ error code is detected, the app can display a textbox with a “Rename” button that lets the client pick a different name for the folder.

  4. Return the Invalid parameter name

    Whenever possible, return the specific input parameter that the developer specified (or did not specify) that caused the API to choke on.

  5. Set Location

    This information is very useful for the developer to quickly identify the place that holds the erroneous piece of request. For REST APIs, location can be a URL, header or entity-body.

  6. Provide a Help URL

    If additional information to debug the error is available, include the link in the error response. The link destination, usually to a knowledge base, will provide more information about the error, often with suggested tips to resolve it. You might consider using the Link header with an extended relation type such as ‘api-help’ to communicate the help URL (since the RFC does not define a standard relation type for errors). Set any other response headers, if applicable.

The response body should be formatted in the representation format negotiated by the client. Returning HTML error messages in response to a request for a JSON payload isn’t cool (assuming the client request makes it to your API server).

You should also make sure that your API documentation clearly describes all the elements that are returned as part of your error response and how they should be used. By following the above best practices, you can make it easier for developers to consume your API and fix any errors that they may have inadvertently caused when making the request.

Clients need to deal with errors by watching for network errors first (for instance, when the request did not even make it to the API server because the network was down at the client side). These are communicated by the HTTP status. Network libraries specific to the programming language handle these errors and surface them to your app. If this check succeeds, check that the message body conforms to the specified format and read application specific errors from the entity body. This strategy to handle errors makes clients robust and reliable.

Upcoming improvements to Box API V2's error responses

For the past few months, I have been working on improving the error responses that the Box API V2 returns for client-side errors. The goal of the project is to provide a descriptive and actionable error response, which we think will help developers identify and fix errors more effectively.

For instance, let’s say you were trying to create a folder in Box through API V2. Assume you forget to set the parent folder id (which is required) when creating the folder. The error response you would currently see is:

[shell]HTTP/1.1 404 Not Found

{
"type":"error",
"status":404,
"code":"not_found",
"help_url":"http://developers.box.com/docs/#errors",
"message":"Not Found",
"request_id":"455888459514fcf1d97e74"
}[/shell]

Once we have rolled out error response enhancements, the new error response for the same error condition would be:

[shell]HTTP/1.1 400 Bad Request

{
"type":"error",
"status":400,
"code":"bad_request",
"context_info": {
"errors": [
{
"reason": "missing_parent_folder",
"message": "'parent' is required",
"name": "parent",
"location": "entity-body"
}
]
},
"help_url": "http://developers.box.com/docs/#errors",
"message": "Bad Request",
"request_id":  "8228434695109958b0ad7d"
}[/shell]

Note the new 'context_info' array. It explains the precise error that occurred, so the developer can quickly fix the problem. The 'reason' attribute in the error response is a constant and can be consumed by programmatically by apps, while 'message' provides developers a human-readable explanation that can change over time. The context_info array can contain multiple error objects — if the request has multiple errors, the response will contain a separate object for every error. For example, assume you try to set expiration and permissions (both of which are not permitted) on a shared link that is accessible to collaborators only, the error response would be:

[shell]HTTP/1.1 400 Bad Request

{
"type":"error",
"status":400,
"code":"bad_request",
"context_info": {
"errors": [
{
"reason": "expiration_not_allowed",
"name": "unshared_at",
"message": "Expiration cannot be set on a shared link with 'collaborators' access",
"location": "entity-body"
},
{
"reason": "permissions_not_allowed",
"name": "permissions",
"message": "Permissions cannot be set on a shared link with 'collaborators' access",
"location": "entity-body"
}
]
},
"help_url": "http://developers.box.com/docs/#errors",
"message": "Bad Request",
"request_id":  "13628284550db80756c518"
}[/shell]

Box API error messages will be improved over the next few months, after the initial release. As such, you shouldn't program against them; they really are only meant for human consumption. If you have any questions, please feel free to contact us.

I hope that these semantically rich error responses add a higher degree of robustness and reliability to your apps, keeping you moving forward with our API.

Tags: api