Duplicating HTTP status in the response body is an anti-pattern

Have only one source of truth in our API responses

In this blog post, I discuss the use of HTTP response status codes and the potential issues with duplicating them in the response body, as well as how RFC7807 provides a solution for conveying application-specific errors.

Sometimes, I see APIs where the error response looks like this (HTTP header and body):

HTTP/1.1 400 Bad Request
Content-Type: application/json

{
    "status": 400,
    "message": "Foo is blank"
}

The HTTP response status code is duplicated in the body of the response. There are different reasons why people think this is a good idea:

  • Duplicating the status code in the response body can make it easier to process messages on the client side. Instead of having to worry about the HTTP status code, developers can simply process the body of the response.
  • There is the concern that some intermediate proxies may change the HTTP code, potentially causing issues with the response. By including the status code in the response body, there is a greater chance that the response will remain intact.
  • Having the error code as part of the response body can make it easier to serialize and deserialize when it’s part of the same structure.
  • Overall, duplicating the status code in the response body can provide greater flexibility for the client if the API contract specifies that this status is always set to the same value as the HTTP response status.

I was motivated to write this blog post after reviewing the recently approved IETF standard, RFC7807, “Problem Details for HTTP APIs.” Unfortunately, this standard includes “status” as an optional field in the body of the response. I believe that this is an anti-pattern and a problematic approach. There are several reasons:

  • Firstly, it adds unnecessary complexity to the API client. Even when the API contract specifies that the code should be the same as in the HTTP response, the client still needs to process the HTTP code, as intermediate proxies can change it, which can lead to discrepancies between the two. This can be a source of security vulnerabilities since it allows a field in one protocol layer to be overridden in a different protocol layer, and the precedence is unclear. This is even mentioned as a security consideration in RFC7807.
  • Secondly, the API client must always process and respect the HTTP status code, and including this status in the response can give the impression that this is not the case. This can lead to hard-to-troubleshoot bugs that could have been avoided.
  • Thirdly, it’s generally a good API design practice to have just one source of truth for everything. This leads to better clarity and less doubt, resulting in a more streamlined and efficient API design.

When having an error code field in your API response a good idea?

HTTP statuses alone cannot convey all nuances in API responses; RFC7807 (mentioned above) proposes a standard solution. Define a “type” field to specify an application error and return HTTP status as an overall response status. Pairing HTTP status like “403 forbidden” with specific error types like https://example.com/errorcode/out-of-credit and https://example.com/errorcode/token-expired clarifies why the request was forbidden. For example:

HTTP/1.1 403 Forbidden
Content-Type: application/problem+json
Content-Language: en

{
  "type": "https://example.com/probs/out-of-credit",
  "title": "You do not have enough credit.",
  "detail": "Your current balance is $30, but that costs $50.",
}

In conclusion, implementing a clear and consistent error handling strategy using HTTP status codes and error types can significantly improve the overall user experience of an API.