In a world where RESTful APIs are used every day to feed our lives with more data than ever possible, it is necessary to understand how they work, or at least how they SHOULD work.
“Most APIs have unclear, incomplete, or simply missing documentation!”
As a developer, I was shocked to discover how few APIs actually follow the basic rules and guidelines in place. So, I thought I would show a few general DOs and DON’Ts based on live APIs that I’ve encountered or used in the past (without mentioning where they were found, no need for finger pointing of course!).
I won’t go into extensive details about everything. In fact, I’ll try to keep it short and straight to the point, and if you are interested after reading my thoughts and sparked curiosity, I would invite you to dig a bit further.
1 — Let’s just use POST for everything!
“You don’t have to create a new endpoint for each action related to a resource.”
👎 DON’T
POST /api/v2/CreateOrder
POST /api/v2/GetOrder?id=12345
POST /api/v2/GetOrders
POST /api/v2/DeleteOrder?id=12345
👍 DO
POST /api/v2/orders
GET /api/v2/orders
GET /api/v2/orders/12345
DELETE /api/v2/orders/12345
I have come across this example a couple of times and in rather large APIs related to online retail or banking. This case is wrong for many reasons.
Firstly, you don’t have to create a new endpoint for each action related to a resource. Instead, you should have a single endpoint (in this case /orders), and use the METHOD sent with your request to determine which action should be performed by the targeted endpoint.
Secondly, the target resource’s ID should not be passed in the query string but should be a sub-path or part of the URL instead (/api/v2/orders/12345). This is the common structure of a RESTful API and we can also imagine that this resource could potentially contain extra ramifications (/api/v2/orders/12345/products).
2 — While we’re at it…
👎 DON’T
Update a post ->
POST /api/v2/posts/12345
👍 DO
Update a post ->
PUT /api/v2/posts/12345
PATCH /api/v2/posts/12345
You will find this example in quite a few APIs out there.
If you read the documentation on HTTP Methods and how they are supposed to be used, you will find that POST is used to create a resource, but to update that same resource you are supposed to use PUT or PATCH.
3 — Let’s use PUT… What do you mean “404”?!
The PUT request should not return “404"…
👎 DON’T
Post 12345 does not exist.Request ->
PUT /api/v2/posts/12345Response Status ->
404 Not Found
👍 DO
Post 12345 does not exist.Request ->
PUT /api/v2/posts/12345Response Status ->
201 Created
The PUT request should return “201"…
👎 DON’T
Post 12345 does not exist.Request ->
PUT /api/v2/posts/12345Response Status ->
200 OK
👍 DO
Post 12345 does not exist.Request ->
PUT /api/v2/posts/12345Response Status ->
201 Created
The use of PUT is often misunderstood.
In the first case, a request using PUT is supposed to result in the creation of a resource (201 Created) if it does not exist or 200 OK if it already exists, therefore it should never result in a 404 Not Found.
Furthermore, the second case created the resource but sent back a status 200, which would have been “OK” (see the pun here?) if the resource was updated, though in our case the request resulted in the creation of the resource and should return status 201 Created.
This last part is extremely important since it is the only way to make the difference between the creation or the update of a resource using PUT.
4 — Everything is “OK”
POST requests should not return 200…
👎 DON’T
Request ->
POST /api/v2/postsResponse Status ->
200 OK
👍 DO
Request ->
POST /api/v2/postsResponse Status ->
201 Created
Responses with an empty body should return 204…
👎 DON’T
Request ->
DELETE /api/v2/posts/12345Response Status ->
200 OKResponse Body ->
NULL
👍 DO
Request ->
DELETE /api/v2/posts/12345Response Status ->
204 No ContentResponse Body ->
NULL
In the first scenario, the correct response to a successful POST request is always 201 Created. 200 OK is used for updates or deletions (including a body).
In the second scenario, if the request was successful and the response’s body is empty (or null) you have to return 204 No content. This is not only to be picky, but it is used to prevent front-end errors. In most cases, if the status returned by a request is 204, the parser will not be called even though the request expected JSON content.
In my opinion, you should never return an empty body. It is considered “Bad practice” in some specification documents. If a resource was deleted, you should send back the resource, and possibly append the response with the exact time of deletion and/or a “deleted” flag.
5 — Server: “400 Bad Request. Body: Empty…” — Client: “Thanks…”
“Yeah… that happens.”
👎 DON’T
Request ->
POST /api/v2/ordersResponse Status ->
400 Bad RequestResponse Body ->
NULL
👍 DO
Request ->
POST /api/v2/ordersResponse Status ->
400 Bad RequestResponse Body ->
Content-Type JSON
Body
{
“code”: “ERR0001”,
“message”: “Your cart is empty.”
}
It is common sense to return an error message when sending back a status 400. It will help the end-user or the API consumer to understand why the request failed since this status is not as self-explanatory as “404 Not Found”.
Two parts are critical in a good error response: a unique reference (or code) for the error and a description (or message) in plain text. The description should give the user enough information to understand where the error is coming from and what needs to be provided or changed.
6 — “400 Bad Request. Body: Some random error” — Client: “But… this is a GET request”
👎 DON’T
Request ->
GET /api/v2/storesResponse Status ->
400 Bad Request
👍 DO
Request ->
POST /api/v2/storesResponse Status ->
503 Service Unavailable
So, I was using a rather large eCommerce API, and it so happens that they were using a third-party geolocation service. As you can imagine, the third-party service went down, and I was left with an error 400 on a GET request. My advice is, if you’re using a third-party service and it goes down, to return a more useful and accurate status such as 503 Service Unavailable.
Note: Do not return a 4XX error status if the error comes from the server — it is highly confusing and will drive your users completely insane!
7 — 404 (Not Found) vs 410 (Gone)
👎 DON’T
Request ->
DELETE /api/v2/posts/12345Response Status ->
200 OKRequest ->
GET /api/v2/posts/12345Response Status ->
404 Not Found
👍 DO
Request ->
DELETE /api/v2/posts/12345Response Status ->
200 OKRequest ->
GET /api/v2/posts/12345Response Status ->
410 Gone
So, this example depends on whether or not you keep track of what has been deleted. But if you do, and a GET request is made on a deleted resource, you should return status 410 Gone. It is useful for your users to know if the resource they’re trying to access once existed or not.
Just imagine for a second:
I am given a link that returns error 404, I immediately think that it is a mistake made by the person who gave me that link but in fact the resource existed and was deleted. If I were given proper feedback in this scenario, the situation would have been clearer and less confusing for both me, and the person who supplied the link.
8 — Get your priorities right
“You should always authenticate the user.”
👎 DON’T
Request ->
PUT /api/v2/posts/12345Response Status ->
400 Bad RequestRequest ->
PUT /api/v2/posts/12345Response Status ->
401 Unauthorized
👍 DO
Request ->
PUT /api/v2/posts/12345Response Status ->
401 Unauthorized
You should always authenticate the user. Typically, your users should go through your authentication gate first, to check the Authorization header for their credentials and validate them or return a status 401 Unauthorized. It is then that the request is validated, or not and, in that case, returns 400 Bad request.
Note: 403 (Forbidden) can be returned at the very end. It means that the User was successfully authenticated, and the request validated, but this particular user does not have the permission to perform this request. To summarize quickly: 401 > 400 > 403.
9 — “We want our API to be public, completely open, and free to use!” — The Care Bears
“You want to open your service to the world for free, but even an open API shouldn’t stay unprotected, or vulnerable to attacks or abuse.”
👎 DON’T
Request 1 ->
GET /api/v2/postsResponse Status ->
200 OK…Request 999,999 ->
GET /api/v2/postsResponse Status ->
504 Gateway Timeout
👍 DO
Request 1 ->
GET /api/v2/postsResponse Status ->
200 OK…Request 51 ->
GET /api/v2/postsResponse Status ->
429 Too many requests
I appreciate that you want to open your service to the world for free, but even an open API shouldn’t stay unprotected, or vulnerable to attacks or abuse. You must put some restrictions in place to avoid and protect against DDoS (Distributed Denial of Service) attacks. One possibility is to limit the number of requests made based on the user’s IP address, but I would instead recommend using some kind of ID-based Authorization system. With this system, the user gets a client ID once registered and must use this client ID in order to consume the API. It allows you to keep record of your users’ requests and put some restrictions in place.
Note: Remember to use status 429 Too many requests in the eventuality that one of your users goes over the authorized limit.
10 — “500 Internal Server Error” more like “500 Whoops, code broke”
“Remember: There is no excuse for a status 500 Internal Server Error, simply because it means there’s an unhandled error in your code.”
👎 DON’T
Request ->
PUT /api/v2/posts/12345Response Status ->
500 Internal Server Error
👍 DO
GOOD CODE
If you have to return a 500, it should be because you caught the error and it is an exception that you did not think about but at least it is handled, and you are aware of its existence.
There is always a lot more we could talk about here, but I think that we have covered some of the most common issues I’ve regularly encountered. If you want to highlight something, ask questions, or give your own opinion, feel free to leave a comment and I will answer with delightful pleasure!
You will most likely run into the same issues I have highlighted above, but the most common is the lack of documentation.
Most APIs have unclear, incomplete, or simply missing documentation! So, take a deep breath and get ready to explore the dark world of blind debugging!
A little treat for developers, if you have any doubts here is a very good reference: https://stripe.com/docs/api.