Browser
Browser runs
client.browser_runs gives a cloud browser agent a task on the sites you allow and reads what it found. A run starts queued, moves to running, and ends succeeded, partial, failed, cancelled or timed_out. It browses only allowed_origins, in a sandbox of its own, and never submits payments, sends messages or makes other irreversible changes.
A run is billed once, when it ends: its Drex Max and LLM tokens and its browser seconds, never more than limits.max_cost_usd. Runs that never start, time out, or fail on Drex's side are not charged; a run cancelled while it runs is charged its browser seconds so far. Reading and listing runs is free. See Browser runs for prices, blocked sites, limits and retention.
| Method | Parameters | Returns | Description |
|---|---|---|---|
create(*, task, start_url, allowed_origins, answer_schema=None, limits=None, artifacts=None, metadata=None, risky_actions=None, model=None, idempotency_key=None) | See Start a run. | BrowserRunRef | Starts a run and returns it queued. |
run(*, task, start_url, allowed_origins, answer_schema=None, limits=None, artifacts=None, metadata=None, risky_actions=None, model=None, idempotency_key=None, wait_timeout=3000.0, poll_interval=2.0) | The parameters of create, wait_timeout: float (seconds, default 3000), poll_interval: float (seconds, default 2) | BrowserRun | create, then wait: returns the run once it is final. retry, timeout and extra_headers apply to every request it makes. |
get(run_id) | run_id: str | BrowserRun | One run, with its steps. Another account's run raises NaceNotFoundError. |
wait(run_id, *, timeout=3000.0, poll_interval=2.0) | run_id: str, timeout: float (seconds, default 3000), poll_interval: float (seconds, default 2) | BrowserRun | Polls get until the run is final. See Wait for a run. |
list(**options) | limit: int (1 to 50, default 20), status: BrowserRunStatus, cursor: str | BrowserRunPage | One page of runs, newest first, without steps. Pass next_cursor as cursor for the next page. |
iter_all(**options) | status: BrowserRunStatus, retry, timeout, extra_headers | Iterator[BrowserRun] | Every run across every page, newest first, 50 a page. |
cancel(run_id) | run_id: str | BrowserRunRef | Cancels a queued or running run and stops its browser. Cancelling a cancelled run answers the same again; any other finished run raises NaceConflictError with code run_finished. |
artifacts(run_id) | run_id: str | BrowserRunArtifactList | The artifacts, such as screenshots, the run reported, with their links. |
A limit outside 1 to 50, a status that isn't one of BROWSER_RUN_STATUSES, an allowed_origins given as one string, or a run_id that is empty or holds /, ? or #, raises NaceError before anything is sent.
from nace_sdk import BrowserRunLimits, NaceClient
with NaceClient() as client:
run = client.browser_runs.run(
task="Find the price of the blue ceramic mug.",
start_url="https://shop.example.com/",
allowed_origins=["https://shop.example.com"],
answer_schema={"type": "object", "properties": {"price": {"type": "string"}}, "required": ["price"]},
limits=BrowserRunLimits(max_cost_usd=0.1, max_seconds=300),
)
print(run.status, run.answer, run.usage.cost_usd if run.usage else None)
if run.outcome is not None:
print(run.outcome.kind, run.outcome.reason)
for artifact in client.browser_runs.artifacts(run.id).items:
print(artifact.kind, artifact.url)Start a run
create and run take:
| Parameter | Type | Required | Description |
|---|---|---|---|
task | str | Yes | What the agent should do, in plain language: 1 to 4000 characters. |
start_url | str | Yes | An http or https page on a public host, on the default port, whose origin is in allowed_origins. |
allowed_origins | Sequence[str] | Yes | 1 to 20 origins, such as https://shop.example.com (no path), the run may load. Everything else is blocked. |
answer_schema | Mapping[str, Any] | None | No | A JSON Schema, at most 16 KB and without pattern or patternProperties, the answer must match. With one, answer holds the structured answer; a completed answer that doesn't match fails the run as answer_invalid, charged. |
limits | BrowserRunLimits | Mapping[str, Any] | None | No | What the run may spend: max_cost_usd (US dollars, 0.01 to 1, default 0.04), max_seconds (1 to 1800, default 600) and max_llm_calls (1 to 100, default 20). A field you leave out takes its default. |
artifacts | BrowserRunArtifactOptions | Mapping[str, Any] | None | No | What the run keeps besides its answer: screenshots (on by default) and har, an HTTP archive of its traffic (off by default). |
metadata | Mapping[str, str] | None | No | Up to 20 strings of your own, each at most 500 characters, returned with the run. |
risky_actions | Literal["deny"] | None | No | Only "deny", the default. |
model | str | None | No | Only "default", the default. |
idempotency_key | str | None | No | Sent as Idempotency-Key. A retried key returns the same run; the same key with a different body raises NaceConflictError (idempotency_conflict). Leave it out and one is minted per call, so retries never start a second run. |
A create is refused with NaceUnprocessableEntityError when the body is invalid (invalid_request), names a site runs may not browse (origin_blocked: banking, payment, identity, large social and government sites, and hosts that aren't public), or the account has zero retention (zero_retention_unsupported). It raises NaceRateLimitError past 20 queued or running runs (rate_limited) or past the account's daily spending limit for runs (daily_spend_limit), and NaceInsufficientCreditError without spendable credit, or when a free account, or one with less than $5 of credit and no pay as you go, already has two unfinished runs and document jobs (insufficient_credit).
Wait for a run
wait polls get every poll_interval seconds until status is succeeded, partial, failed, cancelled or timed_out, for up to timeout seconds in all, and returns the run. run does the same after its create, with wait_timeout. The default, 3000 seconds, outlasts any run: Drex times out a run queued for 15 minutes or browsing past max_seconds.
- A run that didn't succeed is returned, not raised: read
statusandoutcome. - Out of time: raises
NaceBrowserRunTimeoutError, witherror.runanderror.timeout. The run keeps going; wait again orcancelit. runstarted the run, then a read failed (after its retries): raisesNaceBrowserRunWaitError, witherror.run_idand the original error as__cause__. The run keeps going and is billed when it ends:wait(error.run_id)orcancelit, since callingrunagain starts another. A failed create raises its own error. An ambiguous one, such as a connection error after the server accepted the request, may still have started the run: pass your ownidempotency_keyand callrunagain with the same key and arguments, which returns that run instead of starting a second billed one.
from nace_sdk import NaceBrowserRunTimeoutError
created = client.browser_runs.create(task=task, start_url=start_url, allowed_origins=origins)
try:
run = client.browser_runs.wait(created.id, timeout=600)
except NaceBrowserRunTimeoutError as error:
client.browser_runs.cancel(error.run.id)Browser run types
BrowserRun, BrowserRunRef, BrowserRunArtifact and the nested types keep any other field Drex sends as an attribute and in model_extra.
| Type | Fields |
|---|---|
BrowserRunRef | id: str (brun_...), object: str (always browser_run), status: str, created_at: str | None. |
BrowserRun | id: str, object: str, status: str (one of BROWSER_RUN_STATUSES), task: str (empty once the run's content was erased, 48 hours after it ended), start_url: str, allowed_origins: tuple[str, ...], risky_actions: str, limits: BrowserRunLimits, answer: Any (the structured answer, when the run had an answer_schema and the answer matched it), answer_text: str | None, outcome: BrowserRunOutcome | None (set once final), final_url: str | None, steps: tuple[Any, ...] | None (the agent's steps as it reported them, usually n, kind, goal, moves and result; only get returns them), pending_approval: Any (always None), usage: BrowserRunUsage | None, artifacts_url: str, metadata: dict[str, str], created_at: str, started_at: str | None, finished_at: str | None, is_final: bool (True once status is final). |
BrowserRunOutcome | kind: str (see below), reason: str | None, evidence: tuple[BrowserRunEvidence | Any, ...] (an item without that shape is kept as the agent reported it). |
BrowserRunEvidence | url: str | None, text: str | None: where the agent saw what its outcome rests on. |
BrowserRunUsage | drex_input_tokens: int, llm_input_tokens: int, llm_output_tokens: int, browser_seconds: int, cost_usd: float | None (what the run was charged; None until it ended, 0 when it was not charged). |
BrowserRunPage | items: tuple[BrowserRun, ...], next_cursor: str | None (None on the last page). |
BrowserRunArtifact | kind: str, step: Any (the step it belongs to, as reported), url: str | None (an https link, until expires_at), expires_at: Any. |
BrowserRunArtifactList | items: tuple[BrowserRunArtifact, ...]. |
BrowserRunLimits | max_cost_usd: float | None, max_seconds: int | None, max_llm_calls: int | None. Pass one as limits; a field left None takes its default. On a run, the limits it ran with. |
BrowserRunArtifactOptions | screenshots: bool | None, har: bool | None. Pass one as artifacts. |
An outcome's kind says why the run ended with its status:
succeeded:completed, ornot_foundwhen what the task asked for isn't there.partial:blocked_login,blocked_captcha,risky_action_deniedorlimit_reached.failed:site_error,internal_error, oranswer_invalidwhen the answer didn't matchanswer_schema.cancelledandtimed_out: the same as the status.