On the surface, REST API design looks to be quite simple. You just need to follow some conventions for your URL paths and return an appropriate HTTP status code.
But the reality of the networked world and the needs of your API consumers often go beyond what standing up a REST API can offer you. If you want your system to be successful, there’s quite a bit more you should consider.
What Can Go Wrong Will Go Wrong
When designing an API, begin with what you need. With REST, this means you’ll create a hierarchy of paths to contain every kind of object with which your API consumer will need to interact.
You’re thinking about happy paths. Listing things. Detailing things. Adding things. Changing things. But you might not be thinking about things like:
- What happens when a resource is present but unauthorized?
- Is it important for a consumer to know why a resource can’t be found?
- What happens if a dependency is unavailable?
- What happens if the client isn’t using the right URL?
- What happens if there’s no API application installed on the server?
How you answer these questions will help take your API from something that frustrates API consumers/users to something that really makes your application work.
The Nuances of Missing Resources
REST’s answer to “that thing doesn’t exist” is “HTTP status 404, Not Found.” It’s a good choice. A 404 is definitely the right thing to send back if someone asks for
/widgets/42 and widget number 42 doesn’t exist.
(There is generally only one “doesn’t exist” case where you shouldn’t throw a 404. That is when the user asks for a list of things you know about, but you don’t have any to give them. In that case, you should return a 200 OK and an empty JSON list.)
Even when we pass on this case, returning a 404 for
/widgets/42 is still ambiguous. For example, it could mean that we don’t actually have a route for
/widgets. The API consumer might have the wrong URL.
It could also mean that the API consumer is asking in the right place and asking for the right thing, but the application hasn’t been installed correctly on the server.
Or, we could be dealing with a case in which the API consumer isn’t authorized to get any details about widget 42. We might want to deny its existence.
In these cases, you need to decide as a designer if the API consumer needs to know the difference. (The answer is almost always yes.)
If so, we should respond with clarity.
Responding With Clarity
Some of the cases above are out of our control. If our application isn’t installed, the chance that we can control what the server responds with is pretty slim—it’ll probably be 404 if the server isn’t crashing outright.
Similarly, if our application is installed and the client is giving us a path we don’t understand, we could control the response. But 404 is still the correct status code.
What we need is an error body. This makes it clear to the API consumer why they’re getting the 404.
"errorMessage": "Widget does not exist."
Even with a 404, you can and should return a meaningful response body. At a minimum, it should have two important pieces of information:
- A human-readable message
- A machine-readable error code
The human-readable message is just that. It’s written as complete sentences, and you should assume that some application may display it to a user.
The error code is something else entirely. Every time you know why something is wrong—e.g. you can’t find widget 42—you should explain why with an error code.
Your API specification should have a list of these error codes and explain why they can occur. They will become part of your API, so if you need to change how they work, you’ll need to version your API.
If the API consumer doesn’t find an error code, they can assume that your application doesn’t know why you’re getting a 404. They can treat it as an outage.
Finally, if you have a case where the request isn’t authorized for a specific resource, you should consider saying it’s “not found” for security resaons–much as you wouldn’t tell someone they got an existing username right but used the wrong password. If there’s no reason to acknowledge the existence of something, don’t.
There are many, many other cases when 404 Not Found isn’t the right thing to throw back for a failure case, and you can choose from lots of status options.
But it’s still important to be clear. As a general rule, if you’re in control of the response body, you should be returning a message and error code to the API consumer, even if you’re basically repeating what the status code says.
Whether you say exactly what happened or not, especially in 5xx cases, depends on whether you can give any useful information to a consumer. Consider that if it’s something you have to fix (or even something you just might want to correlate), it might be useful to have a request ID you can look up in your logs.
Being explicit beyond what REST asks you to do will solve a lot of problems. It’ll make developers of your API consumers happy and help them provide much more useful feedback to their users.
Don’t stop with just a little REST. Rather, dream about what an informative API could look like.