Start a run
The agent works on the task in a session (its own, stopped at the end unless keepSession, or sessionId). A run stops at its step limit (errorCode: max_steps, then resumable for a while: see Resume a run), at its session's time limit (session_timeout) or when canceled. timeout sets the run's own session's time (default 1800 s, or the plan's maximum when shorter; 402 plan_limit above it, 400 with sessionId). 30 per minute per project. 503 provider_unavailable when no key exists for that provider (add one in project settings); 402 feature_not_in_plan on a plan without platformModels (Free) and no own key; a rejected own key ends the run with model_key_rejected; 402 spend_limit once the monthly model spend cap is reached; 409 session_not_running for a sessionId whose session was deleted (a stopped one is resumed by the run). With an Idempotency-Key, variable values are not compared (they are never stored in any form): a retry with the same key and other values gets the first run.
/v1/agent/runsRequest
| Method and path | POST/v1/agent/runs |
|---|---|
| Base URL | https://api.boxline.dev |
| Authentication | Your API key in the x-api-key header (or Authorization: Bearer). The examples read it from BOXLINE_API_KEY. |
| Safe retries | Send an Idempotency-Key header to retry without creating anything twice. See Idempotency keys. |
| Rate limit | Yes. See Rate limits for the numbers and headers. |
Headers
| Name | Type | Default | Allowed values | Description |
|---|---|---|---|---|
Idempotency-Key | string | – | – | Makes a retry safe: the same key and body within 24 hours returns the first response (with Idempotent-Replayed: true) instead of creating something twice. 1 to 255 printable ASCII characters without spaces, e.g. a UUID. Keys belong to the project (a new API key is replayed only to its caller). At most 300 new keys per minute per project (429). 1 to 255 characters. |
Body
JSON with content-type: application/json. Required.
| Name | Type | Default | Allowed values | Description |
|---|---|---|---|---|
taskrequired | string | – | – | Up to 20000 characters. |
sessionId | string | – | – | Work in this session (its own settings apply) instead of a new one. Not with session. |
session | object | – | – | The run's own session: browser only (the default), shell only ({browser: false, shell: true}) or both. Its timeout defaults to 1800 s for a run (or the plan's maxTimeoutSeconds when shorter). Not with sessionId (400). session.credentials are exported into the shell; the run's credentials are what the AI may type. |
session.browser | boolean | object | – | – | true or omitted: a browser with the defaults; false: none; an object: a browser with these settings. |
session.browser.mode | string | standard | standard, realistic | realistic puts the clock and language on the proxy's country. It never changes what the browser reports about itself. |
session.browser.locale | string | – | – | BCP 47, e.g. de-DE. Wins over the mode. |
session.browser.timezone | string | – | – | IANA, e.g. Europe/Berlin. Wins over the mode. |
session.shell | boolean | false | – | A bash shell with Python, Node, ffmpeg and sudo, sharing /workspace. |
session.timeout | integer | 300 | – | Seconds, up to the plan's maxTimeoutSeconds (402 plan_limit). At least 60. |
session.idleTimeout | integer | null | – | – | Opt-in (default off): the session stops (stopReason idle) after this many seconds without activity: 30 to its timeout. Activity: CDP commands through the gateway, live-view input, terminal keys, exec, files calls, actions and steps, scripts, agent steps and messages; an agent run working in the session (or one that can still be resumed) counts the whole time. Checked every few seconds. null switches it off (PATCH). At least 30. |
session.keepAlive | boolean | false | – | Keep a browser-only session after its last client disconnects. |
session.viewport | object | – | – | |
session.viewport.widthrequired | integer | – | – | From 200 to 3840. |
session.viewport.heightrequired | integer | – | – | From 200 to 2160. |
session.userMetadata | object | – | – | |
session.profile | object | – | – | |
session.profile.idrequired | string | – | – | |
session.profile.persist | boolean | false | – | Save the browser's logins back into the profile at the end. |
session.recordSession | boolean | true | – | |
session.setup | string[] | – | – | Shell commands run in order when the session starts, and again after a resume (needs shell). Up to 20 items. |
session.proxy | boolean | null | object | object[] | – | – | true = a residential US proxy; an object = one proxy; a list = rules by site (at most 10); null or false = none. |
session.proxy.typerequired | string | – | residential, datacenter, custom | |
session.proxy.country | string | – | – | Two-letter country code (residential and datacenter). |
session.proxy.state | string | – | – | Residential, US states, e.g. us_california (implies US). |
session.proxy.city | string | – | – | Residential, e.g. los_angeles (needs country). |
session.proxy.ip | string | – | sticky, rotating | Default sticky for sessions and crawls, rotating for fetch, screenshot, pdf and extract. |
session.proxy.scope | string | browser | browser, all | all also routes the session's shell. |
session.proxy.server | string | – | – | custom: your proxy, http://host:port or https://host:port, on a public address. |
session.proxy.username | string | – | – | |
session.proxy.password | string | – | – | Stored encrypted; never returned. |
session.proxy[].domainPattern | string | – | – | Up to 256 characters. |
session.captcha | string | – | ask, ignore, solve | What happens when a CAPTCHA waits for a person. ask (default): agent runs and steps pause and hand over. ignore: they carry on. solve (opt-in, paid plans): the platform tries to solve it first. |
session.blockAds | boolean | false | – | Refuse requests to ad and tracker sites inside the machine (its local proxy), with or without a session proxy. Blocked requests are counted in blockedRequests and reported in lifecycle events. |
session.cookieBanners | string | reject | reject, off | reject: answer consent banners of the common platforms with "Reject all" or "Necessary only" (never accept), else hide them, in every tab and frame. Each answer is a lifecycle event. |
session.extensions | string[] | – | – | Uploaded extensions (POST /v1/extensions) to load into the browser; needs the plan feature extensions. Up to 10 items. |
session.env | object | – | – | Variables for the session's shell (needs shell): new terminals, exec, scripts and setup commands get them, and they are set again on every new machine (resume, recovery). Names like the shell's ([A-Za-z_][A-Za-z0-9_]*, not PATH, HOME or BOXLINE_*), 64 KB together. Kept sealed; the session shows the names only. |
session.credentials | string[] | – | – | Credentials exported into the shell, one variable per field: a secret as $NAME, a password as $NAME_USERNAME and $NAME_PASSWORD (and boxline-otp NAME prints its 2FA code). Needs shell; the credential needs scope "shell" or "all", or shell true (else 400 credential_not_for_shell). Kept in the machine's memory only (never a file in /workspace), set again on every new machine, and hidden in exec, script and terminal output. Up to 50 items. |
allowWithExtensions | boolean | false | – | Allow variables in a session with extensions, which can read every typed value (400 variables_with_extensions otherwise). Logs a warning in the session's events. |
keepSession | boolean | false | – | Keep the run's own session afterwards (until its expiresAt, with keepAlive on). |
maxSteps | integer | null | 30 | – | The run's work budget: it stops with errorCode max_steps after this many model turns. null: no step limit (the run goes until it is done, or its session's time ends it: set a maxCostUsd or a sensible timeout). From 1 to 1000. |
maxCostUsd | number | – | – | A money budget next to maxSteps: model cost only (as usage.costUsd), checked before each model call; the run stops with errorCode max_cost (resumable) once it is reached. One call can go over it; no summary call is made then. Default: none. From 0.01 to 100. |
maxConsecutiveErrors | integer | 5 | – | Tool errors in a row (element not found, a page that failed to load, …) after which the run stops with errorCode too_many_errors (resumable). From 1 to 20. |
effort | string | – | low, medium, high, xhigh, max | |
provider | string | – | anthropic, openai, xai, google | Anthropic Claude, OpenAI, Grok (xAI), Google Gemini. Grok and Gemini have no computer use (mode computer is 400). |
model | string | – | – | |
variables | object | – | – | Values the agent may type as %name% placeholders; the model sees only the names. A value is filled in only where the model types text or picks an option (and into a bash command only with shell: true); origins limits it to fields on those sites. Names match [A-Za-z_][A-Za-z0-9_]* (64 characters at most); values are 8000 characters at most, 64 KB together. |
mode | string | tools | tools, computer | tools: the page view and browser tools. computer: the provider's own computer-use tool on screenshots (models with supportsComputerUse; 400 otherwise, or without a browser). |
credentials | string[] | – | – | Credentials as placeholders (%NAME% for a secret; %NAME.username%, %NAME.password%, %NAME.otp% for a password), exactly like variables, with the credential's origins and shell rule (scope "agent" or "all"). An explicit variable with the same name wins. The model sees names only. Up to 50 items. |
output | object | – | – | Structured output: a JSON Schema (at most 32 KB, 2,000 parts, 32 levels; local $ref only) the answer must match. The model gets a finish tool taking it; an answer that does not match gets one repair try, then the run fails with errorCode output_invalid. Enforced: type, enum, const, properties, required, additionalProperties, items, prefixItems, min/maxItems, uniqueItems, min/maxLength, minimum, maximum, exclusiveMinimum/Maximum, multipleOf, min/maxProperties, allOf, anyOf, oneOf, not, $ref. Given to the model only: pattern, patternProperties, format. |
More examples:
{"task":"Find the price of the Pro plan on example.com","maxSteps":20}{ "task": "Convert every video in /workspace to MP4", "session": { "browser": false, "shell": true }}{ "task": "Open https://example.com/settings and set the volume slider to 75", "mode": "computer", "provider": "openai", "model": "gpt-6-sol", "maxSteps": 40}curl -X POST "https://api.boxline.dev/v1/agent/runs" \ -H "x-api-key: $BOXLINE_API_KEY" \ -H "content-type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{"task": "Find three laptops under $800 with 16 GB of RAM"}'import { Boxline } from "@boxline/sdk";const bx = new Boxline(); // reads BOXLINE_API_KEY and BOXLINE_API_URLconst run = await bx.agent.run({ task: "Find three laptops under $800 with 16 GB of RAM" });const done = await bx.agent.wait(run.id); // polls until it finishesconsole.log(done.status, done.result);from boxline import Boxlinebx = Boxline() # reads BOXLINE_API_KEY and BOXLINE_API_URLrun = bx.agent.run(task="Find three laptops under $800 with 16 GB of RAM")done = bx.agent.wait(run["id"]) # polls until it finishesprint(done["status"], done["result"])# Starts the run and shows its steps as they happen; the answer comes last, on stdout.boxline run "Find three laptops under \$800 with 16 GB of RAM"Responses
201 Created
The run has started.
Headers
RateLimit-Limit: Requests allowed in the current window of this route's limit. (Rate limits)RateLimit-Remaining: Requests left in the current window. (Rate limits)RateLimit-Reset: Seconds until the window starts again. (Rate limits)Idempotent-Replayed:truewhen this is the kept response of an earlier request with the same Idempotency-Key. (Idempotency keys)
Fields
| Name | Type | Default | Allowed values | Description |
|---|---|---|---|---|
id | string | – | – | |
status | string | – | running | |
sessionId | string | – | – | |
sessionExpiresAt | string | null | – | – | When the run's session ends by its time limit (the run stops then). Format: date-time. |
provider | string | – | anthropic, openai, xai, google | Anthropic Claude, OpenAI, Grok (xAI), Google Gemini. Grok and Gemini have no computer use (mode computer is 400). |
model | string | – | – | |
keySource | string | – | project, platform | Whose key a model call uses, the project's own (project, no model charge from Boxline) or the platform's. |
mode | string | tools | tools, computer | tools: the page view and browser tools. computer: the provider's own computer-use tool on screenshots (models with supportsComputerUse; 400 otherwise, or without a browser). |
{ "id": "run_Xy12Ab34Cd56Ef78", "status": "running", "sessionId": "…", "sessionExpiresAt": "2026-09-29T10:00:00.000Z", "provider": "anthropic", "model": "claude-sonnet-5-5", "keySource": "project", "mode": "tools"}Errors
Errors have a matching HTTP status and the same body. code is stable, so check it in your code; message is for people. See Errors and request ids.
| Status | Codes | When |
|---|---|---|
| 400 Bad Request | invalid_request, invalid_json, invalid_cursor, invalid_idempotency_key, invalid_extension, variables_with_extensions, terms_not_accepted, invalid_reset_token, invalid_confirmation_code, wrong_password, no_password, invalid_next, invalid_oauth_token | The request is not valid. |
| 401 Unauthorized | unauthorized | Missing or invalid API key. |
| 402 Payment Required | feature_not_in_plan, spend_limit, out_of_credit, plan_limit, spend_limit, out_of_credit, organization, project, plan, credit | The plan does not include this, a monthly allowance or spending limit is used up, the organization has no credit left, or the plan allows no more of this. spend_limit and out_of_credit carry error.details.limit: organization, project, plan or credit. |
| 403 Forbidden | project_suspended, organization_suspended, role_forbidden, forbidden, extension_denied, cross_site_request, console_login_required, impersonating, invite_email_mismatch, email_unconfirmed | Not allowed. |
| 404 Not Found | not_found, invite_not_found, provider_unavailable | No such thing in this project (or organization), or no such route. |
| 409 Conflict | session_not_running, shell_disabled, browser_disabled, nothing_saved, not_resumable, invalid_state, conflict, email_taken, identity_in_use, provider_already_linked, last_sign_in_method, not_impersonating, machine_too_old, too_many_credential_values, limit_reached, terms_version_mismatch, no_project, last_owner, organization_not_empty, project_has_sessions, already_member, invite_pending, idempotency_in_progress | The current state does not allow it, or a request with this Idempotency-Key is still being answered. |
| 422 Unprocessable Content | idempotency_mismatch | This Idempotency-Key was already used for a different request. |
| 429 Too Many Requests | rate_limited, concurrency_limit | Too many requests, or the organization's concurrent sessions (its plan's) or the project's concurrencyCap are in use. |
| 503 Service Unavailable | no_capacity, proxy_unavailable, provider_unavailable, search_unavailable, webhooks_unavailable, mail_unavailable, browser_unreachable | No machine free in time, proxies not set up, the model provider is not configured or has no key, or web search is not set up or its provider is not answering, or webhook delivery is not set up, or an email cannot be sent, or the session's browser did not answer in time. |
{ "error": { "code": "not_found", "message": "session not found", "requestId": "req_4fJx0aQm2Lk9Pz7N" }}Use with the browser
Give the run a shell, and the agent can download a file in the browser, work on it with Python, and use the result back in the browser. See Browser, shell and files together.
curl -X POST https://api.boxline.dev/v1/agent/runs \ -H "x-api-key: $BOXLINE_API_KEY" \ -H "content-type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "task": "Download the report from https://httpbin.org/base64/bW9udGgscmV2ZW51ZQpKYW4sMTIwMApGZWIsMTg1MApNYXIsMjQxMAo= (a CSV file). In the shell, add up its revenue column with Python and save the total, and nothing else, to output/total.txt. Then open https://httpbin.org/forms/post, type the total into the first text box of the form, submit it, and tell me what the page says.", "session": {"shell": true} }'# Then poll GET /v1/agent/runs/$RUN_ID until status is "completed" or "failed".import { Boxline } from "@boxline/sdk";const bx = new Boxline();const { id } = await bx.agent.run({ task: "Download the report from https://httpbin.org/base64/bW9udGgscmV2ZW51ZQpKYW4sMTIwMApGZWIsMTg1MApNYXIsMjQxMAo= (a CSV file). In the shell, add up its revenue column with Python and save the total, and nothing else, to output/total.txt. Then open https://httpbin.org/forms/post, type the total into the first text box of the form, submit it, and tell me what the page says.", session: { shell: true },});const run = await bx.agent.wait(id);console.log(run.status, run.result);// Each tool call, with the agent's thought behind it.for (const s of run.steps) if (s.type === "tool") console.log(s.name, JSON.stringify(s.input), s.thought ?? "");from boxline import Boxlinebx = Boxline()started = bx.agent.run( task="Download the report from https://httpbin.org/base64/bW9udGgscmV2ZW51ZQpKYW4sMTIwMApGZWIsMTg1MApNYXIsMjQxMAo= (a CSV file). In the shell, add up its revenue column with Python and save the total, and nothing else, to output/total.txt. Then open https://httpbin.org/forms/post, type the total into the first text box of the form, submit it, and tell me what the page says.", session={"shell": True},)run = bx.agent.wait(started["id"])print(run["status"], run["result"])for s in run["steps"]: if s["type"] == "tool": print(s["name"], s.get("input"), s.get("thought", "")) # with the agent's thoughtboxline run 'Download the report from https://httpbin.org/base64/bW9udGgscmV2ZW51ZQpKYW4sMTIwMApGZWIsMTg1MApNYXIsMjQxMAo= (a CSV file). In the shell, add up its revenue column with Python and save the total, and nothing else, to output/total.txt. Then open https://httpbin.org/forms/post, type the total into the first text box of the form, submit it, and tell me what the page says.' --shellLearn more
These docs describe a beta API. Endpoints, fields and SDK methods may change before general availability; breaking changes will be listed in the changelog.