HATEOAS is its own thing. A good thing, IMO, but not the same thing as REST. Saying that "the innovative aspect of REST is HATEOAS" is like saying "the unique thing about rectangles is that all four sides are the same length." That's squares, not rectangles. Rectangles have a uniqueness all their own, whether they're square or not. REST has innovative aspects all its own, whether you're doing HATEOAS or not.

REpresentational State Transfer is, like it sounds, a very specific concept: it's about how the semantics of HTTP verbs + headers (e.g. Cache-Control headers, Allow headers, Vary headers, etc.) interact with clients and gateways (esp. caching gateways, clients in offline mode, etc); and how these interactions work better and "in harmony with" those HTTP semantics — improving cachability, decreasing number-of-origin-fetches, improving PWA offline graceful degradation, avoiding inconsistency and any need for cache-busting, etc. — when you model your exposed state-representations a certain way.

A REST model — a projection of your data as "REpresentations of State" — is a transformation of your data along lines such that each representation becomes an ADT with semantics that recapitulate those of an object store / WebDAV folder / HTML page as edited in the original WorldWideWeb.app. Each representation can be PUT; POSTed to; PATCHed, DELETEd; GET-ted and then cached; and HEAD-ed without triggering heavy computation. REST's REpresentations are objects that work in harmony with being passed around through the HTTP protocol.

However! Every "RESTful" API you've ever seen (at least if you do CRUD web-dev), is very likely a trivial RESTful API, because it's taking abstractions — resources — that are already inherently ADTs with REST-alike semantics, and just 1-to-1 exposing them via HTTP. (A lot of work may have been put in by some dev at some point in the past, into coming up with a best-practice design for those internal data structures such that they can work for the business-domain while also being inherently RESTful; but it's not REST itself carrying that weight, and it's not REST itself that determines whether it's possible to do that.)

It's great if your internal data is such that you can do that ; but that's not what makes REST powerful; it's not what it means to wield REST as a tool.

REST is a thing like GraphQL: a gateway adapter that reformulates an internal data model into a different, outward-facing data model. It's what frontend devs would call a data-binding layer — but, like GraphQL, one done on the backend. This is why REST talks about representations: representations are to REST as graphs are to GraphQL. They're a modelling layer on top of your data, that reshapes it. Except that, unlike GraphQL, which is almost as bad for HTTP semantics as SOAP, REST is (by definition) whatever transformed form allows your data to be handled most optimally in an ecosystem of HTTP clients+servers+gateways.

This concept is most powerful when applied to things that aren't inherently very RESTful at all; where it's challenging to state them in RESTful terms. For example, an API for controlling an ephemeral pushdown-automata, like an SQL connection. Or an API for managing subscriptions and consumer-groups in a message-queue broker. Or an API for scheduling jobs onto a workload manager.

Re: that last one, Kubernetes is a great example of what it means to "do REST" in a nontrivial way: it takes resources that aren't inherently RESTful, and creates abstract REpresentations of these resources (YAML manifests) that work perfectly under HTTP semantics, transferring the state of these representations to clients in a way where the client can then do things like modify the representation and PUT or PATCH it back. You can stick a caching proxy between you and a Kubernetes control-plane, and everything keeps working. You can query a Kubernetes control-plane from your browser address bar.

But note how, when you create a nontrivial abstraction like this, you end up with very custom types for your REpresentations. Because there are no inherent REST-shaped resources in your backend to manipulate; and because you want your REpresentations to be long-lived documents in their own right (in caches; on the client as something for it to hold and manipulate and patch; etc), you have to define an application-layer formal semantics for what those documents are, and do. You have to ensure that a REpresentation a client fetched a year ago, and is pushing back as a PUT today, will either work as-is; be "migrated" in a forward-compatible way when received by the backend; or be able to be cleanly rejected, by your REST server, despite that document having a schema that is now older than the REpresentation you're currently emitting from that endpoint.

How do you do that? People who don't understand REST use ugly URL hacks for this — like putting the version in the URL, the format as a file extension in the URL, etc. But these hacks fight against the semantics of HTTP.

Say you have an internal Foo ADT, that you first exposed as a FooV1 representation, and then later additionally as a FooV2 representation. If you do that by exposing e.g. /foos_v1/1 and then /foos_v2/1; then a PATCH to /foos_v1/1 can't possibly inherently invalidate a reverse-proxy cached representation for /foos_v2/1. But if you've just got /foos/1, that Vary's on Allow? Then it all "just works."

To make that work, FooV1 and FooV2 have to send separate Content-Types.

QED? ;)