Docs
Dashboard
Guides / AIBeta

Scripts

Run Playwright code inside the session's own sandbox, with step(), extract() and useModel() for the parts that need AI.

A script is Playwright code that runs inside the session’s own sandbox, next to its browser, so every browser call is local and fast. Inside it, step() and extract() bring in AI for the parts where exact selectors are hard. Watch the live view while it runs.

Run a script

POST /v1/sessions/:id/browser/script with { code, env?, timeoutMs?, ai?, credentials?, allowWithExtensions? }. The session needs a browser and a shell: the code runs with Node on the session’s machine, next to its Chrome. The output streams back as it happens.

A shell-only session has no browser and answers 409 browser_disabled. A session without a shell answers 409 shell_disabled. Create the session with {"shell": true} to get both. The script waits for the session’s setup commands to finish.

# In a session with a browser and a shell: the output streams back as JSON lines.curl -N -X POST https://api.boxline.dev/v1/sessions/$SESSION_ID/browser/script \  -H "x-api-key: $BOXLINE_API_KEY" \  -H "content-type: application/json" \  -d @- <<'JSON'{  "code": "await page.goto(env.START_URL);\nawait step(\"type %email% into the email field\");\nawait step(\"click Continue\");\nconst plans = await extract(\"the plan names and monthly prices\");\nreturn plans;",  "env": {    "START_URL": "https://example.com/signup",    "email": "ada@example.com"  }}JSON
FieldDefaultDescription
coderequiredThe JavaScript to run. Up to 200,000 characters.
envnoneText values the code reads as env.NAME. They are not written to the session’s log.
timeoutMs120000How long the script may run, from 1,000 to 600,000 ms.
aiplatform default{ provider?, model? }: the model step() and extract() use by default. See Which model.
credentialsnoneCredentials (scope "agent" or "all") the script’s step() calls may type: %NAME% for a secret, %NAME.username%, %NAME.password% and %NAME.otp% for a password. The values never enter the machine: the platform fills them in when the step runs, for this run only. With a code source of "push" or "url", a step() that types %NAME.otp% waits for the code your system sends. The credential a session’s profile links is offered to scripts only when it is listed here.
allowWithExtensionsfalseAllow credentials in a session with Chrome extensions, which can read typed values (400 variables_with_extensions otherwise). Only this request can agree: an allowWithExtensions in a step the script sends itself is ignored.

The script controls the browser, so it can read what a step typed into a page. Its output is shown with the session’s exported credentials and these credentials’ values hidden.

What the code can use

  • page, context and browser: Playwright, already connected to the session’s browser.
  • env: your variables.
  • console: what you log is streamed back.
  • step(), extract() and useModel(): the AI helpers below.

Top-level await works, and a value you return is printed. Do not call browser.close(): it would close the session’s browser.

Output

The response is newline-delimited JSON, like a streaming shell command: output lines, then one exit line.

Stream (NDJSON)
{"type":"stdout","data":"[{\"name\":\"Pro\",\"price\":\"$20\"}]\n"}{"type":"exit","exitCode":0,"timedOut":false,"durationMs":6120}

The run is also an exec event in the session’s events, with data.by: "script". Its output stays in this response.

AI helpers

HelperWhat it does
step(instruction, { provider?, model?, page? })One plain-English step on the page (or the page you pass). Entries of env whose names can be placeholders are its variables, so %email% is typed from env.email without the model seeing it.
extract(instruction, schema) or extract(instruction, { schema?, provider?, model?, page? })Structured data from the page, shaped by the schema if you give one.
useModel(model) or useModel(provider, model)The model for every later step() and extract() in the script. Call it again to change it; useModel() with nothing goes back to the request’s ai.

step() needs a plan with plain-English steps, like step actions. Model use counts toward your monthly spend cap.

Which model a step or extract uses

The first of these that is set wins:

  1. the call’s own provider and model;
  2. the last useModel();
  3. the request’s ai;
  4. the platform default: the first configured provider and its fast step model.
useModel()
useModel("gpt-6-sol");                                   // OpenAI, found from the model idawait step("click Sign in");                             // gpt-6-solawait step("type %email% into the email field", { model: "claude-haiku-5-5" }); // this call onlyuseModel("anthropic", "claude-sonnet-5-5");const prices = await extract("the plan prices");         // claude-sonnet-5-5
  • With a model id alone, the provider is the one whose model list has it. An unknown id (or one two providers list) throws, with the valid ids in the message.
  • A call that names only another provider gets that provider’s fast model.
  • useModel() only picks the model. It is checked (model list, plan, spend cap) when a step or extract uses it.
  • ai is checked when the script starts: 400 invalid_request for an unknown provider or model, 503 provider_unavailable when that provider is not set up, and 402 feature_not_in_plan without plain-English steps.

Every field is in the API reference: Run a Playwright script. Over MCP, browser_act runs plain-English steps and shell_exec runs commands.

These docs describe a beta API. Endpoints, fields and SDK methods may change before general availability; breaking changes will be listed in the changelog.