Guide — the developer loop

The whole workflow end-to-end with real request and response shapes: get a key, run a goal, cut the cost with the semantic cache, extract structured data, watch runs live, resume human-in-the-loop pauses, hand end-users a sign-in link, and read the live rate card. Endpoint details live in the API reference.

1 · Get a key

Create a per-tenant API key in the dashboard under Keys & Secrets — it starts with ab_live_ and is shown once. Every call is a Bearer request; the tenant is always derived from the key, never from the body. Each run is scoped to your tenant, metered in credits, and written to the audit log.

2 · First run — POST /run

A run is a url (the authorization signal), a natural-language prompt, and a structured success condition the engine must satisfy. Synchronous, ~10 credits; the response carries the verified action path and exactly what was charged.

Run a goalbash
curl https://twin-browser.com/api/v1/run \
  -H "Authorization: Bearer ab_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://app.acme.com/invoices",
    "prompt": "export last month as CSV",
    "success": { "kind": "urlIncludes", "value": "/exports" }
  }'

# → 200 { "success": true, "path": [ … ], "credits_charged": 10, "engineRev": "…" }
#
# success is a structured condition — one of:
#   { "kind": "urlIncludes", "value": "…" }   { "kind": "textVisible", "value": "…" }
#   { "kind": "statusText", "match": "…" }    { "kind": "extracted" }
#   { "kind": "allOf" | "anyOf", "conditions": [ … ] }

A sign-in run that hits a 2FA or approval wall parks by default instead of failing — resume it with a code, drive the browser yourself, or hand the sign-in to your end-user. Set hitl:false to opt out and get a plain miss.

Paused runs & result codesbash
# A sign-in run that hits a 2FA/approval wall PARKS by default (hitl) instead of failing:
# → 200 { "status": "paused", "sessionId": "…", "reason": "…", "challenge": "sms-code" }
#
# Three ways forward from a paused run:
#   1. POST /api/v1/runs/{id}/resume   { "sessionId": "…", "code": "123456" }
#   2. Watch + drive it yourself:
#        GET  /api/v1/runs/{id}/stream          (SSE screencast, free)
#        POST /api/v1/runs/{id}/input           { "event": { "kind": "click", "x": 0.42, "y": 0.61 } }
#        (coordinates are 0..1 viewport fractions; kinds: click | fill | key | scroll | goto)
#   3. POST /api/v1/connect/sessions — send your END-USER a link to sign in by hand.
#
# Two result codes tell you what a run needs:
#   "code": "credential_missing" + missingSecret → store it via POST /secrets, or use a connect link
#   "code": "connect_required"  + connectUrl     → a ready one-time link; hand it to the user

3 · Dispatch vs. run — the semantic cache

POST /run always plans live. POST /dispatch takes the same body but first vector-matches your request against the skills already compiled for that host — a HIT replays the verified path deterministically with no LLM in the loop. Default to dispatch for anything you will ask twice.

dispatch — hit or compilebash
# Same body as /run — but fronted by the semantic cache:
curl https://twin-browser.com/api/v1/dispatch \
  -H "Authorization: Bearer ab_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://app.acme.com/invoices",
    "prompt": "download last month'"'"'s invoices as a CSV",
    "success": { "kind": "urlIncludes", "value": "/exports" }
  }'

# HIT  → your re-phrased request matches an already-compiled skill for that host:
#        deterministic blind replay, no LLM              (~2 credits)
# MISS → live discovery + minimize, then cached with its embedding
#        so the NEXT similar request is a hit            (~10 credits)
# The verified action path is returned in BOTH modes.

# Or manage skills explicitly:
#   POST /api/v1/skills                  { "target": "…", "goal": "…", "as": "acme-export" }   (~50 cr)
#   POST /api/v1/skills/acme-export/run  → deterministic replay, no LLM                        (~1 cr)
#   GET  /api/v1/skills                  → list compiled skills

4 · Extract — structured JSON out of any page

POST /extract reads a page and returns JSON matching the fields or schema you ask for (~5-credit floor). When a per-host template matches, extraction is deterministic — zero LLM, zero metered cost.

extract — fields, schema, or templatebash
curl https://twin-browser.com/api/v1/extract \
  -H "Authorization: Bearer ab_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://news.ycombinator.com",
    "fields": ["title", "points", "url"]
  }'

# → 200 { …structured result…, "credits_charged": 5 }
#
# Describe the output with "fields" (flat list) or "schema" (JSON-schema-like) —
# or neither: a per-host extraction template (see GET /api/v1/templates) matches
# many URLs and returns structured data deterministically, zero LLM, 0 metered cost.
#   "template": "name" forces one; "template": false disables (then schema/fields required)
#   "waitMs": 2000 adds settle time for JS-hydrated pages

5 · Watch it work — live view, HITL, async jobs

POST /live streams the browser back as Server-Sent Events while the run executes, and emits an await-user step when a second factor needs you. For background work, submit the same body to /jobs and poll, stream, or receive an HMAC-signed webhook.

live view & jobsbash
# Run a goal and stream the LIVE browser view back as Server-Sent Events:
curl -N https://twin-browser.com/api/v1/live \
  -H "Authorization: Bearer ab_live_…" \
  -H "Content-Type: application/json" \
  -d '{ "url": "…", "prompt": "…", "success": { "kind": "extracted" } }'

# event: meta    → { "runId": "…" }
# event: frame   → JPEG frames of the browser
# event: step    → on a 2FA wall: { "type": "await-user", "token": "…", "challenge": "sms-code" }
#                  answer with POST /api/v1/live/resume { "token": "…", "code": "123456" }
# event: result  → terminal result; a durable video is then at GET /api/v1/runs/{id}/video

# Prefer fire-and-forget? Submit the same body as an async job:
#   POST /api/v1/jobs → 202 { "jobId": "…" }     (optional callbackUrl + callbackSecret,
#   GET  /api/v1/jobs/{id}                        HMAC-signed completion webhook)
#   GET  /api/v1/jobs/{id}/stream                 (SSE status frames instead of polling)
#   POST /api/v1/jobs/{id}/cancel                 (full refund of reserved credits)

6 · End-user logins — connect links

When the credential belongs to someone whose password you should not hold — or a site blocks automated login outright — mint a connect link. The end-user signs in by hand once; Twin Browser captures the session; every later run is already logged in. OAuth-shaped, for sites without OAuth.

connect/sessionsbash
# Mint a one-time link your END-USER opens to sign in by hand — password, 2FA,
# CAPTCHA — inside a Twin-Browser-hosted browser. The session is captured; the
# password is never stored.
curl https://twin-browser.com/api/v1/connect/sessions \
  -H "Authorization: Bearer ab_live_…" \
  -H "Content-Type: application/json" \
  -d '{ "host": "linkedin.com", "account": "work", "redirectUri": "https://yourapp.com/done" }'

# → 201 { "id": "…", "url": "https://…", "expiresAt": "…", "ttlMinutes": 30 }
#   (the url is shown once; the link is scoped to that one host)

# Check whether the user finished before re-running:
#   GET /api/v1/connect/sessions
#   → { "sessions": [{ "id", "host", "account", "status", … }] }
#     status: pending | active | connected | failed | expired | cancelled

# Re-run the original task afterwards → "sessionRestored": true — already logged in.

For accounts you do own, store the credential once in the write-only secrets vault and reference it by name — the value never leaves the vault except at fill-time inside the browser. Discover the logins you already hold with GET /accounts.

secrets & accountsbash
# Store credentials once, reference them by name — values are write-only,
# encrypted at rest, and redacted from every log and stream:
curl https://twin-browser.com/api/v1/secrets \
  -H "Authorization: Bearer ab_live_…" \
  -H "Content-Type: application/json" \
  -d '{ "name": "ACME_PASSWORD", "value": "…" }'

# Then in a prompt: "sign in as jo@acme.com with {{secret:ACME_PASSWORD}}"

# Which logins do I already have? (metadata only — never a credential)
#   GET /api/v1/accounts?host=acme.com
#   → { "accounts": [{ "label": "work", "host": "acme.com", "emailPreview": "j…@acme.com" }] }
# Pass a label as "account" on any run and that login + its saved session apply.

7 · Beyond curl — MCP, extension, adapters

Everything above is plain REST, but the glue code is already written: npx -y twin-browser-mcp exposes 37 tools (dispatch, runs, skills, extract, connect links, verification resume) natively to Cursor, Claude Desktop, Claude Code, or Cline — MCP setup. For popup-OAuth sign-ins, the capture extension ships a session from the user's own browser (cookies only, never a password). LangChain / AutoGen adapters come from makeTwinBrowserTools(). The full machine-readable surface is GET /api/v1/openapi (public, no key).

8 · What it costs — the live rate card

Flat per-action credit floors plus metered passthrough — the same numbers your Settings → Billing tab bills against, served live from the platform (never a stale copy). Machine-readable at GET /api/v1/pricing.

Rate card

1,000 credits / USD× 1 metered multiplierregion US

A paid action bills the higher of its flat floor or its metered LLM cost — so a cached replay costs the floor and nothing more, and a heavy cold run bills what it actually consumed. Every rate below is live from the platform, never a copy.

Cache verdict

Cache verdict flat credit floors
ActionFloor≈ USD
Cache hit · no LLM2 cr$0.002
Adapt · shared scaffold5 cr$0.005
Cache miss · cold10 cr$0.010
Skill replay1 cr$0.001
Compile a skill50 cr$0.050

Run surface

Run surface flat credit floors
ActionFloor≈ USD
Run10 cr$0.010
Live run10 cr$0.010
Async job10 cr$0.010

Data surface

Data surface flat credit floors
ActionFloor≈ USD
Search3 cr$0.003
Map2 cr$0.002
Crawl · per page3 cr$0.003
Deep search3 cr$0.003

Metered token rates · credits per 1M tokens

Metered per-model credit rates per million tokens
ModelInputOutput
claude-fable-511,625 cr58,125 cr
claude-fable-5-111,625 cr58,125 cr
claude-haiku-4-51,162.5 cr5,812.5 cr
claude-opus-55,812.5 cr29,062.5 cr
claude-opus-5-54,650 cr23,250 cr
claude-sonnet-52,325 cr11,625 cr
claude-sonnet-5-52,325 cr11,625 cr
gemini-3-1-flash-lite290.625 cr1,743.75 cr
gemini-3-1-pro-preview2,325 cr13,950 cr
gemini-3-5-flash-lite348.75 cr2,906.25 cr
gemini-3-7-flash871.875 cr4,359.375 cr
gemini-3-8-flash871.875 cr4,359.375 cr
gemini-3-8-flash-lite-tts581.25 cr6,975 cr
gemini-3-8-flash-tts581.25 cr10,462.5 cr
gpt-5-42,906.25 cr17,437.5 cr
gpt-5-4-mini871.875 cr5,231.25 cr
gpt-5-4-nano232.5 cr1,453.125 cr
gpt-5-55,812.5 cr34,875 cr
gpt-5-6-luna232.5 cr1,395 cr
gpt-5-6-terra2,325 cr13,950 cr
gpt-5-mini290.625 cr2,325 cr
gpt-5-nano58.125 cr465 cr
gpt-6-1-sol2,325 cr11,625 cr
gpt-6-astra11,625 cr58,125 cr
gpt-6-luna116.25 cr581.25 cr
gpt-6-sol2,325 cr11,625 cr
inception-mercury-2290.625 cr871.875 cr
inception-mercury-2-546.5 cr174.375 cr
inception-mercury-edit-2290.625 cr871.875 cr
kimi-k2-61,104.375 cr4,650 cr
kimi-k2-7-code1,104.375 cr4,650 cr
kimi-k33,487.5 cr17,437.5 cr
muse-spark-1-21,453.125 cr4,940.625 cr
muse-spark-1-31,453.125 cr4,940.625 cr
together-deepseek-v4-1-flash348.75 cr1,395 cr
together-deepseek-v4-flash162.75 cr325.5 cr
together-deepseek-v4-pro1,534.5 cr4,603.5 cr
together-glm-5-21,627.5 cr5,115 cr
together-glm-5-31,627.5 cr5,115 cr
together-glm-5-3-flash174.375 cr581.25 cr
together-kimi-k33,487.5 cr17,437.5 cr
together-minimax-m3348.75 cr1,395 cr
zai-glm-5-21,627.5 cr5,115 cr
zai-glm-5-31,627.5 cr5,115 cr
zai-glm-5-3-flash174.375 cr581.25 cr

Reselling. Twin meters every call at cost-plus and rolls it into your tenant's credit balance, so you can wrap the API under your own brand and price — POST /api/v1/tenants provisions subtenants with per-subtenant billing modes and scoped keys. Per-subtenant usage lives on your Billing tab.