Core tool
Run a named goal on a live page
The synchronous execution primitive: open the target in a real browser, observe the DOM, plan, act, and return the indexed page state plus a success verdict — with a resumable pause when a sign-in hits a wall.
run_goal
What it is
The job: Do one thing on one site, right now, and block until you know whether it worked.
run_goal is the shortest path from an agent to a browser that has actually done something. You give it an authorized target URL and a goal name; the engine opens the page, serializes it into an indexed element map, plans the next action against that map, executes it, and repeats until the goal’s success condition is met or the step budget runs out. What comes back is the run: the path of actions taken, the number of steps, the final page state, and a boolean success.
It is synchronous. The HTTP request stays open for the whole run, which is what you want when the agent is waiting on the answer and unhelpful when the run is a ten-minute crawl — that is what submit_run is for.
The behaviour that surprises people most is the pause. A sign-in run that meets a 2FA prompt, an approval push, or a CAPTCHA it cannot clear does not fail: it parks the browser session and returns { status: "paused", sessionId, challenge, codeRequired } while the page stays open and waiting. You continue it with submit_verification, drive it by hand with control_run, or hand the whole sign-in to the user with connect_account. Pass hitl: false to opt out and get a plain needs-human miss instead.
The call
Call it exactly like this.
Copied from the tool's registration and the route handler — not paraphrased.
// MCP tool call — server "twin-browser"
{
"tool": "run_goal",
"arguments": {
"target": "https://app.example.com",
"goal": "login",
"account": "work",
"persistSession": true
}
}curl -X POST https://twin-browser.com/api/v1/run \
-H "Authorization: Bearer $TWIN_API_KEY" \
-H "content-type: application/json" \
-d '{"target":"https://app.example.com","goal":"login","account":"work","persistSession":true}'`goal` is a short identifier from the engine’s built-in goal registry — login, search, extract, reply, like — not a sentence. A free-text goal in the { target, goal } shape is rejected with a 400 that names the valid goals. For free-text instructions use dispatch, which takes { url, prompt, success }.
| Parameter | Type | What it does |
|---|---|---|
| target* | string | Base URL of the target you are authorized to act on. The URL is the authorization signal; a malformed one is a 400. |
| goal* | string | A registry goal name: login, search, extract, reply or like. An unknown name returns 400 with the valid list. |
| account | string | Credential/session label for this host, from list_accounts. Selects which stored login and which saved session jar the run uses. |
| persistSession | boolean | Persist and resume the browser session (cookies) for this account, so the next run is a returning login rather than a fresh one. |
| hitl | boolean | Park (resumable) on a 2FA/approval wall instead of failing. A sign-in run parks by DEFAULT; pass false to opt out. |
| proxy | string | Your own egress proxy URL, e.g. http://user:pass@host:port. Passed through untouched. |
| proxyRotate | boolean | Force per-request proxy rotation — the anonymous-scraping stance, the opposite of account stickiness. |
| sessionKey | string | Explicit override for the account stickiness key that pins the egress IP. |
| blockAssets | boolean | Abort image/media/font requests to save proxy bandwidth. Defaults ON when a proxy is set; pass false to keep visuals. |
| record | boolean | Record the run to video and return a videoId. See record_run, which forces this plus annotation. |
| annotate | boolean | Draw a visible cursor and element highlights during the run. Best paired with record. |
| stealth | boolean | Route to the full desktop browser profile (real Chrome, human-calibrated timing, residential egress). Needs a Pro/Enterprise plan (else 403 plan_required) AND authorized: true, and adds a per-success surcharge. |
| authorized | boolean | REQUIRED with stealth. Your attestation that you are entitled to access this target; recorded against the run. |
Returns
{
"success": true,
"path": [ /* the actions taken, secret values redacted */ ],
"steps": 7,
"bricks": [ /* the path segmented into named sub-flows */ ],
"indexedState": { /* the final indexed element map */ },
"llmCostMicro": 41200,
"totalTokens": 8134,
"runtimeMs": 24118,
"finishReason": "success",
"engineRev": "…",
"runId": "…",
"credits_charged": 10
}
// …or, when a sign-in run parks:
{ "status": "paused", "sessionId": "…", "reason": "…", "challenge": "sms-code", "codeRequired": true }
// …or, when the wall cannot be passed by automation at all:
{ "code": "connect_required", "connectUrl": "…", "connectExpiresAt": "…" }What it costs
10 credits
A flat 10-credit floor, settled as higher-of(floor, metered model + compute + egress) — so a cheap run costs the floor and a model-heavy one bills what it actually consumed. A run that parks refunds its reservation in full; the resume charges the floor when it completes. Stealth adds a per-success surcharge on top.
See the full rate cardWhich one
When a different tool is the right call.
The honest answer is often the neighbouring tool. These are the trades.
dispatchdispatch is the better default for anything you will ask twice: it fuzzy-matches your goal to an already-compiled skill and replays it for a fraction of the cost, compiling on a miss. Reach for run_goal when you want the plain, uncached run — a one-off, or a debugging pass where a cache hit would hide what the planner does.
dispatchsubmit_runIdentical work, asynchronous. Use submit_run when the run may outlive your request timeout or you want to fan several out at once; the trade is that you now own polling and a job id.
submit_runrun_skillIf the flow is already compiled, run_skill replays it deterministically for ~1 credit with no model in the loop. run_goal re-reasons every time — correct on a page that changes, wasteful on one that does not.
run_skillQuestions
run_goal, answered.
- Can I pass a free-text instruction as the goal?
- Not to run_goal. The { target, goal } shape resolves `goal` against the engine’s built-in registry (login, search, extract, reply, like); a goal containing whitespace is rejected at the edge with a 400 that points you at the ad-hoc form. For free text, call dispatch with { url, prompt, success } — it compiles and caches the result, so the second phrasing of the same request is a cheap cache hit.
- What happens when the site asks for a 2FA code?
- By default a sign-in run parks rather than failing. The response becomes { status: "paused", sessionId, challenge, codeRequired } and the browser stays open. Hand the code back with submit_verification, drive the page yourself with control_run, or mint a connect_account link and let the person sign in by hand. The pause refunds the run’s credit reservation; you are charged when it finishes.
- Does run_goal need my password?
- It needs a credential to exist, not to pass through your prompt. Store it once in the vault and reference it by name, or send the user a connect_account link so only the resulting session is captured. If a run needs a credential you have not stored, it returns code: "credential_missing" with the name of the secret it wanted.
Keep going
The rest of the tool set.
dispatchBe the one call an agent makes, and get cheaper every time the agent asks for the same thing in different words.
submit_runRun something that will outlive your request timeout, or run twenty things at once.
run_skillRun a known-good flow again, as cheaply as it can possibly be run.
submit_verificationFinish a sign-in that got as far as the second factor.
connect_accountGet logged into a site that an agent is not going to be allowed to log into.