We spent July measuring how well AI assistants can book tables at Amsterdam restaurants. 200 venues, one browser agent, one task: find the restaurant, understand it, check availability for a real date and party size, and reach the final booking step. We never submitted a reservation.
In the study we published the numbers. In this post I want to talk about the failure mode we saw more than any other, because it is a solvable problem and most people building assistants will hit it eventually.
The failure mode: the booking widget.
Most restaurants that pay for a booking product embed it as an iframe. A person visits the page, sees a button labelled Reserve, clicks it, a modal opens with a calendar and a time picker, they enter their party size, name and phone, and get a confirmation. That flow works because a person can see it, recognise it, and interpret it.
An agent driving a real browser sees something different.
What the agent gets
The parent page contains the button and often not much else about the booking. The button's onclick handler opens an iframe from a different origin. Inside that iframe:
- the DOM is not accessible from the parent page under the same-origin policy
- the fields are often rendered by a client-side app that mounts after a network round-trip
- availability lives in an internal API the widget calls once mounted, over a URL the agent has no access token for
- the confirmation is either shown as text inside the iframe, or as a redirect the parent page never sees
An agent that reads the parent page has no idea a booking flow even exists past the button. An agent that clicks the button gets a new browsing context it cannot introspect at the parent's level. An agent with full frame access can sometimes drive the widget by simulating clicks and typing, if the elements are stable, if focus is handled correctly, if a captcha does not fire, if the widget does not detect automation and reject. That is a lot of "if" for one reservation.
What most sites publish instead
Across the 163 working sites in our Amsterdam sample, we checked for machine-readable descriptions of the restaurant and its booking. Nine had them. All nine were Restaurant or LocalBusiness schema, generated by an SEO plugin. They describe the venue: its name, address, opening hours, cuisine, price range, review count.
They do not describe a way to book.
Schema.org has Reservation and ReserveAction types. They exist. They are not widely used. And where they appear, they typically describe a completed reservation, not a callable interface for creating one.
An action is a URL an agent can POST to, with a shape it can construct. A description is a paragraph an agent can quote back at you. Every site we checked published descriptions. None published an action.
The ChatGPT moment
One moment from building the harness sums this up. We put a small booking API on our demo instance behind a plain HTML page describing it in prose: the endpoint, the required fields, the response shape. Not a widget, just a page.
We asked ChatGPT to make a booking against it. ChatGPT read the page, understood the API, listed the required fields, and reported politely that its web tool cannot issue a POST request. It could not act. The page was for humans, the API was for agents, and the assistant sat between them, unable to bridge either.
Assistants act through tools, not pages. They read pages, they don't press buttons.
What agent-ready would actually look like
In order of what actually moves the needle:
- A callable booking endpoint. HTTP, JSON, versioned. Availability query, slot creation, cancellation. Auth via an API key the venue can rotate. This is what an agent's tool would be pointed at.
- Declare it. A .well-known URL, or a schema.org ReserveAction block, or a Model Context Protocol server, or all three. Discovery is cheap once the action exists.
- If you keep the visual widget, keep it. Both routes reach the same table.
None of that requires redesigning the website. The widget stays. The agent gets a door that isn't behind glass.
A note on Model Context Protocol
MCP is where I ended up after this project. It is a protocol for exposing tools an assistant can call, and it is the closest thing today to a widely supported answer to the question "how does my assistant do this specific action". OpenAI, Anthropic, Google and Perplexity all consume it in some form. Any assistant with an MCP client can list your available tools, understand their inputs and call them, without ever seeing a page.
I will write about the MCP server we built for booking in the next post. This one was about the problem, because the problem is the part most builders discover the hard way, one iframe at a time.
If you build assistants that have to complete real-world actions, I would genuinely like to hear where they break for you.
The full field study, method and aggregate data: https://g-lab.studio/research/amsterdam-restaurants-2026
Top comments (0)