Start here
The eight calls most things start with, each as the smallest request that works: fetch a page, search, start a session, drive its browser, run a command, read a file, stop it, or hand the job to an AI.
Eight calls do most of the work. Each one below is the smallest request that works, in curl, Node and Python (and the CLI where it has a command). The first two need no machine at all. The next five use one session: a machine with a browser, a shell, or both, and a disk they share. The last hands the whole job to an AI.
| Base URL | https://api.boxline.dev |
|---|---|
| API key | Create one in the console, and send it as x-api-key. The examples read it from $BOXLINE_API_KEY. |
| SDKs | @boxline/sdk for Node, boxline-sdk for Python. Both read BOXLINE_API_KEY too. |
1. Fetch a page
POST/v1/fetch
A page as Markdown. No machine to start or stop.
curl -X POST "https://api.boxline.dev/v1/fetch" \ -H "x-api-key: $BOXLINE_API_KEY" \ -H "content-type: application/json" \ -d '{"url": "https://example.com"}'import { Boxline } from "@boxline/sdk";const bx = new Boxline(); // reads BOXLINE_API_KEY and BOXLINE_API_URLconst page = await bx.fetch("https://example.com");console.log(page.title, page.content);from boxline import Boxlinebx = Boxline() # reads BOXLINE_API_KEY and BOXLINE_API_URLpage = bx.fetch("https://example.com")print(page["title"], page["content"])boxline fetch https://example.com2. Search the web
POST/v1/search
Search results. Add fetch: true for each page's text too.
curl -X POST "https://api.boxline.dev/v1/search" \ -H "x-api-key: $BOXLINE_API_KEY" \ -H "content-type: application/json" \ -d '{"query": "best trail running shoes"}'import { Boxline } from "@boxline/sdk";const bx = new Boxline(); // reads BOXLINE_API_KEY and BOXLINE_API_URLconst { results, cached } = await bx.search({ query: "best trail running shoes" });for (const r of results) console.log(r.title, r.url, r.content?.length ?? "");console.log("from the cache:", cached);from boxline import Boxlinebx = Boxline() # reads BOXLINE_API_KEY and BOXLINE_API_URLfound = bx.search("best trail running shoes")for r in found["results"]: print(r["title"], r["url"], len(r.get("content") or ""))print("from the cache:", found["cached"])boxline search "best trail running shoes"3. Create a session
POST/v1/sessions
A machine with a browser. {"shell": true} adds a shell to it, and {"browser": false, "shell": true} makes a shell only.
curl -X POST "https://api.boxline.dev/v1/sessions" \ -H "x-api-key: $BOXLINE_API_KEY" \ -H "content-type: application/json" \ -d '{}'import { Boxline } from "@boxline/sdk";const bx = new Boxline(); // reads BOXLINE_API_KEY and BOXLINE_API_URLconst session = await bx.sessions.create();console.log(session.id, session.connectUrl); // connectUrl is null without a browserfrom boxline import Boxlinebx = Boxline() # reads BOXLINE_API_KEY and BOXLINE_API_URLsession = bx.sessions.create()print(session.id, session.connect_url) # connect_url is None without a browserboxline sessions create4. Run browser actions
POST/v1/sessions/{id}/browser/actions
Drive the browser with steps, plain English included. Or connect Playwright to the session's connectUrl.
curl -X POST "https://api.boxline.dev/v1/sessions/$SESSION_ID/browser/actions" \ -H "x-api-key: $BOXLINE_API_KEY" \ -H "content-type: application/json" \ -d '{ "actions": [ {"action": "goto", "url": "https://example.com"}, "click the Sign in link" ] }'import { Boxline } from "@boxline/sdk";const bx = new Boxline(); // reads BOXLINE_API_KEY and BOXLINE_API_URLconst results = await bx.sessions.actions("SESSION_ID", [ { action: "goto", url: "https://example.com" }, "click the Sign in link",]);for (const r of results) console.log(r.action, r.ok, r.value);from boxline import Boxlinebx = Boxline() # reads BOXLINE_API_KEY and BOXLINE_API_URLresults = bx.sessions.actions("SESSION_ID", [ {"action": "goto", "url": "https://example.com"}, "click the Sign in link",])for r in results: print(r["action"], r["ok"], r.get("value"))5. Run a command
POST/v1/sessions/{id}/shell/exec
Run a command in a persistent bash: cd and export carry over to the next command.
curl -X POST "https://api.boxline.dev/v1/sessions/$SESSION_ID/shell/exec" \ -H "x-api-key: $BOXLINE_API_KEY" \ -H "content-type: application/json" \ -d '{"command": "python3 -V"}'import { Boxline } from "@boxline/sdk";const bx = new Boxline(); // reads BOXLINE_API_KEY and BOXLINE_API_URLconst result = await bx.sessions.exec("SESSION_ID", "python3 -V");console.log(result.stdout, result.exitCode);from boxline import Boxlinebx = Boxline() # reads BOXLINE_API_KEY and BOXLINE_API_URLresult = bx.sessions.exec("SESSION_ID", "python3 -V")print(result["stdout"], result["exitCode"])boxline exec $SESSION_ID -- "python3 -V"# exits with the command's own exit code (124 when it ran out of time)6. Read a file or list a folder
GET/v1/sessions/{id}/files
Read what the shell wrote or the browser downloaded, even after the session stops.
curl -G "https://api.boxline.dev/v1/sessions/$SESSION_ID/files" \ -H "x-api-key: $BOXLINE_API_KEY" \ --data-urlencode "path=report.csv" \ -o report.csv# A folder listing instead: add --data-urlencode "list=1"import { writeFile } from "node:fs/promises";import { Boxline } from "@boxline/sdk";const bx = new Boxline(); // reads BOXLINE_API_KEY and BOXLINE_API_URLconst bytes = await bx.sessions.files.read("SESSION_ID", "report.csv");await writeFile("report.csv", bytes);const folder = await bx.sessions.files.list("SESSION_ID", "."); // the same call with list=1console.log(folder.entries);from boxline import Boxlinebx = Boxline() # reads BOXLINE_API_KEY and BOXLINE_API_URLdata = bx.sessions.files.read("SESSION_ID", "report.csv")with open("report.csv", "wb") as f: f.write(data)print(bx.sessions.files.list("SESSION_ID", ".")) # the same call with list=1boxline files get $SESSION_ID report.csv # saves report.csv hereboxline files ls $SESSION_ID . # a folder listing instead7. Stop a session
POST/v1/sessions/{id}/stop
Save everything and stop paying. Resume brings the session back as it was.
curl -X POST "https://api.boxline.dev/v1/sessions/$SESSION_ID/stop" \ -H "x-api-key: $BOXLINE_API_KEY"import { Boxline } from "@boxline/sdk";const bx = new Boxline(); // reads BOXLINE_API_KEY and BOXLINE_API_URLconst session = await bx.sessions.stop("SESSION_ID");console.log(session.status, session.data.deletesAt); // "STOPPED", the day it will be deletedfrom boxline import Boxlinebx = Boxline() # reads BOXLINE_API_KEY and BOXLINE_API_URLsession = bx.sessions.stop("SESSION_ID")print(session.status, session.deletes_at) # STOPPED, the day it will be deletedboxline sessions stop $SESSION_ID8. Start a run
POST/v1/agent/runs
Or hand the whole job to an AI, which uses the same calls.
curl -X POST "https://api.boxline.dev/v1/agent/runs" \ -H "x-api-key: $BOXLINE_API_KEY" \ -H "content-type: application/json" \ -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"Where to go next
- The three kinds of session: browser only, shell only, or both.
- Changing a running session: more time, a new proxy IP, new links, in one call.
- The AI agent, and saved tasks that run on a schedule.
- Every call, with its parameters, answers and errors; errors, rate limits and safe retries.
These docs describe a beta API. Endpoints, fields and SDK methods may change before general availability; breaking changes will be listed in the changelog.