For procurement, AP and supply-chain teams
One agent across every portal you have a login for.
Forty suppliers, forty portals, forty logins, and no two built the same. Twin keeps a labelled login and its own session per portal, lets a human sign in once where software cannot, and then pulls invoices, order status and documents on your schedule.
The problem
What this costs you today.
Every supplier has their own portal, and every portal is the same three screens in a different order. Somebody on the team signs into each one on a rota, checks order status, downloads the invoices, and re-types the totals into the ERP. The portals with an EDI feed are the lucky ones. The rest are a person with a password manager and a spreadsheet, and when that person is on leave the numbers are simply late.
- “We have logins for thirty-eight supplier portals and no integration with any of them.”
- “Half of them block anything that is not a person, so scripts never worked.”
- “Each portal needs its own login and its own session — one shared account is not an option.”
- “Month-end close waits on someone downloading PDFs by hand.”
- Restore the portal sessiondone
- Open the invoice listingrunning
- Expand rows deterministicallyqueued
- Record numbers and documentsqueued
- POST the result to your ERPqueued
How Twin solves it
The mechanism, not a promise.
The credential system, not the browser, is what makes this tractable: every login is a labelled account with its own encrypted session, and every run-shaped endpoint takes that label. Where a portal refuses software outright, a human signs in once through a hosted link and the captured session carries every later run.
- 1Label a login per portalEach stored login has a label and a host. GET /api/v1/accounts lists what you already have — label, host and a masked email preview, never a credential — so a run can be planned before a credit is spent.
- 2Let a human sign in where software cannotPOST /api/v1/connect/sessions mints a single-use link scoped to one host. Your colleague or the supplier’s user completes the sign-in — password, 2FA, CAPTCHA — in a hosted browser, and the session is captured. The password is never stored.
- 3Re-use the session, not the credentialPass `account` and `persistSession` on a run and the stored cookie jar is restored before the run and re-persisted after (`sessionRestored: true`), so day-to-day work does not touch a password or a second factor at all.
- 4Run the sweep in the backgroundPOST /api/v1/jobs submits the same body asynchronously and returns 202 with a jobId. Poll GET /api/v1/jobs/{id}, stream it, or pass callbackUrl and callbackSecret for an HMAC-signed webhook when it finishes.
- 5Read line items deterministicallyPOST /api/v1/extract with `rowSelector` and `rowFields` reads every structurally-alike row off a listing by selector — no model call for the reading itself — and reports `rowsFound`.
In practice
The actual call, and what it returns.
The portal that will not let software sign in gets a human, once. Everything after that is an async job against a restored session.
# 1 — the portal hard-blocks automated sign-in, so a HUMAN does it once.
curl https://twin-browser.com/api/v1/connect/sessions \
-H "Authorization: Bearer $TWIN_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "host": "portal.supplier.example.com", "account": "acme-supplies" }'
# → 201 { "id": "…", "url": "https://twin-browser.com/connect/…",
# "host": "portal.supplier.example.com", "account": "acme-supplies",
# "expiresAt": "…", "ttlMinutes": 30 }
#
# Send that url to the person who owns the login. Check it landed:
# GET /api/v1/connect/sessions → status: pending | active | connected | failed
# 2 — every later run restores that session. No password, no 2FA.
curl https://twin-browser.com/api/v1/jobs \
-H "Authorization: Bearer $TWIN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://portal.supplier.example.com/invoices",
"prompt": "list every invoice issued this month with its number, date and total",
"success": { "kind": "extracted" },
"account": "acme-supplies",
"persistSession": true,
"callbackUrl": "https://ops.example.com/hooks/twin",
"callbackSecret": "…"
}'
# → 202 { "jobId": "…" } poll GET /api/v1/jobs/{id}What this call does
- The connect link is single-use, scoped to one host, and expires in about thirty minutes — only the resulting session is kept, never the password.
- `account` is the label that keys both the credential and the session, so thirty-eight portals are thirty-eight labels against one API key.
- The job settles credits on its first terminal status; POST /api/v1/jobs/{id}/cancel aborts a running job with a full refund.
- The completion webhook is HMAC-signed with `callbackSecret`, so your endpoint can verify the payload came from Twin.
What it costs
Priced per action, not per seat.
A portal sweep is priced per run, not per portal or per seat. Adding the thirty-ninth supplier costs one more run, not a new licence.
| Action | Credits | What you get |
|---|---|---|
| POST /jobs — an async sweep | 10 / portal run | Submitted, run in the background, settled on its terminal status. |
| POST /run — a synchronous check | 10 / run | For a single portal you want an answer from right now. |
| POST /extract — read the rows | metered | Metered: the higher of a flat floor and the actual model cost. Deterministic `rowFields` reading incurs no model cost at all. |
| POST /skills/{name}/run — a compiled sweep | 1 / portal run | Once a portal’s flow is compiled, its daily sweep replays at the flat replay rate. |
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.
- Connecting an account is not itself a billed action — you pay for the runs that use the session, not for storing it. GET /api/v1/pricing serves the live 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.
A connected session is not permanent
Portals expire sessions, and some force a fresh sign-in on a schedule no automation can satisfy. When that happens the run returns a connect-required result with a fresh link rather than pretending — which means somebody has to click it. Budget for that on the strictest portals.
It does not hold your suppliers’ passwords for them
Where the login belongs to someone whose credentials you should not possess, the connect link is the whole point: they sign in, Twin captures the session, and no password enters your systems. If you were hoping to store their credentials centrally, this deliberately does not do that.
It does not normalize what the portals give you
Twin returns what the page says — the numbers, the rows, the document. Mapping forty suppliers’ idea of "invoice total" onto your chart of accounts is your ERP’s job, not the browser layer’s.
Under the hood
The primitives this runs on.
Nothing here is specific to this problem — the same mechanisms carry every solution on the site.
Credential vault
A write-only per-tenant vault: a stored secret is referenced in a goal as {{secret:NAME}}, resolved inside the browser at fill time, and redacted from every step, frame and log.
Read the mechanism — Credential vaultHuman-in-the-loop handoff
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.
Read the mechanism — Human-in-the-loop handoffDeterministic replay
A successful run is minimized into a named, versioned skill — an ordered action path with its variable parts lifted out — and replaying it is a program, not a prompt.
Read the mechanism — Deterministic replayEgress, geo & locale
A single `country` field sets the residential exit AND the browser locale and timezone, so a site cannot serve a different language than the exit you asked for — or bring your own proxy and keep both.
Read the mechanism — Egress, geo & localeOver MCP, the same work is these tools
connect_accountGet 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).
list_connectionsCheck whether the user finished a connect link you sent them (pending | active | connected | failed | expired) before re-running the task.
list_accountsList the stored logins you already have as { label, host, emailPreview } — masked, never a credential. Pass a returned label as account on run_goal/run_skill and that login plus its own saved session are applied automatically. Nothing back for a host? Use connect_account to have the user sign in once.
submit_runSubmit 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.
extractRead an authorized page and return structured JSON matching the fields or JSON schema you request.
FAQ
Supplier and customer portals — common questions.
What if a portal refuses to let an agent sign in at all?
Can we keep several logins for the same supplier?
How do we get the result into our ERP?
Is any of this stored where a supplier could see it?
Related
Other problems this layer solves.
Reporting from behind a login
Analytics and data-platform teams whose sources are vendor dashboards rather than databases.
Systems with no API
Ops, finance and platform teams blocked by a vendor system that offers no programmatic access.
When the site turns you away
Teams whose authorized automation is being turned away by bot defences on sites they are entitled to use.
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.