Browza API
Send a task in plain language; Browza opens a real browser, does the work, and returns a structured result. The API is a conventional JSON-over-HTTPS interface: create a run, poll or stream its progress, collect the result. Everything the dashboard can see, the API can see.
https://api.browza.xyzcurl -X POST https://api.browza.xyz/v1/runs \
-H "Authorization: Bearer bz_live_..." \
-H "Content-Type: application/json" \
-d '{
"task": "List the top 5 trending GitHub repositories with name and stars",
"config": { "outputSchema": {
"type": "object",
"properties": { "repos": { "type": "array", "items": {
"type": "object",
"properties": { "name": {"type":"string"}, "stars": {"type":"number"} }
}}}
}}
}'
# → 202 { "id": "8b5e2f6a-1c9d-4e37-9f1b-2f60c9f4d21e", "status": "queued" }Run ids are plain UUIDs. The run executes on a worker, not inside your request — poll GET /v1/runs/:id until status is terminal, or follow the event stream.
Authentication
Authenticate with an API key in the Authorization header. Keys are minted in the dashboard under API keys (admin role required) and shown once at creation. Two modes exist — bz_live_… and bz_test_… — both scoped to one workspace.
Authorization: Bearer bz_live_...An API key acts for the workspace as a whole. Endpoints marked Dashboard below manage the account itself (keys, models, members, billing, agent configuration) and require a signed-in dashboard session — an API key calling them receives 403 user_auth_required.
| 401 | unauthenticated | Missing or malformed Authorization header. |
| 401 | invalid_api_key | The key is unknown or has been revoked. |
| 403 | user_auth_required | This endpoint needs a dashboard session, not an API key. |
| 404 | not_found | Also returned for resources outside your workspace — existence is never confirmed. |
Conventions
Error envelope
Every error is JSON with a stable machine-readable code beside a human-readable message:
{ "error": { "code": "tier_limit", "message": "timeoutMs 7200000 exceeds the starter plan limit of 900000" } }Pagination
List endpoints take limit (1–100, default 25) and offset, and return { …rows, total, limit, offset }. Run events use a forward cursor instead — see Run events.
Idempotency
The three run-creating endpoints honor an Idempotency-Key header (up to 200 characters). Repeating a request with the same key returns the original run with HTTP 200 instead of 202 — safe to retry on timeouts.
Rate limits
Most endpoints are unthrottled beyond your plan's own concurrency and allowance gates. The invite and device-pairing endpoints carry abuse limits; a throttled request returns 429 rate_limited with retry-after and x-ratelimit-* headers. Timestamps are ISO-8601 UTC throughout. Request bodies are capped at 1 MB.
The run object
A run is one browser task from prompt to result. Its status moves through queued → claimed → running to one of the terminal states succeeded | failed | cancelled | timed_out. awaiting_human is non-terminal: the browser is alive and waiting for a person (a login or challenge) — see takeover.
{
"id": "8b5e2f6a-1c9d-4e37-9f1b-2f60c9f4d21e",
"status": "succeeded",
"task": "List the top 5 trending GitHub repositories with name and stars",
"config": { "timeoutMs": 600000, "maxSteps": 150, "screenshots": "every_step", ... },
"result": { "answer": "linux leads this month's trending list…", "data": { "repos": [ ... ] } },
"errorCode": null,
"errorMessage": null,
"attempt": 1,
"usage": {
"planner": { "inputTokens": 4210, "outputTokens": 380 },
"observe": { "inputTokens": 0, "outputTokens": 0 },
"act": { "inputTokens": 0, "outputTokens": 0 },
"extract": { "inputTokens": 9120, "outputTokens": 640 },
"total": { "inputTokens": 13330, "outputTokens": 1020, "cachedInputTokens": 3900 }
},
"inputs": {},
"procedureId": null,
"agentId": null,
"modelUsed": { "provider": "openai", "modelName": "gpt-5" },
"pageUrl": "https://github.com/trending",
"rating": null,
"createdAt": "2026-08-30T13:04:11.512Z",
"startedAt": "2026-08-30T13:04:14.906Z",
"finishedAt": "2026-08-30T13:05:02.331Z"
}usage.total.cachedInputTokens is the subset of input tokens served from the provider's prompt cache — an annotation, never additive. result matches your outputSchema when one was supplied.
Create a run
/v1/runsAPI keyBody
taskstring · 1–10,000 charsrequiredWhat to do, in plain language. Include the site when you have one in mind.
configobjectEverything below — omit for sensible defaults.
inputsobject · ≤20 keysNamed string values (≤500 chars each) the task refers to by name. Secret-shaped names or values (passwords, card numbers, tokens) are refused at the API.
config
timeoutMsinteger · 10,000–43,200,000 · default 600,000The run's budget. Browza paces against this and wraps up on its own shortly before it, returning the best answer it has rather than nothing. Plan ceilings: Free 10 min, Personal 15 min, Starter 30 min, Growth 2 h, Scale 6 h. An explicit value above your plan is refused with
422 tier_limit; the untouched default is clamped instead.maxStepsinteger · 1–1500 · default 150A runaway ceiling, not a budget — set it only to cap a pathological loop, which a clock cannot bound. Ordinary work never approaches it; time runs out first. Your plan caps it: Free/Personal 100, Starter 200, Growth 400, Scale 1200.
screenshots"every_step" | "final_only" | "off" · default "every_step"What lands in the artifact store as the run works.
outputSchemaJSON Schema object | nullWhen set, the result is validated against it before the run may finish — you get your shape or an honest failure.
profilestring | nullA named persistent browser identity — the run launches with its cookies and saves them back on clean shutdown. See Browser profiles.
retry.afterActionsboolean · default falseAllow a crash retry to continue even when a prior attempt clicked things. Leave off unless the task is idempotent.
reasonOnDriftboolean · default falseFor automation-pinned runs: fall back to reasoning when the site drifted instead of failing with
procedure_drift.solverEnabledboolean | null · default: the workspace settingAutomatic challenge solving. The workspace's setting (Settings → Automatic challenge solving, admin-only) is the default for every run;
falseopts one run out.trueon a workspace that has not turned it on is refused with403 solver_not_enabled— a run cannot grant what the workspace did not. While on, the run does not carry Browza's signed agent identity.
{ "id": "8b5e2f6a-1c9d-4e37-9f1b-2f60c9f4d21e", "status": "queued" }| 402 | allowance_exhausted | The month's included allowance is spent — add credits, enable auto-reload, or upgrade. |
| 403 | solver_not_enabled | config.solverEnabled: true on a workspace that has not turned automatic challenge solving on. An admin enables it in Settings; omit the field to use the workspace default. |
| 403 | tenant_suspended | The workspace is suspended; the key itself is fine. |
| 422 | tier_limit | An explicit maxSteps/timeoutMs above the plan's ceiling. The message names the limit. |
| 400 | bad_request | Schema violation — including an outputSchema that does not compile; the message says where. |
List runs
/v1/runsAPI keyQuery
searchstring · ≤200Substring match over the task text.
statusstringFilter by one run status.
agentIduuidOnly runs belonging to one agent.
limit / offsetint1–100 (default 25) / ≥0.
{ "runs": [ { ...run } ], "total": 132, "limit": 25, "offset": 0 }Get a run
/v1/runs/:idAPI keyReturns the full run object above, or 404 not_found.
Cancel a run
/v1/runs/:id/cancelAPI keyA queued run cancels immediately; a running one is signalled and stops at its next step boundary. Returns 202 { id, outcome }; a terminal run answers 409 not_cancellable.
Rate a run
/v1/runs/:id/ratingAPI keyBody
ratinginteger · 1–10requiredYour verdict on the result.
feedbackstring · ≤4000What was right or wrong — it feeds Browza's own learning.
Terminal runs only (409 not_ratable otherwise); the latest rating wins. Returns the updated run object.
Run events
/v1/runs/:id/eventsAPI keyThe run's life as an append-only feed: run.claimed, turn.started, step.finished, verification, egress, run.resumed, run.finished, and more.
Query
afterstring · cursor · default 0The
nextAfterfrom your previous page. This is the event id, not its display sequence — always echo it back verbatim.limitinteger · 1–500 · default 200Events per page.
curl "https://api.browza.xyz/v1/runs/:id/events?after=0&limit=200" \
-H "Authorization: Bearer bz_live_..."
# → { "events": [ { "id": "18734", "seq": 12, "type": "step.finished",
# "actor": "worker:…", "payload": { ... }, "ts": "…" } ],
# "nextAfter": "18734" }Run steps
/v1/runs/:id/stepsAPI keyWhat the agent actually did, one row per step: { attempt, seq, kind, status, reason, instruction, input, output, pageUrl, screenshotArtifactId, inputTokens, outputTokens, durationMs, startedAt }. kind is one of goto · observe · act · fill · scroll · extract · verify · decide · use_skill.
/v1/runs/:id/latencyAPI keyPer-kind latency rollup for one run: { kinds: [{ kind, steps, totalMs, totalTokens }] }.
Artifacts
/v1/artifacts/:id/contentAPI keyStreams an artifact's raw bytes (screenshots today) with its content type. Artifact ids arrive on step rows as screenshotArtifactId and in artifact.created events — there is no separate list endpoint.
Live view & takeover
Every run can be watched live — the browser's real frames — and a person can take the mouse mid-run and hand it back. Three endpoints cooperate:
/v1/runs/:id/stream-ticketAPI keyReturns { ticket, expiresInMs: 45000 } — a short-lived credential that gates the socket connection only.
/v1/runs/:id/stream/watch?ticket=…API keyA WebSocket of JPEG frames, tab state, and verification events. Connect within 45 seconds of minting the ticket; the run may outlive many tickets.
/v1/runs/:id/controlAPI keyBody
action"take" | "release"requiredTake the browser (the agent pauses at its next step boundary and your input goes live) or hand it back. A take that loses a race answers
409 conflict.
The agent object
An agent is a persistent browser worker: a goal, an optional signed-in browser profile, durable instructions, attached skills, a file drawer, an optional schedule, and a history of runs. Its status is derived, never stored: ready · running · needs_login · needs_attention · paused.
{
"id": "0f2f9f6e-…",
"name": "Morning briefing",
"goal": "Collect the top stories from our five sources and file a summary",
"browserProfileId": null,
"domainPolicy": "allowlist",
"allowedDomains": ["news.ycombinator.com", "lobste.rs"],
"deniedDomains": [],
"approvalPolicy": "autonomous",
"pausedAt": null,
"createdAt": "…", "updatedAt": "…"
}approvalPolicy is browse_only (refuses page actions), confirm_actions (pauses for a person before acting), or autonomous. Only policies that never pause can be scheduled. The domain policy keeps an agent on task; a stored host matches itself and its subdomains, never upward.
Read agents
/v1/agentsAPI key{ agents: [...] } — each with status, skillCount, instructionCount, and profileName.
/v1/agents/:idAPI keyThe agent plus its instructions and skills (sanitized manifests: names and input names, never selectors or stored values).
/v1/agents/:id/instructionsAPI key/v1/agents/:id/messagesAPI key/v1/agents/:id/scheduleAPI key/v1/agents/:id/filesAPI keyRun an agent
/v1/agents/:id/runAPI keyFire the agent now — from your code, a cron job, or the dashboard's Run button; a schedule produces exactly the same kind of run. The task is the agent's goal plus its instructions; an optional { config } body merges under the agent's own profile. Honors Idempotency-Key.
curl -X POST https://api.browza.xyz/v1/agents/:id/run -H "Authorization: Bearer bz_live_..." -H "Idempotency-Key: briefing-2026-08-30"
# → 202 { "id": "…", "status": "queued" }| 409 | agent_paused | Resume the agent first. |
| 409 | agent_has_no_goal | Give the agent a job before running it. |
Manage agents
Creating and configuring agents is done from the dashboard today (these endpoints require a signed-in session — an API key receives 403 user_auth_required):
| Method | Path | Auth | Does |
|---|---|---|---|
| POST | /v1/agents | Dashboard · developer | Create — name (≤80) required; goal, profile, domain & approval policy optional |
| PATCH | /v1/agents/:id | Dashboard · developer | Update any subset; paused: true/false pauses or resumes |
| DELETE | /v1/agents/:id | Dashboard · developer | Archive (history retained) — 204 |
| POST | /v1/agents/:id/instructions | Dashboard · developer | Add a durable instruction (≤2000 chars) |
| DELETE | /v1/agents/:id/instructions/:iid | Dashboard · developer | Remove one |
| POST | /v1/agents/:id/skills | Dashboard · developer | Attach a skill by procedureId |
| DELETE | /v1/agents/:id/skills/:pid | Dashboard · developer | Detach |
| POST | /v1/agents/:id/messages | Dashboard · developer | Chat with the agent (persisted; never changes behavior until saved as an instruction) |
Schedules
/v1/agents/:id/scheduleAPI key/v1/agents/:id/scheduleDashboard · developerPUT body
cadence"daily" | "weekly"requiredHow often.
hour / minuteint · 0–23 / 0–59Local time of day in the given timezone.
weekdaysint[] · 0–6Weekly only: which days (0 = Sunday). Multiple allowed.
timezoneIANA zonerequirede.g.
America/New_York— DST handled by recomputation. Invalid →400 bad_timezone.
Scheduling requires an approval policy that never pauses — confirm_actions agents are refused with 409 approval_blocks_schedule (nobody answers a confirmation at 8am).
Files
Every file an agent downloads or generates lands in its drawer; you can also add files for it to use.
/v1/filesAPI keyAll files across agents: { files: [{ id, agentId, agentName, runId, origin, filename, contentType, sizeBytes, sourceUrl, createdAt }], total, limit, offset } — origin ∈ downloaded · generated · uploaded.
/v1/files/:fileId/contentAPI keyStreams the bytes with Content-Disposition: attachment.
/v1/agents/:id/filesDashboard · developerUpload: { filename, contentType?, contentBase64 } (base64, whole request ≤1 MB) → 201.
Skills & automations
A procedure is a learned, replayable flow — compiled from a successful run or taught with the recorder. Replays are deterministic: saved actions, zero model calls, verified per step.
/v1/proceduresAPI key{
"id": "…", "domainPattern": "github.com", "intent": "list trending repositories",
"hasActions": false, "source": "agent", "autoMatch": false,
"parameters": [{ "name": "query", "label": "Search term", "required": true }],
"constants": [{ "paramName": "region", "value": "US" }],
"successCount": 14, "failureCount": 1, "version": 3
}Run an automation
/v1/procedures/:id/runAPI keyBody
inputsobject · ≤20 keysValues for the automation's named parameters. Required parameters without a stored constant must be supplied —
400 missing_inputslists what's absent, before any browser opens.configobjectThe same run config as Create a run.
curl -X POST https://api.browza.xyz/v1/procedures/:id/run -H "Authorization: Bearer bz_live_..." -H "Content-Type: application/json" -d '{ "inputs": { "query": "Q3 invoices" } }'
# → 202 { "id": "…", "status": "queued" }| Method | Path | Auth | Does |
|---|---|---|---|
| PATCH | /v1/procedures/:id | Dashboard · developer | { autoMatch: boolean } — opt a taught automation into automatic matching |
| DELETE | /v1/procedures/:id | Dashboard · developer | Forget it — 204 |
autoMatch: false (the default for taught automations) means it runs only when invoked by name — similarity matching never picks it up until a human opts in.
Teaching (recorder)
The Browza Local extension records a demonstration in your own browser; the server compiles it into a parameterized automation. The extension drives these endpoints — documented for completeness, not for hand-rolled clients:
| Method | Path | Auth | Does |
|---|---|---|---|
| POST | /v1/teach/pairings | None (rate-limited) | Start device pairing → { userCode, secret } |
| POST | /v1/teach/pairings/approve | Dashboard · admin | Approve a code from the dashboard |
| POST | /v1/teach/pairings/claim | None (rate-limited) | Device polls until claimed → its bzx_ token |
| GET | /v1/teach/drafts | Dashboard · viewer | Recordings awaiting review |
| POST | /v1/teach/drafts/:id/confirm | Device or dashboard | Name it, map parameters → 201 { procedureId } |
| POST | /v1/teach/forget-all | Dashboard · admin | Delete every taught automation and draft |
Browser profiles
A profile is a durable browser identity — cookies and logins that persist between runs, on any worker. Sign in once (via takeover) and every later run with that profile arrives signed in.
/v1/browser-profilesAPI key{ "id": "…", "name": "quickbooks", "hasIdentity": true, "sizeBytes": 18234,
"clearedAt": "…", "lastUsedAt": "…", "inUse": false, "lastShutdownGraceful": true }/v1/browser-profilesAPI keyBody
namestring · 1–64 · [a-zA-Z0-9._-]requiredIdempotent: creating an existing name returns it.
/v1/browser-profiles/:idAPI key204, or 409 profile_in_use while a run holds it.
Browser sessions
/v1/browser-sessionsAPI keyThe workspace's browser session history: { sessions: [{ id, runId, provider, status, startedAt, endedAt }], total, limit, offset }.
API keys
Keys are managed from the dashboard — you cannot mint a key with a key.
| Method | Path | Auth | Does |
|---|---|---|---|
| GET | /v1/api-keys | Dashboard · admin | List (prefix, mode, last used) |
| POST | /v1/api-keys | Dashboard · admin | { name, mode: 'live'|'test' } → the full key, shown once |
| DELETE | /v1/api-keys/:id | Dashboard · admin | Revoke — takes effect immediately |
Models
Runs use Browza's hosted model by default; bring your own key for OpenAI, Anthropic, Google, or any OpenAI-compatible endpoint. Provider credentials are stored as secret references and live-tested before saving.
| Method | Path | Auth | Does |
|---|---|---|---|
| GET | /v1/model-configs | Dashboard · viewer | List (never returns secrets) |
| POST | /v1/model-configs | Dashboard · admin | { name, provider, modelName, apiKey, baseUrl?, acknowledgeDataFlow? } — tested live before storing |
| POST | /v1/model-configs/:id/test | Dashboard · developer | Re-verify a stored credential |
| POST | /v1/model-configs/use-hosted | Dashboard · admin | Switch back to the hosted model |
| DELETE | /v1/model-configs/:id | Dashboard · admin | Remove |
Workspace & billing
Workspace membership (invites, roles), notifications, and billing (plan, credits, auto-reload, invoices) are dashboard-session surfaces — see the Billing page. The API equivalents exist under /v1/tenant/*, /v1/tenants, and /v1/billing/* and require a signed-in session. One shape worth knowing: GET /v1/billing returns your plan, credit balance, per-axis usage against allowance, and this month's add-on costs.
Usage
/v1/usageAPI key{ "totals": { "runs": 132, "inputTokens": 1204331, "outputTokens": 88213, "cachedInputTokens": 402118 },
"byStatus": { "succeeded": 117, "failed": 9, "cancelled": 4, "timed_out": 2 },
"recentErrors": [ { "code": "source_blocked", "message": "…", "runId": "…", "at": "…" } ] }Knowledge
/v1/knowledgeAPI keyWhat Browza has learned about the sites you use — proven pages, verified flows, and the model calls that learning avoided: { savings, totals, hosts, page, pageSize }.
Egress
/v1/egressAPI keyNetwork identity health: exit nodes, residential bandwidth budget, and per-domain effectiveness — { nodes, bandwidth, effectiveness }.
/healthzNo auth{ ok: true, service: "browza-control-plane" } — also at /v1/health.
Errors
The codes an integration should branch on, beyond the auth set above:
| Status | Code | Meaning |
|---|---|---|
| 400 | bad_request | Schema violation; the message names the field and the issue. |
| 400 | missing_inputs | An automation run lacks required inputs; the message lists them. |
| 402 | allowance_exhausted | Monthly allowance spent — credits or an upgrade are the way forward. |
| 403 | tenant_suspended | The workspace is switched off; contact support. |
| 404 | not_found | Unknown — or not yours; the API never confirms foreign resources exist. |
| 409 | not_cancellable | The run already reached a terminal state. |
| 409 | not_ratable | Rate a run once it finishes. |
| 409 | profile_in_use | The browser profile is held by a running task. |
| 409 | conflict | A takeover lost the race for the browser. |
| 422 | tier_limit | An explicit config value above the plan's ceiling; the message names the limit. |
| 429 | rate_limited | Slow down; honor retry-after. |
| 413 | payload_too_large | Request body over 1 MB. |
| 500 | internal_error | Our fault. Retry with backoff; idempotency keys make retries safe. |
Run-level failures (a blocked site, a wall needing a person, a drifted automation) are not HTTP errors — the run finishes with status: "failed" and a named errorCode such as source_blocked, verification_required, procedure_drift, no_progress, or max_steps_exceeded.
