Docs
Dashboard
Guides / AIBeta

Computer use

Run a computer-use model's clicks and key presses on a session, in Anthropic's or OpenAI's shape, with a loop for each provider, or let the agent work in computer mode.

Computer-use models from Anthropic and OpenAI look at a screenshot and answer with a mouse or keyboard action. There are two ways to use them with a session:

  • Your own loop: send each action the model gives you to POST /v1/sessions/:id/browser/computer exactly as it is, and give the model the screenshot that comes back.
  • The AI agent: start an agent run with mode: "computer", and the platform runs the loop for you. See below.

Either way, the actions run on the session’s browser page only, through the DevTools protocol, never on the machine’s desktop. Only automate sites you are allowed to.

Run one action

POST /v1/sessions/:id/browser/computer takes ONE action, in Anthropic’s shape (the input of a tool_use block of Claude’s computer tool) or OpenAI’s (one item of a computer_call’s actions). It runs the action, then returns the screen. It needs a session with a browser: a shell-only session answers 409 browser_disabled.

curl -X POST https://api.boxline.dev/v1/sessions/$SESSION_ID/browser/computer \  -H "x-api-key: $BOXLINE_API_KEY" \  -H "content-type: application/json" \  -d '{"action": "left_click", "coordinate": [320, 240], "maxWidth": 1024}'curl -X POST https://api.boxline.dev/v1/sessions/$SESSION_ID/browser/computer \  -H "x-api-key: $BOXLINE_API_KEY" \  -H "content-type: application/json" \  -d '{"type": "click", "x": 320, "y": 240, "button": "left", "maxWidth": 1024}'

In the SDKs it is bx.sessions.computer(id, action, options), or session.computer() on a session. Next to the action, the body takes these options (they never clash with the providers’ own fields):

OptionDefaultWhat it does
maxWidthnoneScale the screenshot down to at most this width (100 to 3840). The action’s coordinates are then read in that screenshot’s pixels. Send the same value on every call of a conversation.
screenshottruefalse skips the screenshot, for example for all but the last action of an OpenAI batch.
format, quality"png""jpeg" with a quality from 1 to 100 gives smaller screenshots.
cursorfalsetrue draws the pointer on the screenshot.
Response (shape)
{  "ok": true,  "action": "left_click",  "shape": "anthropic",  "text": "…",  "screenshot": "iVBORw0KGgo…",  "mimeType": "image/png",  "width": 1024,  "height": 576,  "scale": 0.8,  "cursor": { "x": 320, "y": 240 },  "url": "https://books.toscrape.com/",  "title": "All products | Books to Scrape - Sandbox"}
FieldMeaning
okfalse when the action failed in the page (the answer is still 200, with error and the screenshot).
action, shapeThe provider's action name, and "anthropic" or "openai".
textOne line saying what happened.
error, codeWhy it failed, when ok is false.
screenshot, mimeTypeThe screen after the action (base64), or null with screenshot: false.
width, height, scaleThe screenshot's size, and its pixels per CSS pixel of the page.
cursorWhere the pointer is, in screenshot pixels.
url, titleThe page after the action.

An action that fails in the page answers 200 with ok: false, so the model can see the screen and try something else. Input that cannot be mapped is a 400 invalid_request, and a point outside the screenshot a 400 out_of_viewport; then nothing runs. Each session takes 300 computer-use calls per minute, and 300 screenshots per minute together with the screenshot action. See Run a computer-use action in the API reference.

Anthropic actions

One action of Claude’s computer toolset, computer_toolset_20260801: each tool_use block is named after its action (left_click, type, …), so send its name as action with its input, for example { action: "left_click", coordinate: [512, 742] }. Coordinates are [x, y] in screenshot pixels.

actionFieldsRuns as
screenshotnoneThe screenshot only
cursor_positionnoneThe pointer, in the reply's cursor
mouse_movecoordinate: [x, y]move
left_click, right_click, middle_click, double_click, triple_clickcoordinate? (else where the pointer is), text? or key? (keys held, e.g. "shift")click with that button and count
left_mouse_down, left_mouse_upnonemouse_down, mouse_up
left_click_dragstart_coordinate? (else where the pointer is), coordinatedrag in 10 steps
scrollcoordinate?, scroll_direction ("up", "down", "left", "right"), scroll_amount (wheel clicks of 100 px, default 3), text? (keys held)scroll
keytext, xdotool style: "ctrl+s", "Return", "ctrl+a Delete"; repeat? (1–100 presses, default 1)key
hold_keytext, duration (seconds, at most 10)key with holdMs
typetexttype
waitduration? (seconds, at most 30; default 1)wait
zoomregion: [x0, y0, x1, y1]A screenshot of that region, magnified to the screenshot's width (at most 4 times)

OpenAI actions

OpenAI’s GA computer tool ({ type: "computer" }). Send each item of a computer_call’s actions, or its single action, one call at a time.

typeFieldsRuns as
clickx, y, button ("left", "right", "wheel", "back", "forward"), keys? (held)click ("wheel" is the middle button; "back" and "forward" go back or forward in the tab's history)
double_clickx, y, keys?click with count 2
dragpath: [{ x, y }, …] (2 to 200 points), keys?drag along the path
movex, y, keys?move
scrollx, y, scroll_x, scroll_y (pixels of the screenshot), keys?scroll
keypresskeys: ["CTRL", "A"] (held together)key
typetexttype
waitnonewait 1 second
screenshotnoneThe screenshot only

See Mouse and keyboard for the actions they run as.

A loop with Claude

This loop tells Claude the screen size from a first screenshot, then runs each action it asks for and sends back the new screenshot, until it answers in words. It uses your own Anthropic key. It is the tested example from the platform’s examples/integrations/claude-computer-use; how to install, run and check it is on Claude computer use.

/** * Claude computer use with Boxline: your own loop with Claude's computer toolset (Messages API). Claude looks at * screenshots and answers with one `tool_use` block per action, named after the action (`left_click`, `type`, …); each * goes to the session as `{action: <name>, ...input}` (POST /v1/sessions/:id/browser/computer), and the result goes * back to Claude with `toolset_name: "computer"`. * *   npm install && npx tsx index.ts        (BOXLINE_API_KEY and ANTHROPIC_API_KEY in the environment; CUA_MODEL, TASK) * * The toolset sees only the page (there is no address bar): the loop opens the start page first. Writes output/result.json. */import { mkdirSync, writeFileSync } from "node:fs";import { join } from "node:path";import Anthropic from "@anthropic-ai/sdk";import { Boxline, type ComputerAction } from "@boxline/sdk";const out = process.env.OUTPUT_DIR ?? "output";const start = process.env.START_URL ?? "https://books.toscrape.com/";const task = process.env.TASK ?? "Open the Poetry category and tell me the title and price of the cheapest book in it.";const model = process.env.CUA_MODEL ?? "claude-sonnet-5-5"; // any Claude 5.5 model: they take computer_toolset_20260801const MAX_WIDTH = 1024; // screenshots are this wide; Claude's coordinates are read in these pixelsconst anthropic = new Anthropic();const bx = new Boxline();const session = await bx.sessions.create({ timeout: 900, userMetadata: { example: "integrations/claude-computer-use" } });console.log(`Session: ${session.id}`);const actions: unknown[] = [];let tokens = 0;let answer = "";try {  await session.goto(start);  // No screen size: Claude's coordinates are in the pixels of the screenshots it gets (all MAX_WIDTH wide).  const tools: Anthropic.ToolUnion[] = [{ type: "computer_toolset_20260801" }];  const messages: Anthropic.MessageParam[] = [{ role: "user", content: `${task} (The browser shows ${start}.)` }];  for (let turn = 0; turn < 30; turn++) {    // Streamed: a large max_tokens needs it. The conversation is only ever appended to (Claude's thinking is bound to it).    const res = await anthropic.messages.stream({ model, max_tokens: 64000, tools, messages }).finalMessage();    tokens += res.usage.input_tokens + res.usage.output_tokens;    messages.push({ role: "assistant", content: res.content });    const uses = res.content.filter((b): b is Anthropic.ToolUseBlock => b.type === "tool_use");    if (!uses.length) {      answer = res.content.flatMap((b) => (b.type === "text" ? [b.text] : [])).join("\n");      break;    }    // A turn's actions run in order; after a failed one the rest are not run.    const results: Anthropic.ToolResultBlockParam[] = [];    let failed = false;    for (const [i, use] of uses.entries()) {      if (failed) {        results.push({ type: "tool_result", tool_use_id: use.id, toolset_name: "computer", is_error: true, content: "Not executed: an earlier computer action in this turn failed." });        continue;      }      const input = { ...(use.input as object), action: use.name } as ComputerAction;      const after = await session.computer(input, { maxWidth: MAX_WIDTH });      actions.push(input);      console.log(`→ ${use.name}: ${after.text}`);      failed = !after.ok;      // The screen goes back with screenshot and zoom, a failed action and the turn's last action; others say what they did.      const showScreen = use.name === "screenshot" || use.name === "zoom" || failed || i === uses.length - 1;      results.push({        type: "tool_result",        tool_use_id: use.id,        toolset_name: "computer",        is_error: failed,        content: [          { type: "text", text: after.ok ? after.text || "Done." : (after.error ?? "the action failed") },          ...(showScreen && after.screenshot ? [{ type: "image" as const, source: { type: "base64" as const, media_type: after.mimeType as "image/png" | "image/jpeg", data: after.screenshot } }] : []),        ],      });    }    messages.push({ role: "user", content: results });  }  console.log(`\n${answer}`);  mkdirSync(out, { recursive: true });  writeFileSync(join(out, "result.json"), JSON.stringify({ model, task, start, answer, actions, usage: { ownModelTokens: tokens } }, null, 2));} finally {  await session.stop();}

Screenshots add up in the conversation. For long tasks, keep only the last few images, as the platform’s own agent does (it keeps 3).

A loop with OpenAI

With OpenAI’s Responses API, one computer_call can carry several actions. The loop runs them in order, takes a screenshot after the last one only, and sends it back as the call’s output. It is the tested example from examples/integrations/openai-computer-use; see OpenAI computer use to install, run and check it.

/** * OpenAI computer use with Boxline: your own loop with OpenAI's computer tool (Responses API, `{type: "computer"}`). * The model looks at screenshots and answers with `computer_call`s; each action goes to the session unchanged * (POST /v1/sessions/:id/browser/computer), and the screen after it goes back to the model. * *   npm install && npx tsx index.ts        (BOXLINE_API_KEY and OPENAI_API_KEY in the environment; CUA_MODEL, TASK) * * The tool sees only the page (there is no address bar): the loop opens the start page first. When OpenAI asks for a * safety check, a person confirms in the terminal; the loop never acknowledges one by itself. Writes output/result.json. */import { mkdirSync, writeFileSync } from "node:fs";import { join } from "node:path";import { createInterface } from "node:readline/promises";import { Boxline, type ComputerAction, type ComputerResult } from "@boxline/sdk";import OpenAI from "openai";const out = process.env.OUTPUT_DIR ?? "output";const start = process.env.START_URL ?? "https://books.toscrape.com/";const task = process.env.TASK ?? "Open the Poetry category and tell me the title and price of the cheapest book in it.";const model = process.env.CUA_MODEL ?? "gpt-6-luna";const MAX_WIDTH = 1024; // screenshots are this wide; the model's coordinates are read in these pixelsconst openai = new OpenAI();const bx = new Boxline();const session = await bx.sessions.create({ timeout: 900, userMetadata: { example: "integrations/openai-computer-use" } });console.log(`Session: ${session.id}`);const actions: unknown[] = [];let tokens = 0;let answer = "";try {  await session.goto(start);  const first = await session.computer({ type: "screenshot" }, { maxWidth: MAX_WIDTH });  const image = (s: ComputerResult) => `data:${s.mimeType};base64,${s.screenshot}`;  let input: OpenAI.Responses.ResponseInput = [    { role: "user", content: [{ type: "input_text", text: `${task} (The browser shows ${start}.)` }, { type: "input_image", image_url: image(first), detail: "auto" }] },  ];  let previous: string | undefined;  for (let turn = 0; turn < 30; turn++) {    const res = await openai.responses.create({ model, tools: [{ type: "computer" }], input, max_output_tokens: 4000, ...(previous ? { previous_response_id: previous } : {}) });    previous = res.id;    tokens += (res.usage?.input_tokens ?? 0) + (res.usage?.output_tokens ?? 0);    const calls = res.output.filter((o): o is OpenAI.Responses.ResponseComputerToolCall => o.type === "computer_call");    if (!calls.length) {      answer = res.output_text;      break;    }    input = [];    for (const call of calls) {      const checks = call.pending_safety_checks ?? [];      if (checks.length) {        const rl = createInterface({ input: process.stdin, output: process.stdout });        const ok = await rl.question(`OpenAI asks a person to confirm: ${checks.map((c) => c.message ?? c.code).join("; ")}. Go on? (yes/no) `);        rl.close();        if (!/^y(es)?$/i.test(ok.trim())) throw new Error("stopped: the safety check was not confirmed");      }      // One computer_call can carry several actions; only the last needs a screenshot.      const list = call.actions?.length ? call.actions : call.action ? [call.action] : [];      let screen: ComputerResult | null = null;      for (const [i, action] of list.entries()) {        screen = await session.computer(action as unknown as ComputerAction, { maxWidth: MAX_WIDTH, screenshot: i === list.length - 1 });        actions.push(action);        console.log(`→ ${action.type}: ${screen.text}`);      }      if (!screen?.screenshot) screen = await session.computer({ type: "screenshot" }, { maxWidth: MAX_WIDTH });      input.push({        type: "computer_call_output",        call_id: call.call_id,        output: { type: "computer_screenshot", image_url: image(screen) },        ...(checks.length ? { acknowledged_safety_checks: checks.map(({ id, code, message }) => ({ id, code, message })) } : {}),      });    }  }  console.log(`\n${answer}`);  mkdirSync(out, { recursive: true });  writeFileSync(join(out, "result.json"), JSON.stringify({ model, task, start, answer, actions, usage: { ownModelTokens: tokens } }, null, 2));} finally {  await session.stop();}

Computer mode for the AI agent

With mode: "computer" on POST /v1/agent/runs, the agent drives the page with its provider’s own computer-use tool on screenshots, instead of reading the page as numbered elements. The default, "tools", is the page view.

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": "Open https://books.toscrape.com, find the cheapest book in the Poetry category, and tell me its title and price.",    "mode": "computer",    "model": "claude-sonnet-5-5"  }'# Then follow it: its tool steps are name "computer", with the model's own action as input.curl https://api.boxline.dev/v1/agent/runs/$RUN_ID \  -H "x-api-key: $BOXLINE_API_KEY"
ModelTool
claude-opus-5-5, claude-sonnet-5-5, claude-haiku-5-5The computer toolset, computer_toolset_20260801 (no beta, no screen size)
gpt-6-sol, gpt-6-astra, gpt-6-lunaThe GA computer tool

GET /v1/agent/models shows supportsComputerUse and computerTool for each model.

  • Screenshots are JPEG, at most 1280 pixels wide and 1.15 million pixels. Larger pages are scaled down and the model’s coordinates scaled back.
  • Claude answers with one block per action. A turn’s actions run in order and stop at a failed one; the screen goes back with screenshot, zoom, a failed action and the turn’s last action. The conversation is never edited (Claude’s thinking is bound to it): after 12 actions, Anthropic clears the results of all but the last 6.
  • Each action goes through the same checks as POST /v1/sessions/:id/browser/computer. The screen shows only the page, so the run also has browser_navigate, browser_back, browser_tabs and browser_switch_tab, plus bash with a shell and ask_user_for_help.
  • Tool steps have name: "computer", with the provider’s action as input (for Claude, { action, ... } of one toolset action; for OpenAI, { actions: [...] } of one computer_call).
  • Variables are filled in only into the text of a type action, never into coordinates or keys. A variable limited to some sites (origins) cannot be typed with the computer tool, because the focus could be anywhere; the run then has browser_read and browser_type to type it into a field by its number.
  • OpenAI’s safety checks pause the run for a person (handover.by: "agent", with the checks’ messages as the reason). Resuming the run acknowledges them, and the action then runs. The platform never acknowledges them by itself. See Pause and resume.
  • A model without a computer-use tool, or a session without a browser, is a 400 invalid_request.

A tested example of computer mode, in Node and Python: Computer use.

What goes to the model

In your own loop, every screenshot goes to your model provider under your account. The agent covers fields that hold a variable’s value in its screenshots; your own loop does not. Keep secrets off the screen: fill them in yourself with a fill action, or with a plain-English step and its variables, whose values never reach a model.

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