116 comments

[ 4.0 ms ] story [ 75.8 ms ] thread
It felt easier going through the post after reading these bits near the end:

> The widespread adoption of a simpler, RPC-like style over HTTP can probably attributed to practical trade-offs in tooling and developer experience

> Therefore, simply be pragmatic. I personally like to avoid the term “RESTful” for the reasons given in the article and instead say “HTTP” based APIs.

You know what type of API I like best?

    /draw_point?x=7&y=20&r=255&g=0&b=0
    /get_point?x=7&y=20
    /delete_point?x=7&y=20
Because that is the easiest to implement, the easiest to write, the easiest to manually test and tinker with (by writing it directly into the url bar), the easiest to automate (curl .../draw_point?x=7&y=20). It also makes it possible to put it into a link and into a bookmark.

This is also how HN does it:

    /vote?id=44507373&how=up&auth=...
Who cares, honestly? I never understood this debate; nobody has ever produced a perfect RESTful API anyway
UI designers want control over the look of the page in detail. E.g. some actions that can be taken on a resource are a large button and some are hidden in a menu or not rendered in the UI at all.

A client application that doesn't have any knowledge about what actions are going to be possible with a resource, instead rendering them dynamically based on the API responses, is going to make them all look the same.

So RESTful APIs as described in the article aren't useful for the most common use case of Web APIs, implementing frontend UIs.

RESTful APIs are not RESTful because REST is meh. Our APSi includes HATEAOS links and I have never, not once, witnessed their actual use (but they do double the size of response payloads).

It’s interesting that Stripe still even uses form-post on requests.

Academically it might be correct, but shipping real features will in most cases be more important than hitting some text book definition of correctness.
Wasn't the entire point of calling an API RESTful, that it's explicitly not REST, but only kind of REST-like.

Also, who determined these rules are the definition of RESTful?

This post follows the general, highly academic/dogmatic, tone that I’ve seen when certain folks talk about REST. Most of the article talks about what _not_ to do, and has very little details on how to actually do it.

The idea of having client/server decoupled via a REST api that is itself discoverable, and that allows independent deployment, seems like a great advantage.

However, the article lacks even the simplest example of an api done the “wrong” vs the “right” way. Say I have a TODO api, how do I make it so that it uses HATEOAS (also who’s coming up with these acronyms…smh)?

Overall the article comes across more as academic pontification on “what not to do” instead of actionable advice.

I struggle to believe that any API in history has been improved by the developer more faithfully following REST’s strictures. The closest we’ve come to actually decoupled, self describing APIs is MCP, and that required inventing actual AIs to understand them.
> By using HATEOAS and referencing schema definitions (such as XSD or JSON Schema) from within your resource representations, you can enable clients to understand the structure of the data and navigate the API dynamically.

I actually think this is where the problem lies in the real world. One of the most useful features of a JSON schema is the "additionalProperties" keyword. If applied to the "_links" subschema we're back to the original problem of "out of band" information defining the API.

I just don't see what the big deal is if we have more robust ways of serving the docs somewhere else outside of the JSON response. Would it be equivalent if the only URL in "_links" that I ever populate is a link to the JSONified Swagger docs for the "self" path for the client to consume? What's the point in even having "_links" then? How insanely bloated would that client have to be to consume something that complicated? The templates in Swagger are way more information dense and dynamic than just telling you what path and method to use. There's often a lot more for the client to handle than just CRUD links and there exists no JSON schema that could be consistent across all parts of the API.

Nooooo not this discourse again.
Drake meme for me:

REST = Hell No

GQL = Hell No.

RPC with status codes = Grin and point.

I like to get stuff done.

Imagine you are forced to organize your code filed like REST. Folder is a noun. Functions are verbs. One per folder. Etc. Would drive you nuts.

Why do this for API unless the API really really fits that style (rare).

GQL is expensive to parse and hides information from proxies (200 for everything)

Where this kind of API design is useful is when there is a user with an agent (e.g. a browser or similar) who can navigate the API and interact with the different responses based on their media types and what the links are called.

Most web APIs are not designed with this use-case in mind. They're designed to facilitate web apps that are much more specific in what they're trying to present to the user. This is both deliberate and valuable; app creators need to be able to control the presentation to achieve their apps' goals.

REST API design is for use-cases where the users should have control over how they interact with the resources provided by the API. Some examples that should be using REST API design:

  - Government portals for publicly accessible information, like legal codes, weather reports, or property records

  - Government portals for filing forms and other interactions

  - Open data initiatives like Wikipedia and OpenStreetmap
Considering these examples, it makes sense that policing of what "REST" means comes from the more academically-minded, while the detractors of the definition are typically app developers trying to create a very specific user experience. The solution is easy: just don't call it REST unless it actually is.
> Where this kind of API design is useful is when there is a user with an agent (e.g. a browser or similar) who can navigate the API and interact with the different responses based on their media types and what the links are called.

It's also useful when you're programming a client that is not a web page!

You GET a thing, you dereference fields/paths in the returned representation, you construct a new URI, you perform an operation on it, and so on.

Consider a directory / database application. You can define a RESTful, HATEOAS API for it, write a single-page web application for it -or a non-SPA if you prefer-, and also write libraries and command-line interfaces to the same thing, all using roughly similar code that does what I described above. That's pretty neat. In the case of a non-SPA you can use pure HTML and not think that you're "dereferencing fields of the returned representation", but the user and the user-agent are still doing just that.

I spent years fussing about getting all of my APIs to fit the definition of REST and to do HATEAOS properly. I spent way too much time trying to conform everything as an action on a resource. Now, don't get me wrong. It is quite helpful to try to model things at stateless resources with a limited set of actions on them and to think about idempotency for specific actions in ways I don't think we did it properly in the SOAP days(at least I didn't). And in many cases it led to less brittle interfaces which were easier to reason about.

I still like REST and try to use it as much as I can when developing interfaces but I am not beholden to it. There are many cases which are not resources or are not stateless and sure you can find some obtuse way to make them be resources but that at times either leads to bad abstractions that don't convey the vocabulary of the underlying system and thus over time creates this rift in context between the interface and the underlying logic or we expose underlying implementation details as they could be easier to model as resources.

I find it pretty shocking that this was written in 2025 without a mention of the fact that the only clients that are evolvable enough to interface with a REST API can be categorized to these three types:

1. Browsers and "API Browsers" (think something like Swagger)

2. Human and Artificial Intelligence (basically LLMs)

3. Clients downloaded from the server

You'd think that they'd point out these massive caveats. After all, the evolvable client that can handle any API, which is the thing that Roy Fielding has been dreaming about, has finally been invented.

REST and HATEOAS were intentionally developed to against the common use case of a static non-evolving client such as an android app that isn't a browser.

Instead you get this snarky blog post telling people that they are doing REST wrong, rather than pointing out that REST is something almost nobody needs (self discoverable APIs intended for evolvable clients).

If you wanted to build e.g. the matrix chat protocol on top of REST, then Roy Fielding would tell you to get lost.

If what I'm saying doesn't make sense to you, then your understanding of REST is insufficient, but let me tell you that understanding REST is a meaningless endeavor, because all you'll gain from that understanding is that you don't need it.

In REST clients are not allowed to have any out of band information about the structure or schema of the API.

You are not allowed to send GET, POST, PUT, DELETE requests to client constructed URLs.

Now that might sound reasonable. After all HATEOAS gives you all the URLs so you don't need to construct them.

Except here is the kicker. This isn't some URL specific thing. It also applies to the attributes and links in the response. You're not allowed to assume that the name "John Doe" is stored under the attribute "name" or that the activate link is stored in "activate". Your client needs to handle any theoretical API that could come from the server. "name" could be "fullName" or "firstNameAndLastName" or "firstAndLastName" or "displayName".

Now you might argue, hey but I'm allowed to parse JSON into a hierarchical object layout [0] and JPEGs into a two dimensional pixel array to be displayed onto a screen, surely it's just a matter of setting a content type or media type? Then I'll be allowed to write code specific to my resource! Except, REST doesn't define or propose any mechanism for application specific media types. You must register your media type globally for all humanity at IANA or go bust.

This might come across as a rant, but it is meant to be informative so I'll tell you what REST and HATEOAS are good for: Building micro browsers relying on human intelligence to act as the magical evolvable client. The way you're supposed to use REST and HATEOAS is by using e.g. the HAL-FORMS media type to give a logical representation of your form. Your evolvable client then translates the HAL-FORM into a html form or an android form or a form inside your MMO which happens to have a registration form built into the game itself, rather than say the launcher.

Needless to say, this is completely useless for machine to machine communication, which is where the phrase "REST API" is most commonly (ab)used.

Now for one final comment on this article in particular:

>Why aren’t most APIs truly RESTful?

>The widespread adoption of a simpler, RPC-like style over HTTP can probably attributed to practical trade-offs in tooling and developer experience: The ecosystem around specifications like OpenAPI grew rapidly, offering immediate, benefits that proved irresistible to development teams.

This is actually completely irrelevant and ignores the fact that REST as designed was never meant to be used in the vast situations where RPC over HTTP is used. The use cases for "RPC over HTTP" and REST have incredibly low overlap.

>These tools provided powerfu...

> the only clients that are evolvable enough to interface with a REST API can be categorized to these three types

You mention swagger. Swagger is an anti-REST tech. Defining a media type is the REST equivalent of writing a swagger API description.

If you can define an API in swagger, you can define one via a media type. It's just that the latter is generally not done because to do it requires a JSON schema (or similar) and people mostly don't use that or think of that as how one defines an API.

Boss: we need an API for XYZ

Employee: sure thing boss, I'll write it in swagger and implement by Friday!

I sympathize with the pedantry here and found Fielding's paper to be interesting, but this is a lost battle. When I see "REST API" I can safely assume the following:

- The API returns JSON

- CRUD actions are mapped to POST/GET/PUT/DELETE

- The team constantly bikesheds over correct status codes and at least a few are used contrary to the HTTP spec

- There's a decent chance listing endpoints were changed to POST to support complex filters

Like Agile, CI or DevOps you can insist on the original definition or submit to the semantic diffusion and use the terms as they are commonly understood.

> Like Agile, CI or DevOps you can insist on the original definition or submit to the semantic diffusion and use the terms as they are commonly understood

This is an insightful observation. It happens with pretty much everything

As it has been happening recently with the term vibecoding. It started with some definition, and now it’s morphed into more or less just meaning ai-assisted coding. Some people don’t like it[1]

1: https://simonwillison.net/2025/Mar/19/vibe-coding/

I really hate my conclusions here, but from a limited freedom point of view, if all of that is going to happen...

> The team constantly bikesheds over correct status codes and at least a few are used contrary to the HTTP spec

So we should better start with a standard scaffolding for the replies so we can encode the errors and forget about status codes. So the only thing generating an error status is unhandled exception mapped to 500. That's the one design that survives people disagreeing.

> There's a decent chance listing endpoints were changed to POST to support complex filters

So we'd better just standardize that lists support both GET and POST from the beginning. While you are there, also accept queries on both the url and body parameters.

(comment deleted)
> The team constantly bikesheds over correct status codes and at least a few are used contrary to the HTTP spec

I've done this enough times that now I don't really bother engaging. I don't believe anyone gets it 100% correct ever. As long as there is nothing egregiously incorrect, I'll accept whatever.

I also view it as inevitable.

I can count on one hand the number of times I've worked on a service that can accurately be modeled as just representational state transfer. The rest have at least some features that are inherently, inescapably some form of remote procedure call. Which the original REST model eschews.

This creates a lot of impedance mismatch, because the HTTP protocol's semantics just weren't designed to model that kind of thing. So yeah, it is hard to figure out how to shoehorn that into POST/GET/PUT/DELETE and HTTP status codes. And folks who say it's easy tend to get there by hyper-focusing on that one time they were lucky enough to be working on a project where it wasn't so hard, and dismissing as rare exceptions the 80% of cases where it did turn out to be a difficult quagmire that forced a bunch of unsatisfying compromises.

Alternatively you can pick a protocol that explicitly supports RPC. But that's not necessarily any better because all the well-known options with good language support are over-engineered monstrosities like GRPC, SOAP, and (shudder) CORBA. It might reduce your domain modeling headaches, but at the cost of increased engineering and operations hassle. I really can't blame anyone for deciding that an ad-hoc, ill-specified, janky application of not-actually-REST is the more pragmatic option. Because, frankly, it probably is.

(comment deleted)
Fielding won the war precisely because he was intellectually incoherent and mostly wrong. It's the "worse is better" of the 21st century.

RPC systems were notoriously unergonomic and at best marginally successful. See Sun RPC, RMI, DCOM, CORBA, XML-RPC, SOAP, Protocol Buffers, etc.

People say it is not RPC but all the time we write some function in Javascript like

   const getItem = async (itemId) => { ... }
which does a

   GET /item/{item_id}
and on the backend we have a function that looks like

   Item getItem(String itemId) { ... }
with some annotation that explains how to map the URL to an item call. So it is RPC, but instead of a highly complex system that is intellectually coherent but awkward and makes developers puke, we have a system that's more manual than it could be but has a lot of slack and leaves developers feeling like they're in control. 80% of what's wrong with it is that people won't just use ISO 8601 dates.
100% agreed, “language evolves”

This article also tries to make the distinction of not focusing on the verbs themselves. That the RESTful dissertation doesn’t focus on them.

The other side of this is that the IETF RESTful proposals from 1999 that talk about the protocol for implementation are just incomplete. The obscure verbs have no consensus on their implementation and libraries across platforms may do PUT, PATCH, DELETE incompatibly. This is enough reason to just stick with GET and POST and not try to be a strict REST adherents since you’ll hit a wall.

To me, the most important nuance really is that just like "hypermedia links" (encoded as different link types, either with Link HTTP header or within the returned results) are "generic" (think that "activate" link), so is REST as done today: if you messed up and the proper action should not be "activate" but "enable", you are in no better position than having to change from /api/v1/account/ID/activate to /api/v2/account/ID/enable.

You still have to "hard code" somewhere what action anything needs to do over an API (and there is more missing metadata, like icons, translations for action description...).

Mostly to say that any thought of this approach being more general is only marginal, and really an illusion!

I've been doing web development for more than a decade and I still can't figure out what REST actually means, it's more of a vibe.

When I think about some of the RESTy things we do like return part of the response as different HTTP codes, they don't really add that much value vs. keeping things on the same layer. So maybe the biggest value add so far is JSON, which thanks to its limited nature prevents complication, and OpenAPI ecosystem which grew kinda organically to provide pretty nice codegen and clients.

More complexity lessons here: look at oneOf support in OpenAPI implementations, and you will find half of them flat out don't have it, and the other half are buggy even in YOTL 2025.

  > The team constantly bikesheds over correct status codes and at least a few are used contrary to the HTTP spec
Haha yes! Is it even a dev team if they haven't had an overly heated argument about which 4xx code to return for an error state?
I describe mine as a JSON-Based Representational State SOAP API to other internal teams. When their eyes cross I get to work sifting through the contents of their pockets for linting errors and JIRA tickets.
RESTful has gone far beyond the http world. It's the new RPC with JSON payload for whatever. I use it on embedded systems that has no database at all, POST/GET/PUT/DELETE etc are perfectly simple to map into WRITE|READ|Modify|Remove commands. As long as the API is documented, I don't really care about its http origins.
> The team constantly bikesheds over correct status codes and at least a few are used contrary to the HTTP spec

401 Unauthorized. When the user is unauthenticated.

403 Forbidden. When the user is unauthorized.

> - The team constantly bikesheds over correct status codes and at least a few are used contrary to the HTTP spec

I really wish people just used 200 status code and put encoded errors in the payloads themselves instead of trying to fuse the transport layer's (which HTTP serves as, in this case) concerns with the application's concerns. Seriously, HTTP does not mandate that e.g. "HTTP/1.1 503 Ooops\r\n\r\n" should be stuffed into the TCP's RST packet, or into whatever TLS uses to signal severe errors, for bloody obvious reasons: it doesn't belong there.

Like, when you get a 403/404 error, it's very bloody difficult to tell apart the "the reverse proxy before the server is misconfigured and somebody forgot to expose the endpoint" and "the server executed your request to look up an item perfectly fine: the DB is functional, and the item you asked for is not in there" scenarios. And yeah, of course I could (and should) look at and try to parse the response's body but why? This "let's split off the 'error code' part of the message from the message and stuff it somewhere into the metadata, that'll be fine, those never get messed up or used for anything else, so no chance of confusion" approach just complicates things for everyone for no benefit whatsoever.

I have seen monstrosities claiming to be rest that use HTTP but actually have a separate set of action verbs, nestled inside of HTTP's.

In a server holding a "deck of cards," there might be a "HTTP GET <blah-de-blah>/shuffle.html" call with the side-effect of performing a server-side randomization operation.

I just made that up because I don't want to impugn anyone. But I've seen API sets full of nonsense just like that.

Exactly. What you describe is how I see REST being used today and I wish people accepted the semantic shift and stopped with their well-ackshually. It serves nothing.
- the inclusion of HATEAOS links which are NEVER used
If you want to produce better APIs, try consuming them. A lot of places have this clean split between backend and frontend teams. They barely talk to each other sometimes. And a pattern I've seen over and over again is that some product manager decides feature X is needed. The backend team goes to work and delivers some API for feature X and then the frontend team has to consume the API. These APIs aren't necessarily very good if the backend people don't understand how the frontend uses them.

The symptom is usually if a seemingly simple API change on the backend leads to a lot of unexpected client side complexity to consume the API. That's because the API change breaks with some frontend expectation/assumption that frontend developers then need to work around. A simple example: including a userId with a response. To a frontend developer, the userId is not useful. They'll need a user name, a profile photo, etc. Now you get into all sorts of possible "why don't you just .." type solutions. I've done them all. They all have issues and it leads to a lot of complexity on either the server or the client.

You can bloat your API and calculate all this server side. Now all your API calls that include a userId gain some extra fields. Which means extra lookups and joins. So they get a bit slower as well. But the frontend can pretend that the server always tells it everything it needs. The other solution is to look things up from the frontend. This adds overhead. But if the frontend is clever about it, a lot of that information is very cachable. And of course graphql emerged to give frontend developers the ability to just ask for what they need from some microservices.

All these approaches have pros and cons. Most of the complexity is about what comes back, not about how it comes back or how it is parsed. But it helps if the backend developers are at least aware of what is needed on the frontend. A good way is to just do some front end development for a while. It will make you a better backend developer. Or do both. And by that I don't mean do javascript everywhere and style yourself as a full stack developer because you whack all nails with the same hammer. I mean doing things properly and experiencing the mismatches and friction for yourself. And then learn to do it properly.

The above example with the userIds is real. I've had to deal with that on multiple projects. And I've tried all of the approaches. My most recent insight here is that user information changes infrequently and should be looked up separately from other information asynchronously and then cached client side. This keeps APIs simple and forces frontend developers to not treat the server as a magical oracle and instead do sane things client side to minimize API calls and deal with application state. Good state management is key. If you don't have that, dealing with stateless network protocols (like REST) is painful. But state has to live somewhere and having it client side makes you less dependent on how the server side state management works. Which means it's easier to fix things when that needs to change.

When I was working on my first HTTP-based API 13 years ago, based on many comments about true REST, I decided to first study what REST should really be. I've read Fielding's paper cover to cover, I've read RESTful Web Services Cookbook from O'Reilly and then proceeded to workaround Django idioms to provide REST API. This was a bit cargo cult thinking from my end, I didn't truly understand how REST would benefit my service. I took me several more years and several more HTTP APIs to understand that in the case of these services, there were no benefits.

The vision of API that is self discoverable and that works with a generic client is not practical in most cases. I think that perhaps AWS dashboard with its multitude of services has some generic UI code that allows to handle these services without service-specific logic, but I doubt even that.

Fielding's paper doesn't provide a complete recipe for building self-discoverable APIs. It is an architecture, but the details of how clients should really discover the endpoints and determine what these endpoints are doing is left out of the paper. To make truly discoverable API you need to specify protocol for endpoints discovery, operations descriptions, help messages etc. Then you need clients that understand your specification, so it is not really a generic client. If your service is the only one that implements this client, you made a lot of extra effort to end up with the same solution that not REST services implement - a service provides an API and JS code to work with the API (or a command line client that works with the API), but there is no client code reuse at all.

I also think that good UX is not compatible with REST goals. From a user perspective, app-specific code can provide better UX than generic code that can discover endpoints and provide UI for any app. Of course, UI elements can be standardized and described in some languages (remember XUL?), so UI can adapt to app requirements. But the most flexible way for such standardization is to provide a language like JavaScript that is responsible for building UI.

> The vision of API that is self discoverable and that works with a generic client is not practical in most cases. [..] Fielding's paper doesn't provide a complete recipe for building self-discoverable APIs. It is an architecture, but the details of how clients should really discover the endpoints and determine what these endpoints are doing is left out of the paper. To make truly discoverable API you need to specify protocol for endpoints discovery, operations descriptions, help messages etc. Then you need clients that understand your specification, so it is not really a generic client

You said what I've thought about REST better than I could have put it.

A true implementation of a REST client is simply not possible. Any client needs to know what all those URLs are going to do. If you suddenly add a new action (like /cansofspam/123/frobnicate), a client won't know what to do with it. The client will need to be updated to add frobnication functionality, or else it just ignores it. At best, it could present a "Frobnicate" button.

This really is why nobody has implemented a REST server or client that actually conforms to Fielding's paper. It's just not realistic to have a client that can truly self-discover an API without being written to know what APIs to expect.

Personally I never saw "self-discoverable" as a goal, let alone an achievable one, so I think you're overestimating the ambitions of simple client-design.

Notably, the term "discoverable" doesn't even appear in TFA.

Similarly, I call Java programs "Object Oriented programs" despite Alan Kays protests that it isn't at all what Object Orientation was described as in early papers.

The sad truth is that it's the less widely used concept that has to shift terminology, if it comes into wide use for something else or a "diluted" subset of the original idea(s). Maybe the true-OO-people have a term for Kay-like OO these days?

I think the idea of saving "REST" to mean the true Fielding style including HATEOAS and everything is probably as futile as trying to reserve OO to not include C++ or Java.

REST(ful) API issues can all be resolved with one addition:

Adding actions to it!

POST api/registration / api/signup? All of this sucks. Posting or putting on api/user? Also doesn‘t feel right.

POST to api/user:signup

Boom! Full REST for entities + actions with custom requests and responses for actions!

How do I make a restful filter call? GET request params are not enough…

You POST to api/user:search, boom!

(I prefer to use the description RESTful API, instead of REST API -everyone fails to implement pure REST anyways, and it‘s unnecessarily limited.)

I'll never understand why the HATEOAS meme hasn't died.

Is anyone using it? Anywhere?

What kind of magical client can make use of an auto-discoverable API? And why does this client have no prior knowledge of the server they are talking to?

I used it on an enterprise-grade video surveillance system. It was great - basically solved the versioning and permissions problem at the API level. We leveraged other RFCs where applicable.

The biggest issue was that people wanted to subvert the model to "make things easier" in ways that actually made things harder. The second biggest issue is that JSON is not, out of the box, a hypertext format. This makes application/json not suitable for HATEOAS, and forcing some hypertext semantics onto it always felt like a kludge.

(comment deleted)
And not everything in reality maps nicely to hypermedia conventions. The problem with REST is trying to shoehorn a lot of problems in a set of abstractions that were initially created for documents.
At some point, we built REST clients so generic they could handle nearly any use case. Honestly, building truly RESTful APIs has been easy for ages, just render HTML on the server and send it to the browser. That's 100% REST with no fuss.

The irony is, when people try to implement "pure REST" (as in Level 3 of the Richardson Maturity Model with HATEOAS), they often end up reinventing a worse version of a web browser. So it's no surprise that most developers stop at Level 2—using proper HTTP verbs and resource-based URIs. Full REST just isn't worth the complexity in most real-world applications.

https://htmx.org/img/memes/dbtohtml.png

LMAO all companies asking for extensive REST API design/implementation experience in their job requirements, along with the lastest hot frontend frameworks.

I should probably fire back by asking if they know what they're asking for, because I'm pretty sure they don't.