For implementation and onboarding teams

Enter a thousand records into a system with no importer.

Onboarding stalls on data entry: the new system has no bulk import, the old one has no export, and the implementation team is typing. Compile the entry flow once, then replay it per record with the values bound at call time — and reconcile what did not land from the run list.

The problem

What this costs you today.

Every new customer arrives with a spreadsheet and a deadline. The target system’s importer covers three of the eleven fields, or it exists on a plan the customer is not on, or the records have to be created through a wizard because that is the only place the validation runs. So the implementation team types — for a week, per customer — and the go-live date is set by how fast people can key in records.

  • “Onboarding takes three weeks and two of them are data entry.”
  • “The importer does not cover the fields that matter, so we use the UI anyway.”
  • “We hired temps for the migration and then we had to check their work.”
  • “Every customer’s data is slightly different, so a script never survives to the next one.”
portal.example.com
  1. Read the next recorddone
  2. Bind params for this rowrunning
  3. Replay the entry flowqueued
  4. Verify the created recordqueued
  5. Log the failures for reviewqueued
One Twin run for onboarding and migration data entry — the work happens in a real browser, under your guardrails.

How Twin solves it

The mechanism, not a promise.

The entry flow is compiled once and then called like a function, once per record. Because parameters, secrets and the session are bound at call time rather than frozen at compile time, the same skill runs against the next customer’s tenant with different values and a different login.

  1. 1Compile the wizard oncePOST /api/v1/skills discovers the entry flow with the planner and minimizes it into a stored, named path. Write the variable parts of the goal as `{{param:NAME}}`.
  2. 2Replay it per recordPOST /api/v1/skills/{name}/run with `params` for the record and `account` for the login. Each replay is deterministic and runs without a model.
  3. 3Refuse under-specified records earlyA replay missing a parameter returns 400 with `code:"params_missing"` and the missing names, before a run row exists and before credits are reserved — a bad row in the CSV costs nothing.
  4. 4Reconcile from the run listGET /api/v1/runs filters by `mode` and `status`, so `?mode=skill&status=failed` is your exception report. Each run carries a runId, and GET /api/v1/runs/{id}/video is the recording of exactly what happened.
  5. 5Use the customer’s own login, safelyWhen the target tenant belongs to the customer, POST /api/v1/connect/sessions gives them a single-use link to sign in themselves. You never hold their password, and the migration still runs.

In practice

The actual call, and what it returns.

One compiled skill, one call per record, and an exception report you can query. The loop is deliberately boring — that is what makes a migration reviewable.

Replay + reconcilemigrate-records.shbash
# Compile once (POST /api/v1/skills), then replay per record.
while IFS=, read -r email plan; do
  curl -s https://twin-browser.com/api/v1/skills/create-account/run \
    -H "Authorization: Bearer $TWIN_API_KEY" \
    -H "Content-Type: application/json" \
    -d "{\"target\": \"https://app.newvendor.example.com\",
         \"account\": \"impl-team\",
         \"params\": { \"email\": \"$email\", \"plan\": \"$plan\" }}"
done < records.csv

# → 200 { "success": true, "steps": 7, "runId": "…", "credits_charged": 1 }

# Reconcile: everything that did not land, newest first.
curl "https://twin-browser.com/api/v1/runs?mode=skill&status=failed&limit=100" \
  -H "Authorization: Bearer $TWIN_API_KEY"

# Each failure carries a runId — GET /api/v1/runs/{id}/video is the recording.

What this call does

  • `params` are bound per call, so one compiled skill covers every record and every customer tenant.
  • A missing parameter is a 400 with `code:"params_missing"` before anything is charged — a malformed CSV row fails free.
  • GET /api/v1/runs takes `mode` (run | job | skill | compile | adapt) and `status` (running | complete | failed | paused | cancelled) so the exception report is one query.
  • For volume, submit the same work through POST /api/v1/jobs and let the completion webhook tell you when a batch finished.
Every endpoint, every field

What it costs

Priced per action, not per seat.

A migration is one compile and N replays. That is the point: the expensive part is paid once per flow, not once per record.

Credit cost of the actions this solution uses
ActionCreditsWhat you get
POST /skills — compile the entry flow50 / flowOne planning pass, minimized into a stored path.
POST /skills/{name}/run — one record1 / recordDeterministic replay, no model call.
POST /jobs — a background batch10 / runAsync submission with an HMAC-signed completion webhook.
POST /run — the awkward exception10 / runFor the record that does not fit the compiled flow.

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.
  • A cancelled job is refunded in full, and a replay refused for missing parameters is never charged at all.
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 browser-speed, not bulk-load speed

Each record is a real browser doing real clicks: seconds per record, with bounded concurrency. Ten thousand records is an overnight job, not a coffee break — and if the target system does have a working bulk importer, use it.

It does not validate your data for you

Twin verifies the flow’s success condition — that the record was created — not that the record is correct. Field-level validation, deduplication and mapping belong upstream, in the file you feed it.

A mid-flow change means a recompile

If the vendor changes the wizard halfway through a migration, replays that depended on the old structure will fail rather than guessing. That is deliberate, but it means somebody has to notice, recompile, and re-run the failures.

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

  • compile_skill

    Discover a goal once with the planner, then minimize it into a reusable, deterministic skill.

  • run_skill

    Blind-replay a compiled skill with no LLM in the loop — the cheap, deterministic path.

  • 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.

  • connect_account

    Get a one-time link that lets a HUMAN sign into a site by hand, so Twin Browser captures the session and later runs are already logged in. Use it when a run returns code:"credential_missing", when it returns code:"credential_rejected" and you have no better credential to supply (the one stored is wrong — a retry re-types it), or when a site hard-challenges automated logins from datacenter IPs and simply cannot be signed into by an agent. Never ask the user to paste a password — send this link. Re-run the task afterwards and it restores the session (sessionRestored:true).

Every MCP tool

FAQ

Onboarding and migration data entrycommon questions.

How do we prove what the automation actually did?
Every run has an id, a step list and a durable recording at GET /api/v1/runs/{id}/video, and GET /api/v1/runs filters by mode and status. A migration sign-off is a query plus the recordings of anything that failed.
The target system is the customer’s tenant. Do we need their password?
No. POST /api/v1/connect/sessions mints a single-use link scoped to that host; the customer signs in themselves and the captured session carries the migration. Their password never enters your systems.
What happens to the records that fail?
They stay failed and visible. Query /api/v1/runs?mode=skill&status=failed for the exception list, fix the input or recompile the flow, and re-run those records — nothing is silently retried into a half-created state.
Can a person approve a batch before it commits?
Set hitl:true and a run parks on an approval wall with { status:"paused", sessionId } instead of proceeding; POST /api/v1/runs/{id}/resume continues it from that step once a person has acted.

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.