Sponsor: Using RabbitMQ or Azure Service Bus in your .NET systems? Well, you could just use their SDKs and roll your own serialization, routing, outbox, retries, and telemetry. I mean, seriously, how hard could it be?
I’ve been using an HTTP API as a consumer and how ti deals with it’s HTTP API Errors is terrible. Tt’s returning back a 200 OK. And in the body of the response, it has an error property that has a user facing message.
You might be thinking, if you just return a 400 status code, all problems are solved. Well, not really, because ultimately you want some structure of data and a good developer experience so the developers can handle those errors, because often times you need to surface that to your end user.
YouTube
Check out my YouTube channel, where I post all kinds of content on Software Architecture & Design, including this video showing everything in this post.
Error or Success?
It really comes down to being explicit about the success or failure of a request. I have consumed a lot of APIs, likely older systems, that do exactly this: on failure they return a status property that dictates success or failure and they always have a message property that is kind of user facing. The catch is they still return HTTP 200.
Here is what I often see. On failure you get a body like this with a status and message and maybe some other metadata, and on success you get a body with that same structure plus some data.
They both return 200, and those properties always exist.
On error:
On success:
I can always deserialize knowing those properties are present and then decide what to do next.
If you prefer to use HTTP status codes properly, I am all for that. But if you’re going to return 200 and be explicit in the response body, that is okay too. All I really care about is having an explicit indication of success and failure.
Response Body: Why structure matters
The main problem with letting the payload dictate whether it’s an error is tooling and deserialization. Depending on the libraries or languages you use, nullable properties, optional fields, and missing fields can get messy. It makes the developer experience worse.
What you want is a combination of two things:
- Human readable information so someone debugging or an end user can understand the problem.
- Machine readable information so clients can programmaticly react to specific error conditions at runtime.
When failures occur, you often want to do something specific based on the error. For example, you might automatically truncate a value and retry, or you might map a specific error code to a friendly message for the user. To do that reliably, the API needs to provide structured error details that your client can depend on.
Example: QuickBooks Online
A good real world example is QuickBooks Online and how they deal with API errors. Their API always uses a particular response structure that includes a fault. The response shows a fault type and, more importantly, a very specific error code you can look up in documentation, as well as a message and details. They return this in a 200 response for many error conditions. They do return 400 if the request body itself is malformed or has syntax issues.
Here is why this works well:
- The fault type gives you a high level classification, such as validation fault.
- The specific error code tells you exactly what failed, for example length exceeded for the first name.
- The details provide contextual information that helps both debugging and forming better user messages.
Because of the specific error codes, when deserializing you can decide to do things like truncate and retry, show tailored UI messages, or take other programmatic actions. That is good developer experience: both human readable and machine readable.
Problem Details (RFC 9457)
You might be yelling, “Can we just not use a standard? Why does everybody have to create their own error responses?” The good news is many frameworks already support a standard called Problem Details. It has been around for a long time and if you are using any modern web framework, it probably supports it. I just don’t see it enough used in public APIs that I consume.
Problem Details is defined in RFC 9457. It covers everything I have been talking about: being explicit, providing machine readable information for runtime handling, and giving human readable fields to help with debugging and user messaging.
The standard response has fields like:
- type: a URI that identifies the error type. This can also point to documentation about that error type.
- title: a short, human readable summary of the problem.
- status: the HTTP status code that applies to this problem.
- detail: human readable details specific to this occurrence of the problem.
Your type could be something like invalid parameters. The type URI can link to documentation that explains the expected response shape, for example a collection of key value pairs that tell which fields are invalid. That makes it machine readable because your client can expect a consistent structure when it sees that type, and human readable because the title and detail explain what happened.
API Errors
As an API consumer and producer, focus on being explicit about success and failure. When failures happen, separate what is machine readable from what is human readable. Use the machine readable fields to drive runtime behavior and use the human readable fields to help developers and end users understand the problem.
Problem Details is one solution that gives you both. It does have some issues if you’ve used it, and many teams implement their own structure. I’m not entirely against homegrown formats as long as documentation is thorough and clients can reliably parse and react to errors.
API Errors do not need to be hard. Stop hiding errors inside a 200 response without structure. Provide explicit success and failure indicators, give machine readable codes for runtime handling, and include human readable messages for debugging and user messaging.
Join CodeOpinon!
Developer-level members of my Patreon or YouTube channel get access to a private Discord server to chat with other developers about Software Architecture and Design and access to source code for any working demo application I post on my blog or YouTube. Check out my Patreon or YouTube Membership for more info.