API reference

The complete REST surface — 40 paths, 50 operations — rendered from the same OpenAPI 3.1 spec served at GET /api/v1/openapi, so this page and the machine contract cannot drift.

  • Base URLhttps://twin-browser.com/api/v1
  • AuthAuthorization: Bearer ab_live_… on every call — the tenant is always derived from the key, never from the body. Keys are minted in the dashboard under Keys & Secrets.
  • Content typeapplication/json requests; JSON responses except the SSE streams (text/event-stream) and binary video/screenshot bodies.
  • MeteringPrepaid credits, reserved at start and settled on the terminal result (failures refund). Every response reports credits_charged; rates are live at GET /pricing.

Runs & live control

Execute a goal in a real browser — synchronously, streamed live over SSE, or resumed after a human-in-the-loop pause. A paused run can be resumed with a code, driven directly with input events, or handed to an end-user via a connect link.

POST/run

Run a goal synchronously on an authorized target (~10 credits).

A SIGN-IN run (credentials/account/login-worded goal) that hits a 2FA/approval wall it can't auto-resolve PARKS by default: the 200 body is { status:"paused", sessionId, reason, challenge } instead of a completed run — resume it via POST /runs/{id}/resume (or hand a texted/typed code via the streaming /live/resume). Set hitl:false to opt out and get a plain needs-human miss instead. A free-text goal in the { target, goal } shape is a 400 (goal is a named/compiled skill; use { url, prompt, success } for free text).

Request body — AdHocRun
  • urlrequiredstring (uri)Authorized target URL (the authorization signal).
  • promptrequiredstringThe goal in natural language.
  • successrequiredobjectStructured success condition: one of {kind:"statusText",match}, {kind:"urlIncludes",value}, {kind:"textVisible",value}, {kind:"extracted"}, {kind:"allOf",conditions:[...]}, {kind:"anyOf",conditions:[...]}.
  • proxystringOptional outbound proxy URL.
  • proxyRotatebooleanAnti-ban: force per-request proxy rotation (anonymous scraping).
  • sessionKeystringAnti-ban: explicit override for the account stickiness key.
  • accountstringAnti-ban: account/credential label (multiple sessions per host).
  • platformProxybooleanAnti-ban: set false to opt out of the platform sticky-proxy default.
  • persistSessionbooleanAnti-ban: persist/resume browser session state (cookies) for this account.
  • ignoreSessionbooleanForce a FRESH login: skip restoring any stored session jar for this run (a new authenticated jar still persists on success). Use to re-establish a stale/expired session on demand.
  • stealthbooleanRun in a full desktop browser profile (real Chrome, human-calibrated timing, residential egress) so an authorized run is not locked out by a bot check. Requires a Pro/Enterprise plan (else 403 code:"plan_required") AND "authorized": true; adds a per-success surcharge. Most tasks do not need it — reach for it when a run is actually turned away. POST /agent is a deprecated alias for a run with this set.
  • authorizedbooleanAuthorization attestation, REQUIRED when stealth:true — assert you are authorized to access this target and accept the stealth terms. Recorded as the durable authorization artifact.
  • hitlbooleanHuman-in-the-loop: park the run (resumable) on a 2FA/approval wall it can't auto-resolve, returning { status:"paused", sessionId } instead of failing. A sign-in intent (credentials/account/login-worded goal) PARKS BY DEFAULT — set false to opt out. Resume via POST /runs/{id}/resume.
Request body — NamedRun
  • targetrequiredstringAuthorized target.
  • goalrequiredstringNamed goal.
  • proxystringOptional outbound proxy URL.
  • proxyRotatebooleanAnti-ban: force per-request proxy rotation (anonymous scraping).
  • sessionKeystringAnti-ban: explicit override for the account stickiness key.
  • accountstringAnti-ban: account/credential label (multiple sessions per host).
  • platformProxybooleanAnti-ban: set false to opt out of the platform sticky-proxy default.
  • persistSessionbooleanAnti-ban: persist/resume browser session state (cookies) for this account.
  • ignoreSessionbooleanForce a FRESH login: skip restoring any stored session jar for this run (a new authenticated jar still persists on success). Use to re-establish a stale/expired session on demand.
  • stealthbooleanRun in a full desktop browser profile (real Chrome, human-calibrated timing, residential egress) so an authorized run is not locked out by a bot check. Requires a Pro/Enterprise plan (else 403 code:"plan_required") AND "authorized": true; adds a per-success surcharge. Most tasks do not need it — reach for it when a run is actually turned away. POST /agent is a deprecated alias for a run with this set.
  • authorizedbooleanAuthorization attestation, REQUIRED when stealth:true — assert you are authorized to access this target and accept the stealth terms. Recorded as the durable authorization artifact.
  • hitlbooleanHuman-in-the-loop: park the run (resumable) on a 2FA/approval wall it can't auto-resolve, returning { status:"paused", sessionId } instead of failing. A sign-in intent (credentials/account/login-worded goal) PARKS BY DEFAULT — set false to opt out. Resume via POST /runs/{id}/resume.
  • 200Run result, OR { status:"paused", sessionId, reason, challenge, url, challengeUrl } when a sign-in run parked on a verification wall. From a PAUSED run there are three ways forward: POST /runs/{id}/resume with a code (a texted/typed code or an approval you completed out of band); POST /runs/{id}/input to click/type on the page yourself (watch it via GET /runs/{id}/stream); or POST /connect/sessions to get a link your END-USER opens to sign in by hand — the only one that works on sites which block automated login outright. A finished run also reports `engineRev`, and `failures[]` (each recovered/failed action with its error) when anything had to be retried or repaired. If the run needed a credential you have not stored, the result carries `code: "credential_missing"` with `missingSecret` — store it via POST /secrets, or send the user through POST /connect/sessions. If the run hit a wall automation cannot pass (a score-based anti-bot system, or a challenge that persisted), the result carries `code: "connect_required"` with a READY one-time `connectUrl` (+ `connectId`, `connectExpiresAt`) — hand it to your end-user, they sign in by hand once, and every later run restores the captured session. Opt out of the auto-mint with `connect:false` and mint your own via POST /connect/sessions. On hosts with repeated score-wall blocks the run may short-circuit to this response WITHOUT executing (reserve refunded, `policy.fastpath: "score-wall"`) — that is the policy engine saving you the solver cost.
  • 400Malformed body (e.g. a free-text goal in the named { target, goal } shape, or a bad success spec).
  • 401Missing/invalid API key.
  • 402Insufficient credits.
  • 403Unauthorized target.

POST/live

Run a goal and stream the LIVE browser view back as Server-Sent Events (~10 credits).

Same body as /run, but the response is a live SSE screencast of the browser: an `event: meta` frame with { runId }, then `event: frame` JPEG frames, then a terminal `event: result`. The run is recorded, so a durable video is replayable afterwards via GET /runs/{id}/video. Metered like /run (higher-of the ~10-credit floor vs LLM COGS); settles once on the terminal frame or disconnect (failed/no-result → full refund).

Request body — AdHocRun
  • urlrequiredstring (uri)Authorized target URL (the authorization signal).
  • promptrequiredstringThe goal in natural language.
  • successrequiredobjectStructured success condition: one of {kind:"statusText",match}, {kind:"urlIncludes",value}, {kind:"textVisible",value}, {kind:"extracted"}, {kind:"allOf",conditions:[...]}, {kind:"anyOf",conditions:[...]}.
  • proxystringOptional outbound proxy URL.
  • proxyRotatebooleanAnti-ban: force per-request proxy rotation (anonymous scraping).
  • sessionKeystringAnti-ban: explicit override for the account stickiness key.
  • accountstringAnti-ban: account/credential label (multiple sessions per host).
  • platformProxybooleanAnti-ban: set false to opt out of the platform sticky-proxy default.
  • persistSessionbooleanAnti-ban: persist/resume browser session state (cookies) for this account.
  • ignoreSessionbooleanForce a FRESH login: skip restoring any stored session jar for this run (a new authenticated jar still persists on success). Use to re-establish a stale/expired session on demand.
  • stealthbooleanRun in a full desktop browser profile (real Chrome, human-calibrated timing, residential egress) so an authorized run is not locked out by a bot check. Requires a Pro/Enterprise plan (else 403 code:"plan_required") AND "authorized": true; adds a per-success surcharge. Most tasks do not need it — reach for it when a run is actually turned away. POST /agent is a deprecated alias for a run with this set.
  • authorizedbooleanAuthorization attestation, REQUIRED when stealth:true — assert you are authorized to access this target and accept the stealth terms. Recorded as the durable authorization artifact.
  • hitlbooleanHuman-in-the-loop: park the run (resumable) on a 2FA/approval wall it can't auto-resolve, returning { status:"paused", sessionId } instead of failing. A sign-in intent (credentials/account/login-worded goal) PARKS BY DEFAULT — set false to opt out. Resume via POST /runs/{id}/resume.
Request body — NamedRun
  • targetrequiredstringAuthorized target.
  • goalrequiredstringNamed goal.
  • proxystringOptional outbound proxy URL.
  • proxyRotatebooleanAnti-ban: force per-request proxy rotation (anonymous scraping).
  • sessionKeystringAnti-ban: explicit override for the account stickiness key.
  • accountstringAnti-ban: account/credential label (multiple sessions per host).
  • platformProxybooleanAnti-ban: set false to opt out of the platform sticky-proxy default.
  • persistSessionbooleanAnti-ban: persist/resume browser session state (cookies) for this account.
  • ignoreSessionbooleanForce a FRESH login: skip restoring any stored session jar for this run (a new authenticated jar still persists on success). Use to re-establish a stale/expired session on demand.
  • stealthbooleanRun in a full desktop browser profile (real Chrome, human-calibrated timing, residential egress) so an authorized run is not locked out by a bot check. Requires a Pro/Enterprise plan (else 403 code:"plan_required") AND "authorized": true; adds a per-success surcharge. Most tasks do not need it — reach for it when a run is actually turned away. POST /agent is a deprecated alias for a run with this set.
  • authorizedbooleanAuthorization attestation, REQUIRED when stealth:true — assert you are authorized to access this target and accept the stealth terms. Recorded as the durable authorization artifact.
  • hitlbooleanHuman-in-the-loop: park the run (resumable) on a 2FA/approval wall it can't auto-resolve, returning { status:"paused", sessionId } instead of failing. A sign-in intent (credentials/account/login-worded goal) PARKS BY DEFAULT — set false to opt out. Resume via POST /runs/{id}/resume.
  • 200SSE stream (text/event-stream): meta → frame* → result.
  • 401Missing/invalid API key.
  • 402Insufficient credits.
  • 403Unauthorized target.

POST/live/resume

Hand a 2FA code (or cancel) to a live run holding on an await-user step.

When a live /live run hits a login second factor it cannot auto-resolve, its SSE emits an `event: step` with `type:"await-user"` carrying a `token`, a `challenge` (sms-code | totp | email-code | app-approval) and a human `reason`. POST that `token` here with the user-supplied `code` (or `cancel:true`) to unblock the run — it continues on the SAME open SSE, no new connection. Not billed here (the parent run owns metering).

Request body
  • tokenrequiredstringThe token from the await-user SSE event.
  • codestringThe verification code (required unless cancel:true).
  • cancelbooleanAbandon the wait instead of submitting a code.
  • 200{ ok: true } — the run was unblocked.
  • 400Missing token, or no code and no cancel.
  • 401Missing/invalid API key.
  • 409No run is awaiting this token (timed out / already resumed).

GET/runs

List this tenant's runs (newest first, paginated).

Parameters
  • limitinteger · query · default 50
  • offsetinteger · query · default 0
  • mode"run" | "job" | "skill" | "compile" | "adapt…
  • status"running" | "complete" | "failed" | "paused"…
  • 200Paginated list of run summaries.
  • 401Missing/invalid API key.

GET/runs/{id}

Read a single run: status, success, masked action path, credits, video, timestamps.

Parameters
  • idrequiredstring · path
  • 200Run detail (secret tokens in the path are redacted).
  • 401Missing/invalid API key.
  • 404No such run for this tenant.

POST/runs/{id}/resume

Continue a paused (HITL) sync run after an out-of-band approval.

When a sync /run pauses for human action it returns `{ status:"paused", sessionId }`. Once the step is completed out of band (e.g. a phone approval), POST the `sessionId` here to finish the run on the same parked browser session. Charges the flat run floor on completion; still-paused → refunded and returns paused again. This is the continuation channel — a texted/typed CODE goes to POST /live/resume on the streaming path instead.

Parameters
  • idrequiredstring · path
Request body
  • sessionIdrequiredstringThe sessionId from the paused run response.
  • codestringA texted/typed verification code, when the pause was a code-entry wall.
  • secretsobjectCredentials for a run that paused with code:"credential_missing", e.g. { "email": "...", "password": "..." }. Merged into the PARKED session's vault so the same browser continues — no restart, no lost session state. Values are redacted from every frame/step/log, and are vaulted for next time unless the tenant has store_secrets off. Use this when YOUR app can collect the credential; when the password belongs to someone whose credentials you should not hold, mint a link with POST /connect/sessions instead.
  • 200{ status:"complete", success, steps, path } or { status:"paused", sessionId, reason }.
  • 401Missing/invalid API key.
  • 402Insufficient credits.
  • 404No such run for this tenant.
  • 409Run is not paused, or the parked session is gone.

POST/runs/{id}/input

Drive a paused run's browser by hand (click / type / key / scroll / goto).

The companion to /runs/{id}/resume. Resume hands back a verification CODE; this hands back an INTERACTION, so a person (or an agent reading a screenshot) can finish a step the agent could not — typically a sign-in on a site that blocks automated login, or one you hold no credential for. Coordinates are FRACTIONS of the viewport (0..1 from the top-left), not pixels: they are hit-tested server-side against the observed element map, so a click lands correctly whatever the viewer's size. Typed text is redacted from the stream and never stored. There is no "done" call — the run re-checks the page after every interaction and continues by itself once the block clears.

Parameters
  • idrequiredstring · path
Request body
  • eventrequiredobject{ kind:"click", x, y } | { kind:"fill", x, y, text } | { kind:"key", key } | { kind:"scroll" } | { kind:"goto", url }
  • 200{ ok: true, url } — the page URL after the interaction.
  • 400Invalid interaction shape.
  • 401Missing/invalid API key.
  • 404No such run for this tenant.
  • 409Run is not paused, or the parked session expired.

GET/runs/{id}/stream

Watch a paused run's browser live (SSE screencast) so you can drive it.

Server-sent events: `meta`, then `frame` (base64 JPEG) until you disconnect. Drives no agent and costs no credits — it mirrors a browser that is already open and waiting. Pair with POST /runs/{id}/input.

Parameters
  • idrequiredstring · path
  • 200text/event-stream: event: meta | frame | error.
  • 401Missing/invalid API key.
  • 404No such run for this tenant.
  • 409Run is not paused, or the parked session expired.

GET/runs/{id}/video

Stream a run's recorded video (only if the run was created with record:true).

Returns the recorded browser video bytes for a run that opted into recording. Served from durable storage (lazily persisted from the engine on first view). 404 when the run is not this tenant's, was not recorded, or the recording is no longer available.

Parameters
  • idrequiredstring · path
  • 200Video bytes (video/*).
  • 401Missing/invalid API key.
  • 404No such run, no recording, or recording expired.

POST/agentdeprecated

DEPRECATED alias for POST /run with {"stealth":true} — prefer that. Forces the full anti-detection stack.

DEPRECATED — a thin alias for POST /run with {"stealth":true,"humanize":true,"platformProxy":true}. Prefer calling /run directly with "stealth": true. In ONE call it forces stealth-fleet routing, human-mimicry timing, and sticky residential egress. Requires the same REQUIRED "authorized": true attestation and Pro/Enterprise plan as any stealth /run — now enforced by /run itself (the single source of truth), so /run {stealth:true} and /agent gate identically. Billed like a stealth /run: higher-of(~10-credit floor, LLM COGS) plus a per-SUCCESS stealth surcharge.

  • 200Run result (same shape as /run).
  • 400Missing authorization attestation or malformed body.
  • 401Missing/invalid API key.
  • 402Insufficient credits.
  • 403Plan gate — a Pro subscription is required (code:"plan_required").
  • 501Stealth fleet not configured (fail closed — never billed as premium while run honestly).

Semantic cache & skills

The cost curve: dispatch fuzzy-matches a goal against skills already compiled for the host (HIT → deterministic replay, no LLM), skills are compiled and replayed explicitly, and the library/templates expose what already exists.

POST/dispatch

Semantic cache: match to a compiled skill (hit) or compile + cache (miss).

Request body
  • urlrequiredstring (uri)Authorized target URL (the authorization signal).
  • promptrequiredstringThe goal in natural language.
  • successrequiredobjectStructured success condition: one of {kind:"statusText",match}, {kind:"urlIncludes",value}, {kind:"textVisible",value}, {kind:"extracted"}, {kind:"allOf",conditions:[...]}, {kind:"anyOf",conditions:[...]}.
  • proxystringOptional outbound proxy URL.
  • proxyRotatebooleanAnti-ban: force per-request proxy rotation (anonymous scraping).
  • sessionKeystringAnti-ban: explicit override for the account stickiness key.
  • accountstringAnti-ban: account/credential label (multiple sessions per host).
  • platformProxybooleanAnti-ban: set false to opt out of the platform sticky-proxy default.
  • persistSessionbooleanAnti-ban: persist/resume browser session state (cookies) for this account.
  • ignoreSessionbooleanForce a FRESH login: skip restoring any stored session jar for this run (a new authenticated jar still persists on success). Use to re-establish a stale/expired session on demand.
  • stealthbooleanRun in a full desktop browser profile (real Chrome, human-calibrated timing, residential egress) so an authorized run is not locked out by a bot check. Requires a Pro/Enterprise plan (else 403 code:"plan_required") AND "authorized": true; adds a per-success surcharge. Most tasks do not need it — reach for it when a run is actually turned away. POST /agent is a deprecated alias for a run with this set.
  • authorizedbooleanAuthorization attestation, REQUIRED when stealth:true — assert you are authorized to access this target and accept the stealth terms. Recorded as the durable authorization artifact.
  • hitlbooleanHuman-in-the-loop: park the run (resumable) on a 2FA/approval wall it can't auto-resolve, returning { status:"paused", sessionId } instead of failing. A sign-in intent (credentials/account/login-worded goal) PARKS BY DEFAULT — set false to opt out. Resume via POST /runs/{id}/resume.
  • 200Dispatch result (cache hit or compiled).

GET/skills

List this tenant's compiled skills.

  • 200Array of skills.

POST/skills

Compile a skill — discover + minimize (~50 credits).

Request body
  • targetrequiredstring (uri)
  • goalrequiredstring
  • asstringOptional skill name.
  • 200Compiled skill descriptor.

POST/skills/{name}/run

Deterministically replay a compiled skill — no LLM (~1 credit).

Parameters
  • namerequiredstring · path
  • 200Replay result.

GET/library

Search the cross-tenant shared skill corpus — metadata only (free).

Parameters
  • qstring · query
  • 200Matching skills (metadata).

GET/cache/stats

Semantic-cache analytics for this tenant — hit rate + credits saved (free, no charge).

Reduces this tenant's runs over a rolling window into per-mode counts (skill = cache HIT, adapt = cross-tenant ADAPT, compile = MISS, run/job = uncached), a cache hit_rate = (skill+adapt)/(skill+adapt+compile), total credits_charged, and estimated_credits_saved (credits the cache HITs/ADAPTs saved versus cold compiles). Read-only metadata lookup — does not reserve or charge credits.

Parameters
  • daysinteger · query · default 30Rolling window size in days (clamped 1..365).
  • 200Cache analytics: { window_days, totals:{ skill, adapt, compile, run, job, other, total }, hit_rate, credits_charged, estimated_credits_saved }.
  • 401Missing/invalid API key.

GET/templates

List the extraction-template catalog used by /extract and /etl (public, no key).

Per-host + generic-metadata templates that extract structured data deterministically (zero LLM). Optional ?url= reports which template matches that URL.

Parameters
  • urlstring (uri) · queryIf given, also report which template matches this URL.
  • 200Catalog: { templates:[{ name, label, generic }], url?, matched? }.

Async jobs

Fire-and-forget execution: submit the same body as /run, get a jobId back immediately, then poll, stream status frames over SSE, or receive an HMAC-signed completion webhook. Cancelling refunds the reserved credits.

GET/jobs

List this tenant's recent async jobs.

  • 200Array of jobs.

POST/jobs

Submit a goal as an async job (~10 credits). Returns 202 { jobId }.

Optionally pass callbackUrl/callbackSecret to receive an HMAC-signed result webhook on completion. Like /run, a SIGN-IN job that hits a 2FA/approval wall PARKS by default — GET /jobs/{id} then reports status:"paused" with a sessionId (resume via POST /runs/{id}/resume); set hitl:false to opt out.

  • 202Job accepted; poll GET /jobs/{id}.
  • 402Insufficient credits.

GET/jobs/{id}

Poll an async job; settles credits on first terminal status.

Parameters
  • idrequiredstring · path
  • 200Job status.
  • 404No such job.

GET/jobs/{id}/stream

Stream an async job's status as Server-Sent Events (an alternative to polling).

Emits `event: status` frames ({ jobId, status, success, steps, error }) as the engine transitions the job, then a final authoritative frame, then closes. Billing settles once on close (complete → charge, failed/cancelled → refund); a client that disconnects early settles on its next poll.

Parameters
  • idrequiredstring · path
  • 200SSE stream (text/event-stream) of status frames.
  • 401Missing/invalid API key.
  • 404No such job.

POST/jobs/{id}/cancel

Cancel a running async job — full refund of the reserved credits.

Best-effort aborts the in-flight engine work, then flips the run to cancelled and refunds the reservation in full. Idempotent: cancelling an already-terminal job is a no-op that returns its current state with cancelled:false.

Parameters
  • idrequiredstring · path
  • 200Cancellation result: { jobId, status, cancelled }.
  • 401Missing/invalid API key.
  • 404No such job.

Data tools

Request shapes that return data rather than drive a flow: blended search, structured extraction, screenshots, URL discovery, whole-site crawling, and the ETL pipeline into a semantically queryable store.

POST/search

Web search across blended sources (Brave web/discussions/news/faq + Hacker News, deduped, each result tagged with source). Shallow (default): sync ranked results (~3 cr) + optional fetch (+1 cr/page). Deep (depth:"deep"): async job, scrapes top N (3 cr + 5 cr/page), returns 202 { jobId }.

Request body
  • queryrequiredstringThe search query.
  • depth"shallow" | "deep"shallow = synchronous ranked results; deep = async job that scrapes the top N matches for content (returns 202 { jobId }; poll GET /jobs/{id}).
  • countintegerNumber of ranked results. Results blend Brave (web + discussions + news + faq clusters) and Hacker News, deduped by URL; each result carries a `source` field (web | discussions | news | faq | hackernews).
  • fetchContentbooleanShallow only: also load + clean the content of the top results.
  • topNintegerHow many top results to fetch (shallow) / scrape (deep).
  • proxystringOptional outbound proxy URL for content fetches.
  • 200Shallow: ranked results (+ cleaned content when requested) and credits_charged.
  • 202Deep: job accepted; poll GET /jobs/{id}.
  • 402Insufficient credits.
  • 503Search backend not configured.

POST/extract

Extract structured JSON from a page with an LLM (metered, ~5-credit floor).

Request body
  • urlrequiredstring (uri)Authorized target URL (the authorization signal).
  • schemaobjectJSON-schema-like object describing the structured output. Optional when a template matches.
  • fieldsstring[]Flat list of field names to extract. Optional when a template matches.
  • templateboolean | stringExtraction template control: omit for per-host auto-select, false to disable (then schema/fields is required), or a template name to force one (see GET /templates). When a template supplies the result the response includes "template" and llm cost is 0.
  • waitMsintegerOptional extra wait (ms) after load before reading, for JS-hydrated pages.
  • blockAssetsbooleanOverride image/media/font blocking (default on when proxied).
  • promptstringOptional natural-language extraction hint.
  • proxystringOptional outbound proxy URL.
  • proxyRotatebooleanAnti-ban: force per-request proxy rotation (anonymous scraping).
  • sessionKeystringAnti-ban: explicit override for the account stickiness key.
  • accountstringAnti-ban: account/credential label (multiple sessions per host).
  • platformProxybooleanAnti-ban: set false to opt out of the platform sticky-proxy default.
  • persistSessionbooleanAnti-ban: persist/resume browser session state (cookies) for this account.
  • ignoreSessionbooleanForce a FRESH login: skip restoring any stored session jar for this run (a new authenticated jar still persists on success). Use to re-establish a stale/expired session on demand.
  • stealthbooleanRun in a full desktop browser profile (real Chrome, human-calibrated timing, residential egress) so an authorized run is not locked out by a bot check. Requires a Pro/Enterprise plan (else 403 code:"plan_required") AND "authorized": true; adds a per-success surcharge. Most tasks do not need it — reach for it when a run is actually turned away. POST /agent is a deprecated alias for a run with this set.
  • authorizedbooleanAuthorization attestation, REQUIRED when stealth:true — assert you are authorized to access this target and accept the stealth terms. Recorded as the durable authorization artifact.
  • 200Structured result + credits_charged.
  • 402Insufficient credits.

POST/screenshot

Capture a single page as a PNG (flat ~1 credit, no LLM cost).

Request body
  • urlrequiredstring (uri)Authorized target URL (the authorization signal).
  • fullPagebooleanCapture the full scrollable page (default viewport only).
  • selectorstringOptional CSS selector to clip the capture to one element.
  • proxystringOptional outbound proxy URL.
  • blockAssetsbooleanAbort image/media/font requests to save bandwidth.
  • type"png" | "jpeg"Encoding: png (default, lossless) or jpeg (lossy, smaller — best for LLM input).
  • qualityintegerJPEG quality 1-100 (default 70). Ignored for png.
  • proxyRotatebooleanAnti-ban: force per-request proxy rotation (anonymous scraping).
  • sessionKeystringAnti-ban: explicit override for the account stickiness key.
  • accountstringAnti-ban: account/credential label (multiple sessions per host).
  • platformProxybooleanAnti-ban: set false to opt out of the platform sticky-proxy default.
  • persistSessionbooleanAnti-ban: persist/resume browser session state (cookies) for this account.
  • ignoreSessionbooleanForce a FRESH login: skip restoring any stored session jar for this run (a new authenticated jar still persists on success). Use to re-establish a stale/expired session on demand.
  • stealthbooleanRun in a full desktop browser profile (real Chrome, human-calibrated timing, residential egress) so an authorized run is not locked out by a bot check. Requires a Pro/Enterprise plan (else 403 code:"plan_required") AND "authorized": true; adds a per-success surcharge. Most tasks do not need it — reach for it when a run is actually turned away. POST /agent is a deprecated alias for a run with this set.
  • authorizedbooleanAuthorization attestation, REQUIRED when stealth:true — assert you are authorized to access this target and accept the stealth terms. Recorded as the durable authorization artifact.
  • 200PNG image bytes (image/png), or JSON { url } when the engine returns a hosted URL. Charge in x-credits-charged / credits_charged.
  • 402Insufficient credits.

POST/map

Discover all URLs of a site fast — sitemap + robots + shallow link scan (flat ~2 credits, no LLM).

Request body
  • urlrequiredstring (uri)A URL on the site to map (the origin is the target).
  • limitintegerMax URLs to return.
  • includeSubdomainsbooleanInclude subdomains of the site.
  • searchstringCase-insensitive substring filter on the URL.
  • scanbooleanPerform the shallow link scan (false = sitemap-only).
  • 200List of discovered URLs with source_counts and truncated flag.
  • 402Insufficient credits.

POST/crawl

Crawl an entire site as an async job — billed per page (~3 credits/page). Returns 202 { jobId }.

Poll GET /jobs/{id} for the result (an array of pages). Optionally pass callbackUrl/callbackSecret for a completion webhook.

Request body
  • startUrlrequiredstring (uri)The URL to start crawling from (the authorization signal).
  • maxPagesintegerMax pages to read (also caps the credit reserve).
  • maxDepthintegerMax link depth from the start URL.
  • includePathsstring[]Glob allowlist of path patterns, e.g. ["/docs/**"].
  • excludePathsstring[]Glob denylist of path patterns.
  • sameDomainOnlybooleanStay on the start host.
  • respectRobotsbooleanHonor robots.txt Disallow.
  • concurrencyintegerParallel page fetches.
  • fieldsstring[]Optional per-page structured extract fields.
  • extractSchemaobjectOptional per-page JSON Schema (use instead of fields).
  • promptstringOptional extraction hint when fields/extractSchema is set.
  • proxystringOptional outbound proxy URL.
  • proxyRotatebooleanAnti-ban: force per-request proxy rotation (anonymous scraping).
  • sessionKeystringAnti-ban: explicit override for the account stickiness key.
  • accountstringAnti-ban: account/credential label (multiple sessions per host) for a logged-in crawl.
  • platformProxybooleanAnti-ban: set false to opt out of the platform sticky-proxy default.
  • persistSessionbooleanAnti-ban: persist/resume browser session state (cookies) for this account.
  • ignoreSessionbooleanForce a FRESH login: skip restoring any stored session jar for this run (a new authenticated jar still persists on success). Use to re-establish a stale/expired session on demand.
  • stealthbooleanRun in a full desktop browser profile (real Chrome, human-calibrated timing, residential egress) so an authorized run is not locked out by a bot check. Requires a Pro/Enterprise plan (else 403 code:"plan_required") AND "authorized": true; adds a per-success surcharge. Most tasks do not need it — reach for it when a run is actually turned away. POST /agent is a deprecated alias for a run with this set.
  • authorizedbooleanAuthorization attestation, REQUIRED when stealth:true — assert you are authorized to access this target and accept the stealth terms. Recorded as the durable authorization artifact.
  • callbackUrlstring (uri)Optional completion webhook URL.
  • callbackSecretstringOptional secret to HMAC-sign the callback body.
  • 202Crawl accepted; poll GET /jobs/{id}.
  • 402Insufficient credits.

POST/etl

General ETL: extract → transform (clean + optional schema) → chunk → embed → load into the queryable content store.

Domain-agnostic ingest of any source (url | html | text), one or many per call. Embedding is ON by default (set embedding:false for store-only; store:false to return content without persisting). Billed per source: higher-of(~5-credit floor, metered LLM COGS) — only schema/fields extraction incurs LLM cost.

Request body
  • urlstring (uri)A single URL to ingest.
  • urlsstring (uri)[]Multiple URLs to ingest in one call.
  • htmlstringRaw HTML to ingest (no fetch).
  • textstringRaw text to ingest (no fetch).
  • sourcesobject[]Explicit source list; each entry has exactly one of {url|html|text} plus optional metadata.
  • formats"text" | "markdown" | "html" | "json"[]Output formats (default ['text']); 'json' or schema/fields runs structured extraction.
  • schemaobjectOptional JSON Schema for structured extraction.
  • fieldsobjectOptional fields spec (array of names / {name,description} / object map).
  • promptstringOptional instruction to guide structured extraction.
  • embeddingbooleanEmbed chunks for semantic query (default true).
  • storebooleanPersist to the content store (default true).
  • collectionstringOptional namespace to group + filter ingested documents.
  • chunkSizenumberChunk size in chars (default 1200).
  • chunkOverlapnumberChunk overlap in chars (default 120).
  • proxystringOptional proxy URL for url sources.
  • waitMsnumberOptional settle wait (ms) before reading url sources.
  • metadataobjectOptional metadata stored with every document in this call.
  • 200Per-document results { source, url, title, chunks, embedded, stored, document_id, data? } + totals + credits_charged.
  • 402Insufficient credits.

POST/etl/query

Semantic search over content ingested via /etl — top-k similar chunks with source url/title (flat 1 credit, no LLM).

Request body
  • queryrequiredstringNatural-language query to match against stored content.
  • knumberNumber of chunks to return (1-50, default 8).
  • collectionstringOptional collection namespace to scope the search.
  • 200Top-k matches [{ content, url, title, similarity, document_id, chunk_index }] + credits_charged.
  • 402Insufficient credits.

Monitors

Standing watches: check a page on a cadence and POST an HMAC-signed webhook to your callback when the watched value changes.

GET/monitors

List this tenant's monitors (secrets excluded).

  • 200Array of monitors.

POST/monitors

Create a monitor that watches a page on a schedule and pushes a signed webhook on change.

Request body
  • namerequiredstringLabel for the monitor.
  • check_typerequired"extract" | "observe"How the watched value is read.
  • urlrequiredstring (uri)The page to watch.
  • selectorstringOptional CSS selector scoping an observe check to one element.
  • watch_fieldsstring[]For extract: fields to watch.
  • nl_watchstringFor extract: natural-language description of what to watch.
  • interval_secondsrequiredintegerCheck cadence in seconds (min 60).
  • callback_urlrequiredstring (uri)http(s) URL that receives the change webhook.
  • callback_secretstringOptional secret to HMAC-sign the change webhook.
  • proxystringOptional outbound proxy URL.
  • 201Monitor created.
  • 400Invalid monitor definition.

GET/monitors/{id}

Read a single monitor (secrets excluded).

Parameters
  • idrequiredstring · path
  • 200Monitor detail.
  • 404No such monitor for this tenant.

PATCH/monitors/{id}

Update / pause / resume a monitor (active, interval, callback, selector, watch spec).

Parameters
  • idrequiredstring · path
  • 200Updated monitor.
  • 404No such monitor.

DELETE/monitors/{id}

Delete a monitor and its check history.

Parameters
  • idrequiredstring · path
  • 200Deleted.
  • 404No such monitor.

GET/monitors/{id}/history

A monitor's recent check history (changed/unchanged/error + value excerpts).

Parameters
  • idrequiredstring · path
  • limitinteger · query · default 50
  • 200Paginated check history.
  • 404No such monitor.

Sessions & credentials

How runs authenticate without you shipping passwords: connect links an end-user signs into by hand, sessions imported from the capture extension, the write-only secrets vault, saved login accounts, and 2FA inboxes.

GET/connect/sessions

List connect links and whether each sign-in was completed.

Use this to check whether the user finished the sign-in before re-running the task. Never returns the token.

  • 200{ sessions: [{ id, host, account, status, created_at, expires_at, connected_at, error }] }. status: pending | active | connected | failed | expired | cancelled.
  • 401Missing/invalid API key.

POST/connect/sessions

Mint a link that lets a HUMAN sign into a site, so later runs are already logged in.

The answer to `code: "credential_missing"`, and to sites that hard-challenge automated logins from datacenter IPs and cannot be signed into by an agent at all. Returns a single-use `url`. Send your end-user there; they complete the sign-in (password, 2FA, CAPTCHA) in a Twin-Browser-hosted browser, and the resulting session is captured. Re-run the original task afterwards and it restores that session (`sessionRestored: true`) — no credentials, no 2FA. OAuth-shaped, for sites that offer no OAuth. The link expires in ~30 minutes and is scoped to that one host, and the end-user's PASSWORD is never stored — only the session.

Request body
  • hostrequiredstringSite to connect, e.g. "linkedin.com" (a full URL also works).
  • accountstringLabel when several logins are kept per site; must match the `account` used on /run.
  • redirectUristringAbsolute http(s) URL to return the user to once connected.
  • 201{ id, url, host, account, expiresAt, ttlMinutes } — `url` is shown once.
  • 400Missing/invalid host, or a non-absolute redirectUri.
  • 401Missing/invalid API key.
  • 429Too many pending connect sessions.

POST/sessions/import

Import a browser session captured on the user’s OWN machine (the capture extension).

The counterpart to the hosted connect flow, for popup-OAuth sign-ins ("Continue with Google") that only complete in a real local browser: the user signs in on their own machine and the companion extension ships the resulting cookies here. They are assembled into an encrypted session exactly as a hosted sign-in would be, so every later run on that host is `sessionRestored: true`. Cookies are the captured SESSION, never a password. Stored under both apex and www forms of the host.

Request body
  • hostrequiredstringThe site the session belongs to (e.g. "linkedin.com"). `url` or `target` are accepted aliases.
  • accountstringOptional account label, to keep several logins per host apart.
  • cookiesrequiredobject[]Browser cookies: [{ name, value, domain, path?, expires?/expirationDate?, httpOnly?, secure?, sameSite? }]. Non-usable entries are dropped; at least one usable cookie is required.
  • originsobject[]Optional localStorage origins in Playwright storageState form.
  • ttlDaysintegerOptional session TTL override in days.
  • 200{ ok: true, host, account?, cookieCount } — later runs on the host restore this session.
  • 400Missing host, empty cookies, or no usable cookie.
  • 401Missing/invalid API key.

GET/accounts

Which logins this tenant already has — metadata only, never a credential.

The discovery half of the credential system: every run-shaped endpoint accepts an `account` label to pick one of several logins for a host, and this endpoint tells you which labels exist. Read-only metadata — host, label, and a MASKED email preview. The ciphertext is never loaded; a credential leaves the system exactly once, at fill-time inside the browser.

Parameters
  • hoststring · queryNarrow to logins usable on one site (e.g. "linkedin.com"). Accounts with no host bound ("any site") are always included.
  • urlstring (uri) · queryAlternative to host — a full URL whose hostname is used.
  • 200{ accounts: [{ label, host, emailPreview, hasPassword, createdAt }], count }.
  • 401Missing/invalid API key.

GET/secrets

List this tenant's stored secret NAMES (write-only vault — values are never returned). Free.

  • 200Secrets: { secrets:[{ name, created_at }] }.
  • 401Missing/invalid API key.

POST/secrets

Store (encrypt) a named secret for this tenant, referenceable in prompts as {{secret:NAME}}. Free.

Request body
  • namerequiredstringSecret name (1–64 chars of letters, digits, "_", ".", "-").
  • valuerequiredstringThe secret value to encrypt at rest (never returned by GET).
  • 200Stored: { name }.
  • 400Invalid name/value.
  • 401Missing/invalid API key.

DELETE/secrets/{name}

Delete a stored secret by name for this tenant. Free.

Parameters
  • namerequiredstring · path
  • 200Deleted: { name }.
  • 401Missing/invalid API key.
  • 404No such secret for this tenant.

GET/email-inbox

List the connected IMAP inboxes used to auto-resolve emailed 2FA codes (host/user only).

  • 200{ inboxes: [{ name, host, user }] } — never returns the app-password.
  • 401Missing/invalid API key.

POST/email-inbox

Connect an IMAP inbox so sign-in runs auto-fill the emailed 2FA code (no HITL pause).

Stores an IMAP inbox (encrypted) that the engine's email-first path reads to fetch and fill an emailed verification code itself, so a login run resolves the 2FA wall without parking for a human. The inbox is VALIDATED against a real IMAP connection before it is stored (a wrong host/app-password is rejected 400). Use a provider app-password (e.g. Gmail: host imap.gmail.com, port 993). The app-password is write-only — never returned. Re-posting the same user updates it. This is the API-key equivalent of the dashboard's Connect-inbox action.

Request body
  • hostrequiredstringIMAP host, e.g. imap.gmail.com.
  • portintegerIMAP port (default 993).
  • userrequiredstringThe inbox email address.
  • passrequiredstringApp-password for the inbox (write-only; never returned).
  • securebooleanUse TLS (default true).
  • 200{ name, host, user } — the inbox was validated and stored.
  • 400Invalid host/user/pass, or the IMAP connection was rejected.
  • 401Missing/invalid API key.
  • 502The connection check could not run right now — retry.

DELETE/email-inbox

Disconnect a stored IMAP inbox by name.

Parameters
  • namerequiredstring · queryThe inbox name from the list/connect response, e.g. email:you@gmail.com.
  • 200{ name } — the inbox was removed.
  • 400Missing name.
  • 401Missing/invalid API key.

Page intelligence & anti-bot

Observe a page as token-efficient indexed DOM state without acting, or hand a blocked session’s CAPTCHA to the solver.

POST/observe

Serialize a page into indexed DOM state without acting (~1 credit).

Request body
  • urlrequiredstring (uri)
  • 200Indexed DOM state.

POST/solve-captcha

Solve a captcha blocking an in-flight session (~5 credits, charged only on success).

Asks the engine to solve a captcha in an already-open session (via 2captcha). No target URL — the session already holds the page context. Charges 5 credits ONLY when a captcha was actually solved; an unsolved attempt is fully refunded (credits_charged:0).

Request body
  • sessionIdrequiredstringThe in-flight session holding the captcha-blocked page.
  • 200Result: { solved, type, credits_charged } (charged only when solved:true).
  • 401Missing/invalid API key.
  • 402Insufficient credits.

Platform & billing

Reseller subtenant provisioning and the live rate card.

GET/tenants

Reseller: list your subtenants with balances and what they cost you.

  • 200{ subtenants: [{ id, name, billing, balance, billed_to_parent }] }.
  • 403Key lacks the tenants:provision scope.

POST/tenants

Reseller: create a subtenant under your account and mint its API key.

Requires the `tenants:provision` scope. `billing` decides who pays: "self" (the subtenant pays from its own balance; fund it with `credits`) or "parent" (your credit pool pays, each charge attributed to the subtenant). The API key is returned ONCE.

Request body
  • namerequiredstring
  • billing"self" | "parent"Default "self".
  • planstring
  • creditsintegerOpening grant transferred from your balance (billing:"self" only).
  • 201{ tenant: { id, name, slug, plan, billing }, apiKey, granted } — apiKey is shown once.
  • 400Invalid name/billing, or credits on a parent-billed subtenant.
  • 402Insufficient credits to fund the requested grant.
  • 403Key lacks the tenants:provision scope.
  • 409Nested resellers are not supported.

GET/pricing

Public credit rate card (no API key required).

Parameters
  • regionstring · query
  • 200Credit rate card.