What it means for an API to be RESTful

Hacker News Top Tools

Summary

This article explains the true meaning of RESTful APIs, emphasizing the importance of HATEOAS and hypermedia as the engine of application state, and argues that most modern APIs only reach Level 2 of the Richardson Maturity Model.

No content available
Original Article
View Cached Full Text

Cached at: 07/30/26, 01:49 PM

# What it means for an API to be RESTful | Andros Fenollosa Source: [https://en.andros.dev/blog/9761fd2e/what-it-means-for-an-api-to-be-restful/](https://en.andros.dev/blog/9761fd2e/what-it-means-for-an-api-to-be-restful/) I am sorry, but you do not know what a RESTful API is\. I know you are a seasoned developer who has integrated APIs of all kinds, and you may even have dozens in production across multiple languages\. But all of that only proves you have followed the industry's good practices and read immeasurable lines of documentation, which is good\. However, none of it certifies your knowledge of RESTful\. Don't panic\! You are not alone\. Most developers I know could not define it, implement it or point out its strengths either\. That is why in this article I am going to explain its virtues with examples that are easy to understand\. And you may well never look at an API the same way again once you finish reading\. You have been warned\! API RESTful is often used as a synonym for REST API \(Representational State Transfer\), and the nuance matters\. REST is not an HTTP interface: it is an architectural style, defined by Roy Fielding in his doctoral dissertation \(2000\), that imposes constraints: - Client\-server architecture: The client and the server must be separated\. - Stateless: Each client request to the server must contain all the information needed to understand and process the request\. In other words, the server will not keep information about the client's state between requests\. - Cacheable: Responses must be explicitly marked as cacheable or non\-cacheable\. - Layered system: The architecture can be composed of layers, where each layer has a specific function and they are isolated from each other\. - Uniform interface: Communication between the client and the server must be predictable, with a well\-defined pattern\. - Code on demand \(optional\): The server can send executable code to the client, such as JavaScript scripts, to extend the client's functionality\. In other words, REST is a set of architectural principles for serving resources, usually over HTTP\. It is so ingrained in web development, and so standardized, that it feels strange when we do not see it\. And here comes the uncomfortable part: strictly speaking, REST already includes everything you are about to read in this article\. Hypermedia is part of the uniform interface constraint, and "RESTful" is simply the adjective, "conforming to REST"\. What has happened is that popular usage has been degrading "REST" until it means "HTTP API that returns JSON", so much so that Fielding himself published a famous article in 2008,[REST APIs must be hypertext\-driven](https://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven), complaining that we call REST any RPC dressed up in HTTP\. The[Richardson Maturity Model](https://martinfowler.com/articles/richardsonMaturityModel.html)put numbers on that distance: level 0, a single RPC\-style endpoint; level 1, resources with their own URI; level 2, HTTP verbs and status codes used properly; level 3, hypermedia\. The vast majority of the "REST" APIs you consume every day stay at level 2\. In this article I will use RESTful to refer to that level 3, the one Fielding demands\. The goal is to turn an API into a predictable, standardized and self\-descriptive interface\. With just the base URL, the client will be able to explore every resource, without resorting to external documentation\. In addition, we will have the flexibility to change routes without affecting the client, or even play with several communication protocols\. But first there is a concept you must know: HATEOAS \(Hypermedia as the Engine of Application State\)\. This concept is fundamental to understanding how a RESTful API can be self\-descriptive and navigable\. ## HATEOAS \(Hypermedia as the Engine of Application State\) Hypermedia is one of the key characteristics of RESTful: it allows clients to dynamically discover resources through links provided in the responses\. They usually live under the`\_links`parent\. ``` { "id": 123, "name": "John Doe", "_links": { "self": { "href": "/users/123", "method": "GET" }, "update": { "href": "/users/123", "method": "PUT" }, "delete": { "href": "/users/123", "method": "DELETE" }, "friends": { "href": "/users/123/friends", "method": "GET" }, "posts": { "href": "/users/123/posts", "method": "GET" }, "search": { "href": "/search/?query={query}", "method": "GET", "templated": true } } } ``` Thanks to this, clients can navigate the API by parsing and following the links to reach the information they need, similar to how you would browse the internet\. Furthermore, since you jump between relative routes \(`href`\) using their names as identifiers \(the object key\), the backend could change the addresses without affecting the client\. It is very powerful because the client is not tied to a fixed route structure; it moves between nodes\. Notice the use of`templated: true`in the last link\. It indicates that the`href`contains a URI template \(following RFC 6570\) that must be filled in with specific values before use\. It is an elegant way to discover parameterized endpoints\. For example, the client could replace`\{query\}`with "restful api" to search for related content\. An honest note before we continue: the`\_links`convention comes from[HAL](https://stateless.co/hal_specification.html)\(Hypertext Application Language\), but HAL does not define the`method`field; its links only contemplate properties such as`href`,`templated`,`type`or`title`\. I add it because in practice it helps the API be self\-descriptive, and it is a common extension\. If you need actions with formalized methods and fields, look at[Siren](https://github.com/kevinswiber/siren); if you prefer pure HAL, omit`method`and trust the protocol's conventions \(rule 2\)\. Not every resource needs hypermedia, only the relevant ones with the right context\. For example, it would make no sense to include links to a shopping cart in a blog article, but it would be interesting for an article to include the link to the author, the comments or related articles\. Now that we understand the importance of HATEOAS, let's look at the rules a RESTful API must follow\. ## The 6 rules of a RESTful API I am not making these rules up: they are the six conditions Fielding lists in the article I mentioned earlier, adapted here with examples\. ### 1\. Don't depend on a single protocol Use resource identifiers \(URIs\) to define resources\. In other words, instead of using a URL \(`http://example\.com/api/users/123`\), use a URI that indicates its location and ignores the protocol \(`/users/123`\)\. This way you could use others such as WebSocket, MQTT, NNTP, RPC, etc\. Of course you can use HTTP, but you must not depend only on it\. For example, if your API is designed to work exclusively over HTTP, it would not be strictly RESTful\. ### 2\. Don't change the protocol Don't reinvent the wheel\. Don't get creative with protocols\. For example, if you use HTTP, follow the conventions: GET to fetch resources, POST to create, PUT to replace, PATCH for partial updates and DELETE to remove\. Use the proper HTTP response statuses: 200 OK, 201 Created, 204 No Content, 400 Bad Request, 404 Not Found, etc\. Using MQTT? Use the proper commands and statuses\. ### 3\. Focus on media types, not URIs Instead of documenting each URI externally, you must make your API describe the media types it handles\. It is the most misunderstood rule of the six, and for Fielding it is where almost all the descriptive effort should go\. The idea: instead of publishing a list of routes \(what we usually call "the documentation"\), you define and document your media types \(for example`application/vnd\.myshop\.product\+json`\), that is, what the fields of each representation mean and how its links are processed\. The client decides what to do based on the`Content\-Type`it receives, not on the URI it called\. URIs become interchangeable details\. For example, the resource for a user with little information could be: ``` { "id": 123, "name": "John Doe", "_links": { "self": "/users/123", "friends": "/users/123/friends", "lastInvoice": "/users/123/invoice/last" } } ``` While the resource for a user with more information could be: ``` { "id": 123, "name": "John Doe", "_links": { "self": { "href": "/users/123", "method": "GET", "type": "application/json" }, "friends": { "href": "/users/123/friends", "method": "GET", "type": "text/csv" }, "lastInvoice": { "href": "/users/123/invoice/last", "method": "GET", "type": "application/pdf" } } } ``` ### 4\. Don't assume URI structures You must not store or reuse URI structures in the client\. An API could change them without notice, and your client would obviously stop working\. Instead, parse the links and follow the relative identifiers \(`rel`\)\. A resource can have the following structure: ``` { "id": 987, "name": "Totoro plush", "price": 19.99, "_links": { "self": { "href": "/products/987", "method": "GET" }, "addToCart": { "href": "/cart/add/987", "method": "POST" }, "reviews": { "href": "/products/987/reviews", "method": "GET" } } } ``` And the next day: ``` { "id": 987, "name": "Totoro plush", "price": 19.99, "_links": { "self": { "href": "/shop/item/987", "method": "GET" }, "addToCart": { "href": "/shop/cart/987", "method": "POST" }, "reviews": { "href": "/shop/item/987/reviews", "method": "GET" } } } ``` ### 5\. Avoid resource "types" Don't expose the hierarchy, permissions or types of resources in the API\. The client neither cares nor should know\. For example, you should not have a`/users/admin/123`or`/users/guest/123`resource\. Instead, use the links to define the actions that can be performed on the resource\. ### 6\. Start with a bookmark \(root resource\) and let the client explore Clients must start with a root URL \(or bookmark\), and from there they will navigate your API\-verse\. ``` { "message": "Welcome to my RESTful API", "_links": { "users": { "href": "/users", "method": "GET" }, "products": { "href": "/products", "method": "GET" }, "orders": { "href": "/orders", "method": "GET" }, "search": { "href": "/search/?query={query}&page={page}", "method": "GET", "templated": true }, "user-by-id": { "href": "/users/{user_id}", "method": "GET", "templated": true }, "product-search": { "href": "/products/search/?name={name}&category={category}", "method": "GET", "templated": true } } } ``` Notice that several links use`templated: true`, the URI templates we saw in the HATEOAS section: the client discovers even the parameterized endpoints without leaving the response\. You now have a general idea of how to build a RESTful API\. Between the REST principles, the RESTful API rules and the HATEOAS concept, you will be able to build a solid API\. ## FAQ ### How do I indicate the version of the API or endpoint? The most common approach is to include the version in the URL, for example:`/api/v1/users`\. However, if you want to follow the RESTful rules, you should take the version out of the URI\. The most recommended option is to negotiate it with the`Accept`header using the structure`application/vnd\.example\.v1\+json`, where`example`is the name of your API and`v1`is the version\. For example, my API is called`dream`and the latest version is`2\.1`, so the header would be:`Accept: application/vnd\.dream\.v2\.1\+json`\. Another simpler option, although less recommended, is to use a custom header such as`Api\-Version`, where the value would be`2\.1`\. That said, avoid the classic`X\-`prefix \(`X\-API\-Version`\): it has been discouraged since 2012 by[RFC 6648](https://www.rfc-editor.org/rfc/rfc6648)\. ### How do I handle pagination? You will need to include metadata in the response to indicate the pagination with`meta`\. A pagination example could be: ``` { "data": [...], "meta": { "total": 150, "page": 1, "perPage": 10, "hasNext": true, "hasPrevious": false }, "_links": { "self": {"href": "/api/items?page=1", "method": "GET"}, "next": {"href": "/api/items?page=2", "method": "GET"}, "first": {"href": "/api/items?page=1", "method": "GET"}, "last": {"href": "/api/items?page=15", "method": "GET"} } } ``` Where: - `total`: Total number of available items\. Not on the current page, but in the whole collection\. - `page`: Current page number\. It can never be 0 or negative\. - `perPage`: Number of items per page\. - `hasNext`: Indicates whether there are more pages available\. - `hasPrevious`: Indicates whether there are previous pages available\. - `\_links`: Links to navigate the pagination, such as`next`,`previous`,`first`,`last`and`self`\. When a link does not apply \(there is no previous page on the first page\), it is omitted, not set to`null`: that is how HAL does it and it avoids ambiguity for the client\. You can also use templates to let the client specify the page number: ``` { "_links": { "page": { "href": "/api/items?page={page}&perPage={perPage}", "method": "GET", "templated": true } } } ``` ### How do I handle errors? Here there is a standard, although few people know it:[RFC 9457](https://www.rfc-editor.org/rfc/rfc9457)\(Problem Details for HTTP APIs, which updates RFC 7807 from 2016\)\. It defines the`application/problem\+json`media type with five fields:`type`\(a URI identifying the problem category\),`title`\(a short, stable summary\),`status`\(the HTTP status code\),`detail`\(an explanation of this specific occurrence\) and`instance`\(the URI of the affected resource\)\. And it allows you to add your own extension fields\. For example, a response with a`404 Not Found`status and the`Content\-Type: application/problem\+json`header would carry this body: ``` { "type": "https://example.com/errors/article-not-found", "title": "Article not found", "status": 404, "detail": "There is no article with id c9bb4e4a", "instance": "/api/blog/c9bb4e4a" } ``` If you are starting an API from scratch, my advice is to use Problem Details: it is the standard and your clients will thank you\. In my projects I use my own convention \(`type`,`errors`,`data`and`meta`\) that integrates with the response structure of my use cases; if you are curious, I explain it in[Implementing Clean Architecture in Python](https://en.andros.dev/blog/c8838a2b/implementing-clean-architecture-in-python/)\. ## Conclusions A true RESTful API, the one Fielding described, is not an evolution of REST: it is complete REST, the level 3 of the Richardson Maturity Model that almost nobody reaches\. While popular usage stops at resources, verbs and status codes, hypermedia adds what turns an API into a self\-descriptive and navigable interface\. For errors you already have a standard, RFC 9457: use it and your clients will always know what to expect\. In short, a well\-implemented RESTful API does not just transfer data; it takes the client by the hand through all of its resources, creating an experience that is intuitive and resilient to change\. Which is essential for a long\-term project\. ## Sources If you want to keep digging into the topic, here are some recommended sources: - [Roy Fielding's Dissertation](https://ics.uci.edu/~fielding/pubs/dissertation/top.htm) - [REST APIs must be hypertext\-driven](https://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven)by Roy Fielding\. - [Richardson Maturity Model](https://martinfowler.com/articles/richardsonMaturityModel.html)by Martin Fowler\. - [RFC 9457: Problem Details for HTTP APIs](https://www.rfc-editor.org/rfc/rfc9457) - [HAL \- Hypertext Application Language](https://stateless.co/hal_specification.html) - [REST Architectural Constraints](https://restfulapi.net/rest-architectural-constraints/)by Lokesh Gupta\. It would also be interesting to read more about HAL and JSON\-LD, since they are formats that make it easier to implement HATEOAS and the self\-description of RESTful APIs\.

Similar Articles

How do you version public web APIs?

Lobsters Hottest

A discussion on API versioning practices, questioning the coupling of URL paths (e.g., /v1/) with semantic versioning and exploring potential antipatterns.

Designing APIs for Agents

Hacker News Top

The article argues that designing APIs for AI agents requires different principles than for humans, emphasizing clarity, explicitness, and avoiding defaults, because agents can read entire docs and write extensive code.

AEPs: API Enhancement Proposals

Hacker News Top

The AEP Project provides API design specifications and tooling for protobuf and HTTP REST APIs, hosted on GitHub.

The API epidemic and where it's headed with AI social media

Reddit r/artificial

This article exposes a pattern where major platforms like X, Reddit, and Stack Overflow train AI models on user-generated content for free, then charge exorbitant API fees for access to that same data, and ban users who protest or delete their content.