Product
How Rindler Works: Websites as APIs
Rindler exposes websites through a hosted MCP server that returns structured JSON. Your agent can drive a site without parsing raw HTML, accessibility trees, or DOM selectors. Authentication, navigation, retries, and error recovery run server-side.
Rindler does the hard work once, before execution. The system has two parts joined by a config: a pre-execution mapping that turns a site into screens, actions, and extraction schemas, and a runtime that executes those mapped actions inside managed browser sessions and returns records instead of pages. This page covers both parts and traces one complete session.
One problem, split in two
Most approaches to putting an agent on a website make the model do the work live: fetch the page, read the DOM or an accessibility tree, guess selectors, click, re-read, and recover. That work repeats on every run and breaks when the layout shifts. Per-site API integrations avoid that fragility, but someone still has to build each one. The sites that matter most (legacy portals, private SaaS dashboards, government systems, multi-step forms) often never got a reliable API.
Rindler splits the problem. Before execution, a site is mapped into a config: its screens, the actions valid on each screen, and the extraction schemas that turn each screen into records. At runtime, a managed browser session executes those mapped actions and returns structured JSON. The server instructions to agents state the contract directly: Rindler turns websites into deterministic APIs, managing browser sessions and structured data extraction so agents never touch raw HTML or CSS selectors. Bot evasion is managed for you.
This applies to any website, including stores. If you can reach it in a browser, it can be mapped.
Part one: the pre-execution map
A mapping captures a site's capabilities in a config with three layers:
- Screens. Every state the site can be in gets a stable identifier: a results screen, a detail screen, a form. Each response names the current screen, so your agent always knows where it is.
- Actions. Each screen carries the actions valid on it. Each action has a name, a description, and a params schema. Your agent reads them from the response instead of inventing clicks.
- Extraction schemas. Every screen exposes structured records through an extraction schema: a results screen comes back as a
listingsarray, a detail screen as alistingrecord. A raw ARIA accessibility tree is only a fallback for screens with no schema yet.
Two properties keep the map accurate. Filters are surfaced dynamically, never hardcoded: a screen's filter controls change with context. For example, a marketplace's Vehicles category adds Make, Model, and Year. extract_content returns the page's live filter controls as a filters array each time instead of assuming a fixed set. Every mapping ships only after an automated verifier has passed it against the real site.
The catalog is not fixed. If the site you need is missing, paste its URL into the mapper at chat.rindler.ai and pick a Fast or Deep crawl. The result is a private config scoped to your account, usable immediately from your connected agent through start_session.
Part two: the runtime
At runtime, your agent never touches a page. start_session provisions a live browser session server-side, loads the site, and returns state instead of markup:
screen: the current screen identifiercontext: persistent session state (cart contents, a delivery address)data: the screen's records, including thesession_idavailable_actionsandglobal_actions: what the agent may do next, each with a name, description, and params schema
From there, dispatch_action is the verb. It executes one mapped action (search, filter, submit, add to cart) and returns the new screen plus the action names now valid. It is the one tool that changes external site state: it can submit forms, orders, messages, and applications. The contract is strict in both directions: the agent may only use action names from the most recent response, item names must come from a prior result's data field, and it never constructs URLs, CSS selectors, or HTML references.
That strict contract is what buys determinism. A response is typically hundreds of tokens of structured JSON, where a raw fetch of the same page is typically 50,000+ tokens of nav, footer, sidebar, and tracking noise around the actual data. The same call against the same site returns the same response shape across runs, independent of DOM changes, popups, or A/B-tested layouts.
Failure is structured too. If a modal dialog blocks the page, the error is blocked_by_dialog, naming the dialog and listing its safe dismiss candidates for the global dismiss_dialog action. The agent clears it and retries instead of losing the session. If the information lives in a linked PDF, extract_document reads it, fetched anonymously with no cookies, so it sees only what a signed-out visitor could. If a site has no config at all, start_session returns screen "unsupported_site", an explicit signal to fall back to native browsing instead of improvising.
A session, end to end
This example shows a complete session on a housing site. The shapes come straight from the live tool contracts. Values are elided.
// 1. Open the site. One call returns the landing screen and every valid action.
start_session({ "url": "https://apartments.com" })
// => {
// "screen": "...", // current screen identifier
// "context": { ... }, // persistent session state
// "data": { "session_id": "..." }, // screen data, incl. the session_id
// "available_actions": [
// { "name": "search_listings", "description": "...", "params": { ... } }
// ],
// "global_actions": [ ... ] // valid from any screen
// }
// 2. Act. Params match the schema published in available_actions.
dispatch_action({
"session_id": "...",
"action": "search_listings",
"params": { "min_beds": "2", "max_price": "3000" }
})
// => {
// "screen": "...",
// "data": { "listings": [ { "title": "Luxury 2BR in SoMa", "price": "..." } ] },
// "available_action_names": [ "view_listing", ... ],
// "global_action_names": [ ... ]
// }
// 3. Re-read the current screen as records at any point.
extract_content({ "session_id": "..." })
// => { "data": { "listings": [ ... ], "filters": [ ... ] } }
// 4. Done. Release the browser promptly.
close_session({ "session_id": "..." })
// => { "ok": true, "closed": true }
The protocol is the middle loop: read available_actions, dispatch one, read the new screen, repeat. close_session is idempotent, so closing an already-closed session is a safe no-op.
Signing in to gated sites
Two logins exist here, and they are deliberately separate.
Connecting your agent to Rindler itself uses OAuth 2.0 PKCE on first use: no API key, nothing to paste. get_auth_url generates the authorization URL.
Signing in to a website stays human. When a site or an action needs a login, the response carries an auth_context classifying the lane and, for the hosted-browser lane, a capture_url: a single-use sign-in link whose token expires in 15 minutes. You open it, sign in on the real site inside the managed browser (MFA included), and click "I'm logged in." Meanwhile, your agent calls await_auth, which long-polls (default 30 seconds per call, max 120) and returns status "captured" as soon as the login is ready. The agent then calls start_session again to pick up the authenticated session.
In this hosted-browser flow, Rindler never asks for or stores your password. You type it into the real site inside the live browser. Rindler keeps only the resulting signed-in session, encrypted with AES-256-GCM under a per-user key, scoped to you and that one site, and loaded automatically on later runs until it expires. When it expires, the response says so (auth_status comes back expired) and the agent gives you a fresh link.
A limited set of sites use a credential lane instead. There, the auth_context marks a credential prompt: the agent asks for exactly what the login needs (a username, password, or a one-time code) and dispatches it through Rindler as a mapped credential action, with no hosted browser involved. Letting Rindler store that credential so it can sign in for you is opt-in, per site, and encrypted. A credential_save_supported flag tells the agent when a login is a fresh one-time code that is used once and never saved. So for most sites, Rindler never stores your password at all.
manage_cookies lists every site you are signed into and can clear any saved login. It returns metadata only, never credential or cookie values. The full flow, including session lifetimes, is at /docs/auth.
Where memory fits
The config layer knows what a site can do. The memory tool stores what your agent learns while using it. It has three verbs: remember stores a fact, decision, or reusable how-to; recall runs hybrid semantic plus keyword search over past memories, with a per-site filter for site quirks; forget soft-retires anything wrong or stale. Memory is scoped per user, and no other user's memory is ever returned.
Because the mapping layer already handles page understanding, memory carries task knowledge (decisions, learned workflows, per-site quirks), not selector trivia that goes stale with the next redesign.
Where to go next
- /docs/quickstart: connect your agent with one command and run your first task.
- /docs/auth: review the full login handoff, what the agent sees, and session lifetimes.
- /compare: compare this model with scraping, raw browser automation, and per-site API builds.