For AI product teams

Ship the browser feature without running the fleet.

Your product needs an agent that can sign in, click and read live pages for each of your customers. Twin Browser is that layer as an API: per-customer subtenants, isolated sessions, a live view you can stream into your own UI, and repeat work that replays from a compiled skill instead of a fresh model call.

The problem

What this costs you today.

The demo took a weekend: an LLM, a headless browser, a few selectors. Production is a different product. Every customer needs their own logins and their own isolated session. Sites answer a datacenter IP differently than they answer a person. Someone has to own proxy egress, CAPTCHA, 2FA, session persistence, video capture for support tickets, and a fleet that does not fall over at 9am. None of that is the thing you are selling, and all of it is now on your roadmap — while the model bill grows linearly with every customer you add.

  • “Two of our four engineers are maintaining browser infrastructure, not building the product.”
  • “Our per-customer COGS goes up every month because every run re-pays for the same reasoning.”
  • “We need each customer’s credentials and cookies isolated from every other customer’s, and I do not want to build that.”
  • “Support asks what the agent actually did, and all we can show them is a log line.”
portal.example.com
  1. Your agent posts a goaldone
  2. Subtenant key resolves the sessionrunning
  3. Browser acts on the live pagequeued
  4. Frames stream to your UIqueued
  5. Result + recording returnedqueued
One Twin run for browser infrastructure for ai products — the work happens in a real browser, under your guardrails.

How Twin solves it

The mechanism, not a promise.

Twin is the execution layer, not a framework you adopt. You keep your agent, your prompts and your product; you call an HTTP endpoint (or an MCP tool) when it needs to touch a real browser. Isolation, credentials, egress, recording and metering are the layer’s job.

  1. 1One subtenant per customerPOST /api/v1/tenants provisions an isolated tenant with its own API key, its own vault and its own sessions. `billing:"parent"` bills your pool and attributes every charge to that subtenant; `billing:"self"` gives them a balance of their own. API-key scopes are enforced, so a customer key cannot provision.
  2. 2Your agent calls one endpointPOST /api/v1/run takes a URL, a goal in words, and a structured success condition. The tenant is derived from the Bearer key, never from the body — there is no cross-tenant call to get wrong.
  3. 3Stream the browser into your own UIPOST /api/v1/live returns the same run as Server-Sent Events: a `meta` frame with the runId, JPEG `frame` events, then a terminal `result`. GET /api/v1/runs/{id}/video serves the durable recording afterwards.
  4. 4Repeat work stops paying for reasoningPOST /api/v1/dispatch fronts the same body with the semantic cache: a re-phrased request matches a skill this key already compiled and replays it, so the planner model is never invoked on the second, tenth or thousandth run.
  5. 5A wall becomes a callback, not a failureA run that meets a 2FA or approval wall returns `{ status:"paused", sessionId }` instead of failing, resumable via POST /api/v1/runs/{id}/resume — so your product can ask its user for a code rather than showing them an error.

In practice

The actual call, and what it returns.

Two calls: one to give a customer their own isolated tenant, one for that customer’s agent to drive a browser under it. The second call is the only one your product makes at runtime.

Provision + runper-customer-agent.shbash
# 1 — provision an isolated subtenant (needs the tenants:provision scope).
curl https://twin-browser.com/api/v1/tenants \
  -H "Authorization: Bearer $TWIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "acme-corp", "billing": "parent" }'

# → 201
# {
#   "tenant": { "id": "…", "name": "acme-corp", "slug": "acme-corp",
#               "plan": "…", "billing": "parent" },
#   "apiKey": "…"          <- returned ONCE; store it against your customer
# }

# 2 — that customer's agent drives a browser with its own key.
curl https://twin-browser.com/api/v1/run \
  -H "Authorization: Bearer $SUBTENANT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://app.vendor.example.com/invoices",
    "prompt": "open the newest unpaid invoice and read its number and total",
    "success": { "kind": "extracted" },
    "account": "vendor-login",
    "persistSession": true
  }'

# → 200 { "success": true, "steps": 6, "runId": "…", "credits_charged": 10 }

What this call does

  • `billing` decides who pays: "parent" charges your pool and attributes the charge to the subtenant; "self" gives the subtenant its own balance.
  • The API key is returned exactly once by POST /tenants — there is no endpoint that reads it back.
  • Nested resellers are refused with a 409, so the tenancy tree stays one level deep and auditable.
  • Swap /run for /live and the identical body streams JPEG frames over SSE, which is what you render inside your own product.
Every endpoint, every field

What it costs

Priced per action, not per seat.

You are buying per-run cost that falls with repetition, not a seat licence. These are the flat floors; a run that burns more in model and compute than its floor bills the higher of the two.

Credit cost of the actions this solution uses
ActionCreditsWhat you get
POST /run — an agent run10 / runThe synchronous goal-to-result call your product makes.
POST /live — the same run, streamed10 / runSSE frames for your UI; the recording is kept for replay.
POST /dispatch — cache hit2 / runA re-phrased request matched a compiled skill. No planner model.
POST /dispatch — cache miss10 / runNothing matched: it plans once and caches the result for next time.

How the unit works

  • $1 buys 1,000 credits; the smallest pack is $5.
  • A paid action bills the higher of its flat floor and what it actually spent on model, compute and egress — so a cheap run stays cheap.
  • “Metered” means the action has no published flat floor on this page: GET /api/v1/pricing serves the live card.
  • Subtenants add no per-tenant fee — a "parent"-billed subtenant spends from your pool and every charge is attributed to it.
The full rate card

Be sure this fits

What this does not do.

Every one of these will come up in your evaluation. Here they are first, from us.

It is not a raw browser you drive frame by frame

There is no CDP socket to attach to. You get goal-shaped endpoints (/run, /live, /observe, /extract) and manual control of a PAUSED run via POST /runs/{id}/input. If your product needs to script every keystroke itself, drive Playwright and use Twin for the parts that need planning.

The dashboard is ours, not yours

A subtenant is an isolation and billing boundary in the API. There is no white-label console you can re-skin and hand to your customers — the surface you give them is the one you build on top of these endpoints.

The first run of a new flow still costs a full planning pass

The cache pays off on repetition. A customer whose every request is genuinely novel pays the miss price every time; the economics improve because your customers do the same things repeatedly, not because the cache is magic.

Under the hood

The primitives this runs on.

Nothing here is specific to this problem — the same mechanisms carry every solution on the site.

Over MCP, the same work is these tools

  • run_goal

    Execute a goal on a target URL you provide — observe, plan, and act until a success condition is met. Takes free text ({ url, prompt, success }) or a one-word built-in goal ({ target, goal }). Full run control: egress country and tier, humanized timing, warm-up, stealth, file upload, credentials, recording. A sign-in run parks by default on a 2FA/approval wall it can’t auto-resolve, returning { status:"paused", sessionId } (resume with submit_verification); pass hitl:false to opt out. If the result carries code:"credential_rejected", the site REFUSED the identifiers the run typed (credentialRejected.secrets names them, never their values) — running the same goal again sends the same wrong ones, so supply different credentials or send the user a connect_account link instead of retrying.

  • dispatch

    The cheap path: fuzzy-match a goal to an existing compiled skill via the semantic cache — a hit replays deterministically (~5× cheaper), a miss compiles and caches it for next time (needs WEB_BASE_URL + TWIN_API_KEY).

  • submit_run

    Submit a goal as an async background job; returns a job id immediately. Same two shapes and the same run controls as run_goal. Like run_goal, a sign-in job parks by default on a 2FA/approval wall — get_job then reports status:"paused" with a sessionId.

  • get_job

    Poll an async job by id for status, success, steps and result.

Every MCP tool

FAQ

Browser infrastructure for AI productscommon questions.

Can I keep my own agent framework?
Yes. Twin is called, not adopted: your planner, prompts and product logic stay where they are, and you POST to /api/v1/run (or load the MCP server) at the moment the agent has to touch a real page. There is no SDK you have to build around.
How do I keep one customer’s logins away from another’s?
Provision a subtenant per customer with POST /api/v1/tenants. Each gets its own API key, its own credential vault and its own stored sessions, and the tenant is always derived from the key on the request rather than from anything in the body.
What does my customer see when a site asks for a 2FA code?
The run returns { status:"paused", sessionId } with the challenge type instead of failing. Your product collects the code from your user and POSTs it to /api/v1/runs/{id}/resume, and the same parked browser session continues from the step it stopped on.
Do I have to use the semantic cache?
No. /run always plans; /dispatch is the cached front door for the same body. Point the traffic you expect to repeat at /dispatch and leave genuinely novel work on /run.

Try it on your hardest screen.

Start free, point a run at the system that is blocking you, and watch it happen live. If it does not work, the run tells you why — and what to do instead.