One Bearer key, from goal to action.
A small, versioned REST surface under /api/v1/*: run a goal, dispatch to the semantic cache, queue async jobs, or replay a compiled skill. Usage-billed in credits, with the cache outcome and cost in every response header.
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" }
}'
# HTTP/2 200
# x-twin-cache: hit ← matched a compiled skill
# x-twin-skill: acme-export ← which skill replayed
# x-twin-llm-calls: 0 ← zero model calls on a cache hit
#
# { "success": true, "path": [ … ], "credits_charged": 1 }Three lines from your stack to ours.
No SDK required — the twin client and one fetch call against https://twin-browser.com/api/v1 are interchangeable. The cache outcome and exact credit cost come back in the response headers, so the wedge is observable from line one.
// One key, one POST. Base URL: https://twin-browser.com/api/v1
const res = await fetch('https://twin-browser.com/api/v1/run', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.TWIN_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
url: 'https://app.acme.com/invoices',
prompt: 'export last month as CSV',
success: { kind: 'urlIncludes', value: '/exports' },
}),
});
const run = await res.json();
// Cache + cost are in the response headers — read the wedge directly:
res.headers.get('x-twin-cache'); // 'hit' | 'miss'
res.headers.get('x-twin-llm-calls'); // '0' on a replay
console.log(run.success, run.credits_charged); // true 1Authenticated, observable, settled
Every call runs the same four stages — and the third is where cost falls away.
- 1
Authenticate
Every call carries Authorization: Bearer with a per-tenant key. Default-deny RLS scopes every read and write to your tenant; an invalid key fails closed with 401.
- 2
Observe the target
Twin loads the URL you passed and serializes it into a compact, numerically-indexed map of interactive elements — token-efficient state, not raw HTML.
- 3
Replay or plan
The semantic cache fuzzy-matches the request to a compiled skill. On a hit it replays deterministically with zero LLM calls; on a miss the planner discovers a path and compiles it.
- 4
Settle & audit
Credits settle on the first terminal read — failed, cancelled, or unsolved actions refund in full. The goal, path, target, and key are written to the audit log.
Five families, one engine
Run goals, compile and replay skills, manage vault secrets, watch runs live, and read the rate card — each is a thin, predictable set of endpoints.
Run & dispatch
Run a goal synchronously, queue it as an async job, or hand it to the semantic cache to replay-or-compile.
- POST /run
- POST /dispatch
- POST /jobs
- GET /jobs/:id
Skills
Compile a goal into a named deterministic skill, list your skills, and replay them with no LLM in the loop.
- POST /skills
- GET /skills
- POST /skills/:name/run
Secrets
Store credentials in the vault as named references — filled at run time on your own runs, never returned or logged.
- POST /secrets
- GET /secrets
- DELETE /secrets/:name
Runs & live view
Watch a run live over SSE, hand back a 2FA code when it pauses, replay its recording, or serialize a page to indexed state.
- POST /live
- POST /live/resume
- GET /runs/:id/video
- POST /observe
Pricing & corpus
Read the public credit rate card and search the sanitized cross-tenant skill corpus by intent.
- GET /pricing
- GET /library
The /api/v1 reference
Costs are in credits. Metered LLM cost is charged as the higher of the flat price and actual usage; failed, cancelled, or unsolved actions are fully refunded.
| Method | Endpoint | Credits | Description |
|---|---|---|---|
| POST | /api/v1/run | 10 | Run a goal synchronously on the target URL you provide. A sign-in run parks on a 2FA/approval wall by default (returns status:"paused" + sessionId). |
| POST | /api/v1/agent | 10+ | Premium (Pro plan): one call forcing the full anti-detection stack (stealth + human mimicry + sticky residential proxy). Same body as /run plus a required { authorized:true } attestation. |
| POST | /api/v1/dispatch | 1 / 10 | Semantic cache: replay a matched skill (hit) or compile + cache (miss). |
| POST | /api/v1/jobs | 10 | Submit a goal as an async background job (202 + job id). |
| GET | /api/v1/jobs/:id | — | Poll an async job; settles credits on the first terminal read. |
| GET | /api/v1/jobs/:id/stream | — | Server-sent progress frames for a running job. |
| POST | /api/v1/jobs/:id/cancel | — | Cancel a running job (full refund). |
| POST | /api/v1/skills | 50 | Compile a skill (discover + minimize). |
| GET | /api/v1/skills | free | List your compiled skills. |
| POST | /api/v1/skills/:name/run | 1 | Deterministically replay a compiled skill (no LLM). |
| POST | /api/v1/secrets | free | Store a credential in the vault as a named reference. |
| GET | /api/v1/secrets | free | List secret names and metadata (never the values). |
| GET | /api/v1/library | free | Search the cross-tenant shared skill corpus (sanitized metadata). |
| POST | /api/v1/observe | 1 | Serialize a page into indexed DOM state without acting. |
| POST | /api/v1/live | 10 | Run a goal and watch it live (SSE screencast); also recorded. |
| POST | /api/v1/live/resume | free | Hand a 2FA code (or cancel) to a live run waiting on an await-user step. |
| POST | /api/v1/runs/:id/resume | 10 | Continue a paused (HITL) run after an out-of-band approval. |
| GET | /api/v1/runs/:id/video | free | Stream a run’s recording (webm). |
| GET | /api/v1/pricing | free | Public credit rate card (edge-cached). |
Full request/response shapes, success-condition specs, and error codes live in the docs.
Read the wedge on every call
The cache outcome and cost ride back in x-twin-* headers — no body parsing to see whether a call hit a compiled skill or how many model calls it cost.
| Header | Example | Meaning |
|---|---|---|
| x-twin-cache | hit · miss | Whether a compiled skill matched and replayed. |
| x-twin-skill | acme-export | Name of the skill that replayed, when cached. |
| x-twin-llm-calls | 0 … n | Model calls made for this request — 0 on a replay. |
| x-twin-credits | 1 … 50 | Credits charged (mirrors the body’s credits_charged). |
| x-twin-run-id | run_… | Stable id to fetch the recording or audit record. |
Compile once, replay for one credit
Compile a goal into a named, deterministic skill, list your skills, and replay them with no LLM in the loop — the cheap, repeatable path.
# Compile a goal into a reusable, deterministic skill (discover + minimize).
curl -X POST https://twin-browser.com/api/v1/skills \
-H "Authorization: Bearer ab_live_…" -H "Content-Type: application/json" \
-d '{ "url": "https://app.acme.com/invoices",
"prompt": "export last month as CSV", "name": "acme-export" }'
# → { "name": "acme-export", "steps": 6, "credits_charged": 50 }
# List your compiled skills.
curl https://twin-browser.com/api/v1/skills -H "Authorization: Bearer ab_live_…"
# Replay a skill by name — zero LLM, deterministic, 1 credit.
curl -X POST https://twin-browser.com/api/v1/skills/acme-export/run \
-H "Authorization: Bearer ab_live_…" -H "Content-Type: application/json" \
-d '{ "url": "https://app.acme.com/invoices" }'
# → { "success": true, "credits_charged": 1, "x-twin-llm-calls": 0 }Limits, discovery, and the rate card
Rate limits
Applied per tenant. Over the limit returns 429 with a Retry-After header — back off and retry. Higher tiers raise limits and replay throughput.
OpenAPI + rate card
The surface is described by an OpenAPI document for client generation and agent discovery, and the live rate card is JSON at /api/v1/pricing.
Auth & audit
Per-tenant keys, default-deny RLS, and an audit log on every call. The target URL is the authorization signal — see the security page.
API, answered
How do I authenticate?
What are the x-twin-* response headers?
Is there an OpenAPI spec?
How are rate limits enforced?
What does a call cost?
Where to go next
Prefer tools over endpoints?
The same engine is an MCP server for Claude and Cursor and a one-line LangChain adapter.