CAPABILITY · HITL

A wall parks the run instead of failing it

A 2FA prompt, an approval push or a CAPTCHA the agent cannot clear returns status "paused" with a live session — resolve it automatically from a connected inbox, hand back a code, or drive the page yourself.

status: "paused" → POST /api/v1/runs/{id}/resume

Capability

Inside human-in-the-loop handoff

Real automation meets steps a machine should not decide alone, and steps it simply cannot complete. Both are handled the same way: the run parks rather than failing. The browser session stays open and waiting, the response carries a sessionId and a description of the challenge, and there are four ways forward — auto-resolution from a connected inbox, a code handed back, direct interaction with the page, or a link the end user opens themselves.

Sign-in runs park by default

A run with credentials, an account label or a login-worded goal that hits a wall it cannot auto-resolve returns { status: "paused", sessionId, reason, challenge } instead of a failure. Pass hitl: false to opt out and get a plain needs-human miss.

No human at all, when a code is fetchable

Connect an IMAP inbox (POST /api/v1/email-inbox, validated against a real connection before it is stored) and the engine reads and fills the emailed code itself. The pause never surfaces.

Four ways forward from a park

Hand back a code or an out-of-band approval with POST /runs/{id}/resume; supply a missing credential in the same call; click and type on the page yourself with POST /runs/{id}/input; or mint a connect link so the end user signs in by hand.

Watch while you decide

GET /runs/{id}/stream mirrors the parked browser as a live screencast. It drives no agent and costs no credits — it is showing you a browser that is already open and waiting.

How it works

The mechanism, in execution order

4 stages, in the order the runtime performs them — not a summary of them.

HUMAN IN THE LOOPRun in flight2FA / approvalstatus: "paused"Connected inboxTwin fills the codeA person supplies itcode · approval · linkResumesame stepit parks and waits — it does not guess, and it does not start over
  1. 1Hit the wallThe executor reaches a second factor, an approval push, or a challenge it cannot clear in session.
  2. 2Try to resolve itWith a connected inbox or phone number, the emailed or texted code is fetched and filled automatically and the run continues.
  3. 3ParkOtherwise the run returns status "paused" with a sessionId, a human-readable reason, and the challenge kind. The credit reservation is refunded.
  4. 4Resume in placeA code, a credential, an interaction or a completed connect link unblocks the same parked session. The run continues from the step it stopped on and is charged when it completes.

In practice

A call you can paste and run

Resume continues the browser that is already open — no restart, no lost session state, no replay from the top.

resume.shbash
# A sign-in run parks:
# { "status": "paused", "sessionId": "sess_…", "reason": "…",
#   "challenge": "sms-code", "codeRequired": true }

# Hand back the code — the same parked browser continues
curl -X POST https://twin-browser.com/api/v1/runs/RUN_ID/resume \
  -H "Authorization: Bearer $TWIN_API_KEY" \
  -H "content-type: application/json" \
  -d '{"sessionId":"sess_abc123","code":"483920"}'

# …or supply a credential the run turned out to need
curl -X POST https://twin-browser.com/api/v1/runs/RUN_ID/resume \
  -H "Authorization: Bearer $TWIN_API_KEY" \
  -H "content-type: application/json" \
  -d '{"sessionId":"sess_abc123",
      "secrets":{"email":"ops@acme.com","password":"the-password"}}'
# Values are redacted from every frame, step and log.
api.twin-browser.com
  1. Hit the walldone
  2. Try to resolve itrunning
  3. Parkqueued
  4. Resume in placequeued

What it costs, how to switch it on

Priced from the same rate card the API serves

Every credit figure on this page is read from the rate card behind GET /api/v1/pricing — it is not typed into the copy, so it cannot drift from what you are billed.

Free to park · 10-credit floor when it completes

A park refunds the run's reservation in full — you are not charged for a run that stopped and asked. Resuming charges the run floor on completion; a resume that finds the run still paused is refunded again and returns paused. Watching a parked session (GET /runs/{id}/stream) and driving it (POST /runs/{id}/input) cost nothing: they mirror and steer a browser that is already open.

Full rate card

Turning it on

  1. 1Do nothing for the defaultA sign-in run already parks. You only need `hitl` when you want to opt OUT with false.
  2. 2Connect an inbox to skip the humanPOST /api/v1/email-inbox with an IMAP host, user and app-password. It is validated against a real connection before it is stored, and the password is write-only.
  3. 3Handle the paused shapeBranch on status === "paused" and keep the sessionId. It is the handle for every continuation path.
  4. 4Give a person a windowGET /runs/{id}/stream plus POST /runs/{id}/input is a two-endpoint remote-control surface you can put in your own UI.

At a glance

The contract, in the fewest rows that say it

PropertyTwin Browser
Paused shape{ status, sessionId, reason, challenge }
ResumePOST /api/v1/runs/{id}/resume
InteractPOST /api/v1/runs/{id}/input
WatchGET /api/v1/runs/{id}/stream (free)
Streaming pathPOST /api/v1/live/resume (token)
DefaultSign-in intent parks; `hitl: false` opts out

Limits

Where it stops, and what it deliberately does not do

A capabilities page with no limits section is a brochure. These are the ceilings, the defaults that will surprise you, and the things this capability is not.

Limits and defaults

  • A parked session does not wait forever. Resume against an expired session answers 409.
  • Interaction coordinates are viewport FRACTIONS (0..1 from the top-left), not pixels — they are hit-tested server-side against the observed element map.
  • There is no "done" call on /input. The run re-checks the page after every interaction and continues by itself once the block clears.
  • A code handed to a LIVE streamed run goes to POST /api/v1/live/resume with its token, not to the run resume endpoint.

What it does not do

  • It does not guess. A wall it cannot clear stops the run and asks; it never invents an action to get past one.
  • It does not generate authenticator (TOTP) codes for you. A code only you hold is a code you hand back.
  • It does not store typed text from /input. Interaction text is redacted from the stream and not persisted.
  • It does not turn a sign-in that a site blocks outright into an automated one — that case mints a connect link for a human instead.

FAQ

Questions about human-in-the-loop handoff

Does the run start over after a handoff?
No. The browser session is parked, not torn down. Resuming continues on the same open session from the step that stopped, with the code, credential or interaction folded in.
Is this how MFA is handled?
Partly, and the split matters. An emailed code with a connected IMAP inbox is fetched and filled with no human in the loop. An authenticator-app code, a phone approval, or a code only a person holds parks the run and waits for you to hand it back.
What if the run needs a password I never stored?
It returns code: "credential_missing" with the name of the secret it wanted. Either store it (POST /api/v1/secrets) and re-run, pass it in the `secrets` field of the resume call, or mint a connect link so the person whose password it is signs in themselves.
Am I charged for a run that parks?
No. The reservation is refunded when it parks; the run floor is charged when a resume completes it. Watching and driving a parked session are free.

Put your agent to work. Keep the decision.

Start free. Hand your agent a goal on a site you authorize, set the guardrails, and let the first successful run compile the skill every run after it replays.