HTTP Status Error Codes for REST APIs

I’ve been both using and building REST APIs in my software development work. One of the more confusing and controversial topics regarding REST APIs are the meaning of HTTP status error codes. By error code, I’m referring to those code that are of the 4xx or 5xx variety.

After doing a bit of research and reading the standards, I’ve distilled things down to the 5 basic error codes that I feel should be a part of every REST API.

Note: I’m not going to cover HTTP status success codes here, which are those codes in the 2xx range. For a complete overview of all HTTP status codes, both success, error, and redirect codes, I suggest reading over the MDN documents which can be found here.

400 – Bad Request

If I, as the developer, send a malformed request to the server, either through query parameters in the URL or in the POST/PUT body, I expect that the server will respond with a 400 error. Likewise, when I build an API, I use the 400 error to indicate that the server received the response, interpreted it, but didn’t know what to do with it.

Essentially, a 400 error response means the server is working, the API is operational, but the error is in the client code. The client needs to review how they are calling the API or what they are sending to the API, review the documentation, and update their code.

500 – Internal Server Error

When I, as the developer, receive a 500 response, it means that my client code is fine, but the server is either not working or the server code threw an unexpected error. Similarly, in my APIs, I respond with a 500 error whenever the server code throws an error related to code syntax or server operational issues.

What a 500 error signals to the consumer of an API is that there is nothing they need to change in their code. The problem is on the server side and needs to be resolved in the server operation or server code. As a developer of APIs, logging of 500 errors and/or raising alerts can be helpful to understand if there are underlying issues in the code that need to be addressed. Otherwise, absent of logging, the API developer will be reliant on users reporting 500 errors when they occur.

404 – Not Found

The 404 error is a generic error that indicates to the client that the endpoint (or resource) they are trying to access does not exist.

Please, please, please, do not use this error to indicate that a call to a search API yielded no results or that a particular product or service id was not found. In these cases, the server did the work and should return a successful response (code 200) and a result set of 0.

In other words, there is no error in the connection between the client and the server, or the code on the client or the server. Everything worked properly. It is up to the client to interpret a successful reply with a result set of 0 and supply the proper message to the user and what the user needs to do if they are not happy with the result.

401 – Unauthorized

The 401 is a simple return message that the client tried to access an API and either a) did not supply the proper authentication credentials, b) the provided authentication credentials were invalid, or c) in the case of token validation, the token has expired.

The MDN documents clarify the 401 – Unauthorized response by pointing out that “unauthorized” semantically means “unauthenticated. The server is unable to authenticate and identify the client in order to provide a response to the resource the client has requested.

403 – Forbidden

At first glance, the 403 response appears identical to a 401 response, but there is a very important semantic difference. A 403 – Forbidden response indicates to the client that the server was able to authenticate and identify who you are, but the identified client is not allowed to access the requested resource. Think of it as a regular user trying to access an API endpoint that requires special privileges such as administrator or super-user.

The semantic difference between a 401 and 403 response is important because the client might need to implement different actions when a user is unauthorized or unauthenticated versus when a user is forbidden to access a resource.

Some people may say that it’s better to respond with a 404 – Not Found instead of a 403 – Forbidden in order to obfuscate the error, but I disagree since it makes it more difficult for the client to determine what to do and provides a misleading message back to the user.

Optional: 405 – Method Not Allowed

If a client tries to use a method that is not allowed on an endpoint, it may be worth replying with a 405 – Method Not Allowed. For example, if an endpoint only supports GET methods, and the user tries a POST or PUT method, the server could respond with a 405 error.

This is another one of those gray areas. Technically, a 405 error should be used when an unsupported method is called on an endpoint. However, the 400 – Bad Request is also a reasonable response, in my opinion. A 405 request could be considered a subset of the 400 – Bad Request error code.

These are my 5 (or 6) go-to error code that I like to use in my API implementations and that I expect from the API implementations I use. Of course, if you read through the MDN documents, you’ll see that you can get a lot more nuanced in your error handling. HTTP status error codes can provide more detailed messaging back to the client such as server availability, timeout issues, rate limiting, and, my personal favorite, error status code 418 – I’m a teapot. However, in many cases, these are unnecessary since more detailed error information can be provided back to the client in the API response body, but that’s a deep subject that would best be covered in a different post. I’ll save that one for some other time.