Chapter 09 of 10
Design for the Client You Now Have
Hypermedia promised runtime discovery to clients that were all generated ahead of time. A caller that reads your description at runtime finally exists, and it reads the prose.
Hypermedia was the part of REST that never landed. The idea was that a response would carry the operations available next, so a client could navigate an API the way a browser navigates a site rather than by hard coding URLs.
It was coherent and it went almost entirely unused. This chapter is about why, and about what changed.
The audience did not exist
The reason hypermedia controls stayed unused is not that the argument was wrong. Roy Fielding's often quoted post insisting that a REST API must be hypertext driven made the case clearly enough, and Leonard Richardson's maturity model put hypermedia controls at the top for the same reason.
The problem was the client. Almost every consumer of an HTTP API was written against a description at build time, whether by a person reading documentation or by a generator producing a typed client. Such a client already knows every URL it will ever call, so a link object in a response tells it something it has been compiled with. Following the links would have been strictly more work for the same result.
So the capability shipped in a handful of APIs and the callers ignored it, which is the ordinary fate of a feature nobody is on the other side of.
The runtime reader arrives
An agent driven client is genuinely different, because it decides which call to make while it is running. That is the consumer hypermedia was designed for, and it does not use hypermedia.
What it reads is your description. It is given a set of operations with their names, summaries, parameters and schemas, and it picks one by reasoning over that text. The link objects in a response body are not where it looks, because it needed to know what was possible before it made the first call.
The idea therefore came back and the mechanism did not. Runtime discovery is now real, and the surface it happens on is the API description rather than the payload.
What the reader actually uses
Designing for that reader is mostly a matter of writing down what you were leaving to convention.
The operation name carries the first guess. An identifier like cancelOrder says what happens, and one like postOrdersIdCancel2 says only where it sits. That name is also what a code generator turns into a method, so the work pays twice.
The description answers what a schema cannot. Which of two similar operations to use, what the operation is for, what it costs, and when not to call it. A field typed as a string with no description is a field the caller will fill in with something plausible.
Preconditions have to be stated. A create that needs an idempotency key, a filter that is required in practice, an ordering constraint between two calls. None of that is expressible in a schema, and all of it is expressible in a sentence.
Enums need meaning, not just membership. A status of pending against processing is a distinction a schema records and does not explain, so the difference belongs in the description.
Errors should say what to change. A caller that can read prose can act on an instruction. That makes the error text from the error design chapter worth more here than anywhere else, because a good message turns a failure into a corrected retry rather than a stop.
The descriptions chapter of the OpenAPI guide goes through the questions a description has to answer in more detail.
The hypermedia that did survive
Two narrow uses of links outlived the general idea, and both are worth keeping.
The Location header on a 201 points at the resource that was created, which saves every client from digging an identifier out of a body. And a 202 needs somewhere to look for the outcome, so handing back a reference to a status resource is a link in everything but name. Both are covered in methods and status codes.
What did not survive is the general link collection on every representation. If your API does not have it, adding it now for a runtime reader is aiming at the wrong surface.
In Routebase
The description is a first class field on the operation rather than an afterthought, and it is edited where the operation is defined.
Operation identifiers are generated from the method and path and can be overridden, which is the moment to give an action endpoint a name that reads like a method. Uniqueness is checked as an error, and a second rule keeps the identifier safe to use in a URL.
Because descriptions are part of the specification rather than a layer beside it, they travel with every export and reach the published reference documentation without a second edit. Style guide rules cover the completeness side, flagging operations and responses that carry no description, at severities you set per organisation and per project. The Endpoints guide covers the description editor and operation identifiers.
Frequently asked questions
Does HATEOAS matter now that clients can be LLM driven?
Not in its original form. Hypermedia proposed that a client discover what it can do next from link objects in the response. An agent driven client does discover at runtime, and it discovers from the API description rather than from links in a payload. So the underlying idea is more relevant than it was, while the specific mechanism is still largely unused.
What does an AI agent need from an API description?
The same things a new engineer needs, stated explicitly rather than implied. It needs an operation name that reads like what it does, and a description saying what the operation is for and when not to use it. It also needs preconditions that no schema can express, and error responses whose text says what to change. Anything left to convention is a thing the agent has to guess.
Is HATEOAS dead?
It was never widely adopted, which is a different thing. Most APIs called REST stop short of hypermedia controls, and the reason is that hardly any client was written to follow them, since clients were generated from a description ahead of time. The parts that did survive are the narrow ones, such as the Location header on a created resource and links to a status resource for asynchronous work.
How should error messages be written for agent callers?
So that the next action is in the message. A caller that can read prose can act on an instruction, so an error saying which field was wrong and what a valid value looks like turns a failure into a corrected retry. An error that says the request was invalid gives a human and an agent exactly the same amount to work with, which is none.
Last reviewed by The Routebase Team.