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.

Base URL
https://api.browza.xyz
Your first run
curl -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.

401unauthenticatedMissing or malformed Authorization header.
401invalid_api_keyThe key is unknown or has been revoked.
403user_auth_requiredThis endpoint needs a dashboard session, not an API key.
404not_foundAlso 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.

The run object
{
  "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

POST/v1/runsAPI key

Body

  • taskstring · 1–10,000 charsrequired

    What to do, in plain language. Include the site when you have one in mind.

  • configobject

    Everything below — omit for sensible defaults.

  • inputsobject · ≤20 keys

    Named 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,000

    The 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 150

    A 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 | null

    When set, the result is validated against it before the run may finish — you get your shape or an honest failure.

  • profilestring | null

    A named persistent browser identity — the run launches with its cookies and saves them back on clean shutdown. See Browser profiles.

  • retry.afterActionsboolean · default false

    Allow a crash retry to continue even when a prior attempt clicked things. Leave off unless the task is idempotent.

  • reasonOnDriftboolean · default false

    For automation-pinned runs: fall back to reasoning when the site drifted instead of failing with procedure_drift.

  • solverEnabledboolean | null · default: the workspace setting

    Automatic challenge solving. The workspace's setting (Settings → Automatic challenge solving, admin-only) is the default for every run; false opts one run out. true on a workspace that has not turned it on is refused with 403 solver_not_enabled — a run cannot grant what the workspace did not. While on, the run does not carry Browza's signed agent identity.

Response · 202 (200 when an Idempotency-Key deduplicated)
{ "id": "8b5e2f6a-1c9d-4e37-9f1b-2f60c9f4d21e", "status": "queued" }
402allowance_exhaustedThe month's included allowance is spent — add credits, enable auto-reload, or upgrade.
403solver_not_enabledconfig.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.
403tenant_suspendedThe workspace is suspended; the key itself is fine.
422tier_limitAn explicit maxSteps/timeoutMs above the plan's ceiling. The message names the limit.
400bad_requestSchema violation — including an outputSchema that does not compile; the message says where.

List runs

GET/v1/runsAPI key

Query

  • searchstring · ≤200

    Substring match over the task text.

  • statusstring

    Filter by one run status.

  • agentIduuid

    Only runs belonging to one agent.

  • limit / offsetint

    1–100 (default 25) / ≥0.

Response
{ "runs": [ { ...run } ], "total": 132, "limit": 25, "offset": 0 }

Get a run

GET/v1/runs/:idAPI key

Returns the full run object above, or 404 not_found.

Cancel a run

POST/v1/runs/:id/cancelAPI key

A 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

POST/v1/runs/:id/ratingAPI key

Body

  • ratinginteger · 1–10required

    Your verdict on the result.

  • feedbackstring · ≤4000

    What 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

GET/v1/runs/:id/eventsAPI key

The 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 0

    The nextAfter from your previous page. This is the event id, not its display sequence — always echo it back verbatim.

  • limitinteger · 1–500 · default 200

    Events per page.

Poll from where you left off
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

GET/v1/runs/:id/stepsAPI key

What 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.

GET/v1/runs/:id/latencyAPI key

Per-kind latency rollup for one run: { kinds: [{ kind, steps, totalMs, totalTokens }] }.

Artifacts

GET/v1/artifacts/:id/contentAPI key

Streams 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:

POST/v1/runs/:id/stream-ticketAPI key

Returns { ticket, expiresInMs: 45000 } — a short-lived credential that gates the socket connection only.

WS/v1/runs/:id/stream/watch?ticket=…API key

A WebSocket of JPEG frames, tab state, and verification events. Connect within 45 seconds of minting the ticket; the run may outlive many tickets.

POST/v1/runs/:id/controlAPI key

Body

  • action"take" | "release"required

    Take 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.

The agent object
{
  "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

GET/v1/agentsAPI key

{ agents: [...] } — each with status, skillCount, instructionCount, and profileName.

GET/v1/agents/:idAPI key

The agent plus its instructions and skills (sanitized manifests: names and input names, never selectors or stored values).

GET/v1/agents/:id/instructionsAPI key
GET/v1/agents/:id/messagesAPI key
GET/v1/agents/:id/scheduleAPI key
GET/v1/agents/:id/filesAPI key

Run an agent

POST/v1/agents/:id/runAPI key

Fire 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.

Run an agent from anywhere
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" }
409agent_pausedResume the agent first.
409agent_has_no_goalGive 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):

MethodPathAuthDoes
POST/v1/agentsDashboard · developerCreate — name (≤80) required; goal, profile, domain & approval policy optional
PATCH/v1/agents/:idDashboard · developerUpdate any subset; paused: true/false pauses or resumes
DELETE/v1/agents/:idDashboard · developerArchive (history retained) — 204
POST/v1/agents/:id/instructionsDashboard · developerAdd a durable instruction (≤2000 chars)
DELETE/v1/agents/:id/instructions/:iidDashboard · developerRemove one
POST/v1/agents/:id/skillsDashboard · developerAttach a skill by procedureId
DELETE/v1/agents/:id/skills/:pidDashboard · developerDetach
POST/v1/agents/:id/messagesDashboard · developerChat with the agent (persisted; never changes behavior until saved as an instruction)

Schedules

GET/v1/agents/:id/scheduleAPI key
PUT/v1/agents/:id/scheduleDashboard · developer

PUT body

  • cadence"daily" | "weekly"required

    How often.

  • hour / minuteint · 0–23 / 0–59

    Local time of day in the given timezone.

  • weekdaysint[] · 0–6

    Weekly only: which days (0 = Sunday). Multiple allowed.

  • timezoneIANA zonerequired

    e.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.

GET/v1/filesAPI key

All files across agents: { files: [{ id, agentId, agentName, runId, origin, filename, contentType, sizeBytes, sourceUrl, createdAt }], total, limit, offset } — origin ∈ downloaded · generated · uploaded.

GET/v1/files/:fileId/contentAPI key

Streams the bytes with Content-Disposition: attachment.

POST/v1/agents/:id/filesDashboard · developer

Upload: { 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.

GET/v1/proceduresAPI key
Response (one row)
{
  "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

POST/v1/procedures/:id/runAPI key

Body

  • inputsobject · ≤20 keys

    Values for the automation's named parameters. Required parameters without a stored constant must be supplied — 400 missing_inputs lists what's absent, before any browser opens.

  • configobject

    The same run config as Create a run.

Run a taught automation by id
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" }
MethodPathAuthDoes
PATCH/v1/procedures/:idDashboard · developer{ autoMatch: boolean } — opt a taught automation into automatic matching
DELETE/v1/procedures/:idDashboard · developerForget 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:

MethodPathAuthDoes
POST/v1/teach/pairingsNone (rate-limited)Start device pairing → { userCode, secret }
POST/v1/teach/pairings/approveDashboard · adminApprove a code from the dashboard
POST/v1/teach/pairings/claimNone (rate-limited)Device polls until claimed → its bzx_ token
GET/v1/teach/draftsDashboard · viewerRecordings awaiting review
POST/v1/teach/drafts/:id/confirmDevice or dashboardName it, map parameters → 201 { procedureId }
POST/v1/teach/forget-allDashboard · adminDelete 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.

GET/v1/browser-profilesAPI key
Response (one row)
{ "id": "…", "name": "quickbooks", "hasIdentity": true, "sizeBytes": 18234,
  "clearedAt": "…", "lastUsedAt": "…", "inUse": false, "lastShutdownGraceful": true }
POST/v1/browser-profilesAPI key

Body

  • namestring · 1–64 · [a-zA-Z0-9._-]required

    Idempotent: creating an existing name returns it.

DELETE/v1/browser-profiles/:idAPI key

204, or 409 profile_in_use while a run holds it.

Browser sessions

GET/v1/browser-sessionsAPI key

The 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.

MethodPathAuthDoes
GET/v1/api-keysDashboard · adminList (prefix, mode, last used)
POST/v1/api-keysDashboard · admin{ name, mode: 'live'|'test' } → the full key, shown once
DELETE/v1/api-keys/:idDashboard · adminRevoke — 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.

MethodPathAuthDoes
GET/v1/model-configsDashboard · viewerList (never returns secrets)
POST/v1/model-configsDashboard · admin{ name, provider, modelName, apiKey, baseUrl?, acknowledgeDataFlow? } — tested live before storing
POST/v1/model-configs/:id/testDashboard · developerRe-verify a stored credential
POST/v1/model-configs/use-hostedDashboard · adminSwitch back to the hosted model
DELETE/v1/model-configs/:idDashboard · adminRemove

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

GET/v1/usageAPI key
Response
{ "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

GET/v1/knowledgeAPI key

What 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

GET/v1/egressAPI key

Network identity health: exit nodes, residential bandwidth budget, and per-domain effectiveness — { nodes, bandwidth, effectiveness }.

GET/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:

StatusCodeMeaning
400bad_requestSchema violation; the message names the field and the issue.
400missing_inputsAn automation run lacks required inputs; the message lists them.
402allowance_exhaustedMonthly allowance spent — credits or an upgrade are the way forward.
403tenant_suspendedThe workspace is switched off; contact support.
404not_foundUnknown — or not yours; the API never confirms foreign resources exist.
409not_cancellableThe run already reached a terminal state.
409not_ratableRate a run once it finishes.
409profile_in_useThe browser profile is held by a running task.
409conflictA takeover lost the race for the browser.
422tier_limitAn explicit config value above the plan's ceiling; the message names the limit.
429rate_limitedSlow down; honor retry-after.
413payload_too_largeRequest body over 1 MB.
500internal_errorOur 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.