Playground examples
The ready-made tasks and scripts of the console's Try an example list, with the same thing as an API call.
Every example here is also in the console’s playground, under Try an example. Each one shows the task or the script, a link that opens it in the playground, and the same thing as an API call in curl, Node and Python.
AI tasks run as agent runs. Scripts run next to the browser, in a session with a shell (see Scripts). They use public demo sites that allow automation, or ask for your own site or browser profile. Nothing in the playground runs until you press Run, so read a task or script before you run it.
For complete programs with a result check, see the examples.
Browser
Plain Playwright, no AI
Opens a page with plain Playwright and reads the first 5 books and their prices. No AI, so no model cost.
Needs a script, a shell.
// Plain Playwright against this browser: no AI, no model// cost. page, context and browser are already connected.await page.goto("https://books.toscrape.com");const books = await page.$$eval("article.product_pod", (items) => items.slice(0, 5).map((item) => ({ title: item.querySelector("h3 a").getAttribute("title"), price: item.querySelector(".price_color").textContent.trim(), })),);console.table(books);return books;# 1. A session with a browser and a shell (scripts run next to the browser).curl -X POST https://api.boxline.dev/v1/sessions \ -H "x-api-key: $BOXLINE_API_KEY" \ -H "content-type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{"shell": true}'# 2. Run script.js in it (use the session's id; jq puts the file into JSON). The output streams back, one JSON line at a time.jq -n --rawfile code script.js '{code: $code}' | 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 @-# 3. Stop it.curl -X POST https://api.boxline.dev/v1/sessions/$SESSION_ID/stop \ -H "x-api-key: $BOXLINE_API_KEY"import { readFile } from "node:fs/promises";import { Boxline } from "@boxline/sdk";const bx = new Boxline(); // reads BOXLINE_API_KEY and BOXLINE_API_URLconst session = await bx.sessions.create({ shell: true });try { const result = await session.runScript(await readFile("script.js", "utf8"), { onData: (_stream, text) => process.stdout.write(text), }); console.log("exit code", result.exitCode);} finally { await session.stop();}from boxline import Boxlinebx = Boxline() # reads BOXLINE_API_KEY and BOXLINE_API_URLwith bx.sessions.create(shell=True) as session: # leaving the block stops it code = open("script.js").read() with session.run_script(code) as script: for stream, text in script: print(text, end="") print("exit code", script.result["exitCode"])An AI that asks when unsure
Looks for a present on a demo book shop and, when more than one book fits, hands the browser to you to choose, then carries on.
Needs an AI task (agent run).
On https://books.toscrape.com, open the Travel category and find the books that cost less than £30. If more than one fits, don't finish yet: ask me for help to choose, and wait until I hand the browser back with my choice. Then tell me the title and price of the one I chose.# 1. Start the run (the task is the JSON's "task").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 @- <<'JSON'{ "task": "On https://books.toscrape.com, open the Travel category and find the books that cost less than £30. If more than one fits, don't finish yet: ask me for help to choose, and wait until I hand the browser back with my choice. Then tell me the title and price of the one I chose."}JSON# 2. Follow it: status, steps and result (use its id).curl https://api.boxline.dev/v1/agent/runs/$RUN_ID \ -H "x-api-key: $BOXLINE_API_KEY"# When status is "paused", the agent is asking you. Answer in the live view, or in the note, and resume the run:curl -X POST https://api.boxline.dev/v1/agent/runs/$RUN_ID/resume \ -H "x-api-key: $BOXLINE_API_KEY" \ -H "content-type: application/json" \ -d '{"note": "Done, carry on."}'import { createInterface } from "node:readline/promises";import { Boxline } from "@boxline/sdk";const bx = new Boxline(); // reads BOXLINE_API_KEY and BOXLINE_API_URLconst run = await bx.agent.run({ task: "On https://books.toscrape.com, open the Travel category and find the books that cost less than £30. If more than one fits, don't finish yet: ask me for help to choose, and wait until I hand the browser back with my choice. Then tell me the title and price of the one I chose.",});// Watch the run. When it pauses, the agent is asking you: answer, then resume it.const rl = createInterface({ input: process.stdin, output: process.stdout });for await (const event of bx.agent.stream(run.id)) { if (event.type === "thought") console.log("…", event.text); if (event.type === "tool") console.log(event.name); if (event.type === "status" && event.status === "paused" && event.by !== "captcha") { const { liveUrl } = await bx.sessions.get(run.sessionId); // a fresh signed link: keep it private console.log("The agent needs you:", event.reason ?? "", "\nLive view:", liveUrl); await bx.agent.resume(run.id, { note: await rl.question("Your answer (Enter when done): ") }); } if (event.type === "done") console.log(event.status, event.result);}rl.close();from boxline import Boxlinebx = Boxline() # reads BOXLINE_API_KEY and BOXLINE_API_URLrun = bx.agent.run( "On https://books.toscrape.com, open the Travel category and find the books that cost less than £30. If more than one fits, don't finish yet: ask me for help to choose, and wait until I hand the browser back with my choice. Then tell me the title and price of the one I chose.",)# Watch the run. When it pauses, the agent is asking you: answer, then resume it.for event in bx.agent.stream(run["id"]): if event["type"] == "thought": print("…", event["text"]) elif event["type"] == "tool": print(event["name"]) elif event["type"] == "status" and event["status"] == "paused" and event.get("by") != "captcha": live_url = bx.sessions.get(run["sessionId"]).live_url # a fresh signed link: keep it private print("The agent needs you:", event.get("reason") or "", "\nLive view:", live_url) bx.agent.resume(run["id"], note=input("Your answer (Enter when done): ")) elif event["type"] == "done": print(event["status"], event.get("result"))boxline run 'On https://books.toscrape.com, open the Travel category and find the books that cost less than £30. If more than one fits, don'\''t finish yet: ask me for help to choose, and wait until I hand the browser back with my choice. Then tell me the title and price of the one I chose.'# When the agent asks you, open the live view it shows, then press Enter here to resume the run.When it asks, answer in the box under the steps.
Accessibility check
Runs the axe accessibility scan on your page and lists the issues it finds, worst first.
Needs a script, a shell.
// An accessibility scan of one page with axe-core. Set SITE// in Variables and credentials to check your own page.const site = env.SITE || "https://books.toscrape.com";await page.goto(site, { waitUntil: "load" });// axe-core, loaded from its public CDN and run in the page.const axeUrl = "https://cdn.jsdelivr.net/npm/axe-core@4.10.2/axe.min.js";await page.evaluate(await (await fetch(axeUrl)).text());const { violations } = await page.evaluate(() => axe.run());const order = { critical: 0, serious: 1, moderate: 2, minor: 3 };const issues = violations .map((v) => ({ id: v.id, impact: v.impact, count: v.nodes.length, help: v.help })) .sort((a, b) => order[a.impact] - order[b.impact]);console.log(`${issues.length} kinds of issue on ${page.url()}`);console.table(issues);return issues;# 1. A session with a browser and a shell (scripts run next to the browser).curl -X POST https://api.boxline.dev/v1/sessions \ -H "x-api-key: $BOXLINE_API_KEY" \ -H "content-type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{"shell": true}'# 2. Run script.js in it (use the session's id; jq puts the file into JSON). The output streams back, one JSON line at a time.jq -n --rawfile code script.js '{code: $code, env: { SITE: "https://books.toscrape.com" }}' | 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 @-# 3. Stop it.curl -X POST https://api.boxline.dev/v1/sessions/$SESSION_ID/stop \ -H "x-api-key: $BOXLINE_API_KEY"import { readFile } from "node:fs/promises";import { Boxline } from "@boxline/sdk";const bx = new Boxline(); // reads BOXLINE_API_KEY and BOXLINE_API_URLconst session = await bx.sessions.create({ shell: true });try { const result = await session.runScript(await readFile("script.js", "utf8"), { env: { SITE: "https://books.toscrape.com" }, onData: (_stream, text) => process.stdout.write(text), }); console.log("exit code", result.exitCode);} finally { await session.stop();}from boxline import Boxlinebx = Boxline() # reads BOXLINE_API_KEY and BOXLINE_API_URLwith bx.sessions.create(shell=True) as session: # leaving the block stops it code = open("script.js").read() with session.run_script(code, env={"SITE": "https://books.toscrape.com"}) as script: for stream, text in script: print(text, end="") print("exit code", script.result["exitCode"])Dark pattern review
Goes through a demo shop's checkout (without placing the order) and points out confusing or deceptive steps.
Needs an AI task (agent run).
Open https://www.saucedemo.com, a demo shop made for testing, and sign in with the demo username and password shown on its page. Add one product to the cart and go through the checkout up to the last step before the order is placed, using the name Test User and the postcode 10115. Don't place the order. For each step, tell me if anything is confusing or deceptive: hidden costs, pre-ticked options, fake urgency, or a hard way back.# 1. Start the run (the task is the JSON's "task").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 @- <<'JSON'{ "task": "Open https://www.saucedemo.com, a demo shop made for testing, and sign in with the demo username and password shown on its page. Add one product to the cart and go through the checkout up to the last step before the order is placed, using the name Test User and the postcode 10115. Don't place the order. For each step, tell me if anything is confusing or deceptive: hidden costs, pre-ticked options, fake urgency, or a hard way back."}JSON# 2. Follow it: status, steps and result (use its id).curl https://api.boxline.dev/v1/agent/runs/$RUN_ID \ -H "x-api-key: $BOXLINE_API_KEY"import { Boxline } from "@boxline/sdk";const bx = new Boxline(); // reads BOXLINE_API_KEY and BOXLINE_API_URLconst run = await bx.agent.run({ task: "Open https://www.saucedemo.com, a demo shop made for testing, and sign in with the demo username and password shown on its page. Add one product to the cart and go through the checkout up to the last step before the order is placed, using the name Test User and the postcode 10115. Don't place the order. For each step, tell me if anything is confusing or deceptive: hidden costs, pre-ticked options, fake urgency, or a hard way back.",});const done = await bx.agent.wait(run.id); // polls until it finishesconsole.log(done.status, done.result);// Each step, with the agent's thought behind it.for (const s of done.steps) console.log(s.type, s.name ?? "", s.thought ?? s.text ?? "");from boxline import Boxlinebx = Boxline() # reads BOXLINE_API_KEY and BOXLINE_API_URLrun = bx.agent.run( "Open https://www.saucedemo.com, a demo shop made for testing, and sign in with the demo username and password shown on its page. Add one product to the cart and go through the checkout up to the last step before the order is placed, using the name Test User and the postcode 10115. Don't place the order. For each step, tell me if anything is confusing or deceptive: hidden costs, pre-ticked options, fake urgency, or a hard way back.",)done = bx.agent.wait(run["id"]) # polls until it finishesprint(done["status"], done["result"])for s in done["steps"]: print(s["type"], s.get("name", ""), s.get("thought") or s.get("text", "")) # with the agent's thoughtboxline run 'Open https://www.saucedemo.com, a demo shop made for testing, and sign in with the demo username and password shown on its page. Add one product to the cart and go through the checkout up to the last step before the order is placed, using the name Test User and the postcode 10115. Don'\''t place the order. For each step, tell me if anything is confusing or deceptive: hidden costs, pre-ticked options, fake urgency, or a hard way back.'Public registry lookup
Looks a company up in the UK's public company register and gives the record as JSON.
Needs an AI task (agent run).
Look up the company ARM LIMITED on the UK Companies House register (https://find-and-update.company-information.service.gov.uk) and give me, as JSON: its company number, status, incorporation date and registered office address.# 1. Start the run (the task is the JSON's "task").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 @- <<'JSON'{ "task": "Look up the company ARM LIMITED on the UK Companies House register (https://find-and-update.company-information.service.gov.uk) and give me, as JSON: its company number, status, incorporation date and registered office address."}JSON# 2. Follow it: status, steps and result (use its id).curl https://api.boxline.dev/v1/agent/runs/$RUN_ID \ -H "x-api-key: $BOXLINE_API_KEY"import { Boxline } from "@boxline/sdk";const bx = new Boxline(); // reads BOXLINE_API_KEY and BOXLINE_API_URLconst run = await bx.agent.run({ task: "Look up the company ARM LIMITED on the UK Companies House register (https://find-and-update.company-information.service.gov.uk) and give me, as JSON: its company number, status, incorporation date and registered office address.",});const done = await bx.agent.wait(run.id); // polls until it finishesconsole.log(done.status, done.result);// Each step, with the agent's thought behind it.for (const s of done.steps) console.log(s.type, s.name ?? "", s.thought ?? s.text ?? "");from boxline import Boxlinebx = Boxline() # reads BOXLINE_API_KEY and BOXLINE_API_URLrun = bx.agent.run( "Look up the company ARM LIMITED on the UK Companies House register (https://find-and-update.company-information.service.gov.uk) and give me, as JSON: its company number, status, incorporation date and registered office address.",)done = bx.agent.wait(run["id"]) # polls until it finishesprint(done["status"], done["result"])for s in done["steps"]: print(s["type"], s.get("name", ""), s.get("thought") or s.get("text", "")) # with the agent's thoughtboxline run 'Look up the company ARM LIMITED on the UK Companies House register (https://find-and-update.company-information.service.gov.uk) and give me, as JSON: its company number, status, incorporation date and registered office address.'Change ARM LIMITED to the company you want.
Company details from its legal pages
Finds the legal name and postal address of the company behind a site, in its terms or privacy pages.
Needs an AI task (agent run).
Find the legal name and postal address of the organisation behind https://www.eff.org. Look in its terms, privacy or imprint pages (linked at the bottom of most pages). Give them as JSON, with the page you found them on.# 1. Start the run (the task is the JSON's "task").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 @- <<'JSON'{ "task": "Find the legal name and postal address of the organisation behind https://www.eff.org. Look in its terms, privacy or imprint pages (linked at the bottom of most pages). Give them as JSON, with the page you found them on."}JSON# 2. Follow it: status, steps and result (use its id).curl https://api.boxline.dev/v1/agent/runs/$RUN_ID \ -H "x-api-key: $BOXLINE_API_KEY"import { Boxline } from "@boxline/sdk";const bx = new Boxline(); // reads BOXLINE_API_KEY and BOXLINE_API_URLconst run = await bx.agent.run({ task: "Find the legal name and postal address of the organisation behind https://www.eff.org. Look in its terms, privacy or imprint pages (linked at the bottom of most pages). Give them as JSON, with the page you found them on.",});const done = await bx.agent.wait(run.id); // polls until it finishesconsole.log(done.status, done.result);// Each step, with the agent's thought behind it.for (const s of done.steps) console.log(s.type, s.name ?? "", s.thought ?? s.text ?? "");from boxline import Boxlinebx = Boxline() # reads BOXLINE_API_KEY and BOXLINE_API_URLrun = bx.agent.run( "Find the legal name and postal address of the organisation behind https://www.eff.org. Look in its terms, privacy or imprint pages (linked at the bottom of most pages). Give them as JSON, with the page you found them on.",)done = bx.agent.wait(run["id"]) # polls until it finishesprint(done["status"], done["result"])for s in done["steps"]: print(s["type"], s.get("name", ""), s.get("thought") or s.get("text", "")) # with the agent's thoughtboxline run 'Find the legal name and postal address of the organisation behind https://www.eff.org. Look in its terms, privacy or imprint pages (linked at the bottom of most pages). Give them as JSON, with the page you found them on.'Computer use
A computer-use model drives the browser by screenshots and the mouse.
Needs an AI task (agent run), computer mode (a model with a computer-use tool).
Open https://books.toscrape.com and add the cheapest poetry book to the basket.# 1. Start the run (the task is the JSON's "task").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 @- <<'JSON'{ "task": "Open https://books.toscrape.com and add the cheapest poetry book to the basket.", "mode": "computer"}JSON# 2. Follow it: status, steps and result (use its id).curl https://api.boxline.dev/v1/agent/runs/$RUN_ID \ -H "x-api-key: $BOXLINE_API_KEY"import { Boxline } from "@boxline/sdk";const bx = new Boxline(); // reads BOXLINE_API_KEY and BOXLINE_API_URLconst run = await bx.agent.run({ task: "Open https://books.toscrape.com and add the cheapest poetry book to the basket.", mode: "computer",});const done = await bx.agent.wait(run.id); // polls until it finishesconsole.log(done.status, done.result);// Each step, with the agent's thought behind it.for (const s of done.steps) console.log(s.type, s.name ?? "", s.thought ?? s.text ?? "");from boxline import Boxlinebx = Boxline() # reads BOXLINE_API_KEY and BOXLINE_API_URLrun = bx.agent.run( "Open https://books.toscrape.com and add the cheapest poetry book to the basket.", mode="computer",)done = bx.agent.wait(run["id"]) # polls until it finishesprint(done["status"], done["result"])for s in done["steps"]: print(s["type"], s.get("name", ""), s.get("thought") or s.get("text", "")) # with the agent's thoughtBrowser + shell
Download and add up
Downloads a CSV report, adds up its revenue column with Python in the shell, saves output/total.txt, and types the total into a form.
Needs an AI task (agent run), a shell.
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 form's first text box, submit it, and tell me what the page says.# 1. Start the run (the task is the JSON's "task").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 @- <<'JSON'{ "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 form's first text box, submit it, and tell me what the page says.", "session": { "shell": true }}JSON# 2. Follow it: status, steps and result (use its id).curl https://api.boxline.dev/v1/agent/runs/$RUN_ID \ -H "x-api-key: $BOXLINE_API_KEY"import { Boxline } from "@boxline/sdk";const bx = new Boxline(); // reads BOXLINE_API_KEY and BOXLINE_API_URLconst run = 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 form's first text box, submit it, and tell me what the page says.", session: { shell: true },});const done = await bx.agent.wait(run.id); // polls until it finishesconsole.log(done.status, done.result);// Each step, with the agent's thought behind it.for (const s of done.steps) console.log(s.type, s.name ?? "", s.thought ?? s.text ?? "");from boxline import Boxlinebx = Boxline() # reads BOXLINE_API_KEY and BOXLINE_API_URLrun = bx.agent.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 form's first text box, submit it, and tell me what the page says.", session={"shell": True},)done = bx.agent.wait(run["id"]) # polls until it finishesprint(done["status"], done["result"])for s in done["steps"]: print(s["type"], s.get("name", ""), s.get("thought") or s.get("text", "")) # 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 form'\''s first text box, submit it, and tell me what the page says.' --shellDownload and add up, in code
The same in code: downloads a CSV, adds it up with Python (child_process), reads the result with fs, and has the AI type it into a form with step().
Needs a script, a shell.
Before you run it, replace env.REPORT_URL with a page with a CSV link.
// Download a CSV, add it up with Python in the shell, and// type the total into a form. One AI step; the rest is code.const { execSync } = require("child_process");const fs = require("fs");// Downloads land in /workspace/downloads, next to the shell.async function download(click) { const cdp = await browser.newBrowserCDPSession(); await cdp.send("Browser.setDownloadBehavior", { behavior: "allow", downloadPath: "/workspace/downloads", eventsEnabled: true, }); let name = ""; cdp.on("Browser.downloadWillBegin", (e) => (name = e.suggestedFilename)); const done = new Promise((ok, fail) => cdp.on("Browser.downloadProgress", (e) => { if (e.state === "completed") ok(); if (e.state === "canceled") fail(new Error("download canceled")); }), ); await click(); await done; await cdp.detach(); return `downloads/${name}`;}// 1. The report. Set REPORT_URL (a page with a CSV link) in// Variables and credentials; without it, a demo page is made.if (env.REPORT_URL) { await page.goto(env.REPORT_URL);} else { const csv = "month,revenue\nJan,1200\nFeb,1850\nMar,2410"; await page.setContent(`<h1>Quarterly report</h1> <a download="report.csv" href="data:text/csv,${encodeURIComponent(csv)}"> Download report</a>`);}const file = await download(() => page.getByRole("link", { name: /download/i }).first().click(),);console.log("Downloaded", file);// 2. Add up the revenue column with Python, in the shell.fs.mkdirSync("output", { recursive: true });execSync( `python3 - "${file}" <<'PY'import csv, sysrows = csv.DictReader(open(sys.argv[1]))total = sum(float(r["revenue"]) for r in rows)open("output/total.txt", "w").write(f"{total:g}")PY`, { stdio: "inherit" },);const total = fs.readFileSync("output/total.txt", "utf8").trim();console.log("Total revenue:", total);// 3. The AI types it into the form; code submits it.await page.goto(env.FORM_URL || "https://httpbin.org/forms/post");await step(`type ${total} into the first text box of the form`);const before = page.url();await page.locator("form button, form [type=submit]").first().click();await page.waitForURL((u) => u.href !== before);return (await page.locator("body").innerText()).slice(0, 400);# 1. A session with a browser and a shell (scripts run next to the browser).curl -X POST https://api.boxline.dev/v1/sessions \ -H "x-api-key: $BOXLINE_API_KEY" \ -H "content-type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{"shell": true}'# 2. Run script.js in it (use the session's id; jq puts the file into JSON). The output streams back, one JSON line at a time.jq -n --rawfile code script.js '{code: $code, env: { REPORT_URL: "https://example.com", FORM_URL: "https://httpbin.org/forms/post" }}' | 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 @-# 3. Stop it.curl -X POST https://api.boxline.dev/v1/sessions/$SESSION_ID/stop \ -H "x-api-key: $BOXLINE_API_KEY"import { readFile } from "node:fs/promises";import { Boxline } from "@boxline/sdk";const bx = new Boxline(); // reads BOXLINE_API_KEY and BOXLINE_API_URLconst session = await bx.sessions.create({ shell: true });try { const result = await session.runScript(await readFile("script.js", "utf8"), { env: { REPORT_URL: "https://example.com", FORM_URL: "https://httpbin.org/forms/post" }, onData: (_stream, text) => process.stdout.write(text), }); console.log("exit code", result.exitCode);} finally { await session.stop();}from boxline import Boxlinebx = Boxline() # reads BOXLINE_API_KEY and BOXLINE_API_URLwith bx.sessions.create(shell=True) as session: # leaving the block stops it code = open("script.js").read() with session.run_script(code, env={"REPORT_URL": "https://example.com", "FORM_URL": "https://httpbin.org/forms/post"}) as script: for stream, text in script: print(text, end="") print("exit code", script.result["exitCode"])PDF to summary
Downloads a PDF, turns it into text with pdftotext in the shell, and sums it up in 5 bullet points.
Needs an AI task (agent run), a shell.
Download the PDF at https://arxiv.org/pdf/1706.03762 into the downloads folder, turn it into text with pdftotext in the shell, and summarise it in 5 short bullet points.# 1. Start the run (the task is the JSON's "task").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 @- <<'JSON'{ "task": "Download the PDF at https://arxiv.org/pdf/1706.03762 into the downloads folder, turn it into text with pdftotext in the shell, and summarise it in 5 short bullet points.", "session": { "shell": true }}JSON# 2. Follow it: status, steps and result (use its id).curl https://api.boxline.dev/v1/agent/runs/$RUN_ID \ -H "x-api-key: $BOXLINE_API_KEY"import { Boxline } from "@boxline/sdk";const bx = new Boxline(); // reads BOXLINE_API_KEY and BOXLINE_API_URLconst run = await bx.agent.run({ task: "Download the PDF at https://arxiv.org/pdf/1706.03762 into the downloads folder, turn it into text with pdftotext in the shell, and summarise it in 5 short bullet points.", session: { shell: true },});const done = await bx.agent.wait(run.id); // polls until it finishesconsole.log(done.status, done.result);// Each step, with the agent's thought behind it.for (const s of done.steps) console.log(s.type, s.name ?? "", s.thought ?? s.text ?? "");from boxline import Boxlinebx = Boxline() # reads BOXLINE_API_KEY and BOXLINE_API_URLrun = bx.agent.run( "Download the PDF at https://arxiv.org/pdf/1706.03762 into the downloads folder, turn it into text with pdftotext in the shell, and summarise it in 5 short bullet points.", session={"shell": True},)done = bx.agent.wait(run["id"]) # polls until it finishesprint(done["status"], done["result"])for s in done["steps"]: print(s["type"], s.get("name", ""), s.get("thought") or s.get("text", "")) # with the agent's thoughtboxline run 'Download the PDF at https://arxiv.org/pdf/1706.03762 into the downloads folder, turn it into text with pdftotext in the shell, and summarise it in 5 short bullet points.' --shellScreenshots to a PDF report
Takes screenshots of 3 pages and combines them into one PDF report with Python (Pillow) in the shell.
Needs a script, a shell.
// Screenshots of 3 pages, combined into one PDF report// with Python (Pillow is installed in the shell).const { execSync } = require("child_process");const fs = require("fs");const urls = [ "https://books.toscrape.com", "https://quotes.toscrape.com", "https://httpbin.org",];fs.mkdirSync("output", { recursive: true });const shots = [];for (const [i, url] of urls.entries()) { await page.goto(url, { waitUntil: "load" }); const file = `output/shot-${i + 1}.png`; await page.screenshot({ path: file }); shots.push(file); console.log("Saved", file, "·", await page.title());}execSync( `python3 - ${shots.join(" ")} <<'PY'import sysfrom PIL import Imagepages = [Image.open(p).convert("RGB") for p in sys.argv[1:]]pages[0].save("output/report.pdf", save_all=True, append_images=pages[1:])PY`, { stdio: "inherit" },);const kb = Math.round(fs.statSync("output/report.pdf").size / 1024);return `output/report.pdf: ${shots.length} pages, ${kb} KB`;# 1. A session with a browser and a shell (scripts run next to the browser).curl -X POST https://api.boxline.dev/v1/sessions \ -H "x-api-key: $BOXLINE_API_KEY" \ -H "content-type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{"shell": true}'# 2. Run script.js in it (use the session's id; jq puts the file into JSON). The output streams back, one JSON line at a time.jq -n --rawfile code script.js '{code: $code}' | 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 @-# 3. Stop it.curl -X POST https://api.boxline.dev/v1/sessions/$SESSION_ID/stop \ -H "x-api-key: $BOXLINE_API_KEY"import { readFile } from "node:fs/promises";import { Boxline } from "@boxline/sdk";const bx = new Boxline(); // reads BOXLINE_API_KEY and BOXLINE_API_URLconst session = await bx.sessions.create({ shell: true });try { const result = await session.runScript(await readFile("script.js", "utf8"), { onData: (_stream, text) => process.stdout.write(text), }); console.log("exit code", result.exitCode);} finally { await session.stop();}from boxline import Boxlinebx = Boxline() # reads BOXLINE_API_KEY and BOXLINE_API_URLwith bx.sessions.create(shell=True) as session: # leaving the block stops it code = open("script.js").read() with session.run_script(code) as script: for stream, text in script: print(text, end="") print("exit code", script.result["exitCode"])In code, because the AI's screenshots are for its own eyes: it can't save them as files.
Video clip
Installs yt-dlp, downloads a video you're allowed to download, and cuts its first 30 seconds into output/clip.mp4 with ffmpeg.
Needs an AI task (agent run), a shell.
In the shell, install yt-dlp (pip install yt-dlp), download https://archive.org/download/BigBuckBunny_328/BigBuckBunny_512kb.mp4 with it, and cut the first 30 seconds into output/clip.mp4 with ffmpeg. Tell me the clip's size and length. If the site refuses the download, stop and tell me: don't try to get around it.# 1. Start the run (the task is the JSON's "task").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 @- <<'JSON'{ "task": "In the shell, install yt-dlp (pip install yt-dlp), download https://archive.org/download/BigBuckBunny_328/BigBuckBunny_512kb.mp4 with it, and cut the first 30 seconds into output/clip.mp4 with ffmpeg. Tell me the clip's size and length. If the site refuses the download, stop and tell me: don't try to get around it.", "session": { "shell": true }}JSON# 2. Follow it: status, steps and result (use its id).curl https://api.boxline.dev/v1/agent/runs/$RUN_ID \ -H "x-api-key: $BOXLINE_API_KEY"import { Boxline } from "@boxline/sdk";const bx = new Boxline(); // reads BOXLINE_API_KEY and BOXLINE_API_URLconst run = await bx.agent.run({ task: "In the shell, install yt-dlp (pip install yt-dlp), download https://archive.org/download/BigBuckBunny_328/BigBuckBunny_512kb.mp4 with it, and cut the first 30 seconds into output/clip.mp4 with ffmpeg. Tell me the clip's size and length. If the site refuses the download, stop and tell me: don't try to get around it.", session: { shell: true },});const done = await bx.agent.wait(run.id); // polls until it finishesconsole.log(done.status, done.result);// Each step, with the agent's thought behind it.for (const s of done.steps) console.log(s.type, s.name ?? "", s.thought ?? s.text ?? "");from boxline import Boxlinebx = Boxline() # reads BOXLINE_API_KEY and BOXLINE_API_URLrun = bx.agent.run( "In the shell, install yt-dlp (pip install yt-dlp), download https://archive.org/download/BigBuckBunny_328/BigBuckBunny_512kb.mp4 with it, and cut the first 30 seconds into output/clip.mp4 with ffmpeg. Tell me the clip's size and length. If the site refuses the download, stop and tell me: don't try to get around it.", session={"shell": True},)done = bx.agent.wait(run["id"]) # polls until it finishesprint(done["status"], done["result"])for s in done["steps"]: print(s["type"], s.get("name", ""), s.get("thought") or s.get("text", "")) # with the agent's thoughtboxline run 'In the shell, install yt-dlp (pip install yt-dlp), download https://archive.org/download/BigBuckBunny_328/BigBuckBunny_512kb.mp4 with it, and cut the first 30 seconds into output/clip.mp4 with ffmpeg. Tell me the clip'\''s size and length. If the site refuses the download, stop and tell me: don'\''t try to get around it.' --shellOnly videos you own or have the rights to, or ones with an open licence.
Fill a form from a spreadsheet
Reads the rows of a CSV with Python in the shell and fills one form per row, asking you to check the first one before it submits.
Needs an AI task (agent run), a shell.
In the shell, download the spreadsheet at https://httpbin.org/base64/bmFtZSxwaG9uZSxlbWFpbApBZGEgTG92ZWxhY2UsKzEgNTU1IDAxMDAsYWRhQGV4YW1wbGUuY29tCkFsYW4gVHVyaW5nLCsxIDU1NSAwMTAxLGFsYW5AZXhhbXBsZS5jb20KR3JhY2UgSG9wcGVyLCsxIDU1NSAwMTAyLGdyYWNlQGV4YW1wbGUuY29tCg== and read its rows with Python. For each of the first 3 rows, open https://httpbin.org/forms/post and fill in the customer name, telephone and email from that row. Before you submit the first one, ask me to check it and wait; after I hand the browser back, submit it and do the other rows the same way without asking again. At the end, tell me what the page said after each submit.# 1. Start the run (the task is the JSON's "task").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 @- <<'JSON'{ "task": "In the shell, download the spreadsheet at https://httpbin.org/base64/bmFtZSxwaG9uZSxlbWFpbApBZGEgTG92ZWxhY2UsKzEgNTU1IDAxMDAsYWRhQGV4YW1wbGUuY29tCkFsYW4gVHVyaW5nLCsxIDU1NSAwMTAxLGFsYW5AZXhhbXBsZS5jb20KR3JhY2UgSG9wcGVyLCsxIDU1NSAwMTAyLGdyYWNlQGV4YW1wbGUuY29tCg== and read its rows with Python. For each of the first 3 rows, open https://httpbin.org/forms/post and fill in the customer name, telephone and email from that row. Before you submit the first one, ask me to check it and wait; after I hand the browser back, submit it and do the other rows the same way without asking again. At the end, tell me what the page said after each submit.", "session": { "shell": true }}JSON# 2. Follow it: status, steps and result (use its id).curl https://api.boxline.dev/v1/agent/runs/$RUN_ID \ -H "x-api-key: $BOXLINE_API_KEY"# When status is "paused", the agent is asking you. Answer in the live view, or in the note, and resume the run:curl -X POST https://api.boxline.dev/v1/agent/runs/$RUN_ID/resume \ -H "x-api-key: $BOXLINE_API_KEY" \ -H "content-type: application/json" \ -d '{"note": "Done, carry on."}'import { createInterface } from "node:readline/promises";import { Boxline } from "@boxline/sdk";const bx = new Boxline(); // reads BOXLINE_API_KEY and BOXLINE_API_URLconst run = await bx.agent.run({ task: "In the shell, download the spreadsheet at https://httpbin.org/base64/bmFtZSxwaG9uZSxlbWFpbApBZGEgTG92ZWxhY2UsKzEgNTU1IDAxMDAsYWRhQGV4YW1wbGUuY29tCkFsYW4gVHVyaW5nLCsxIDU1NSAwMTAxLGFsYW5AZXhhbXBsZS5jb20KR3JhY2UgSG9wcGVyLCsxIDU1NSAwMTAyLGdyYWNlQGV4YW1wbGUuY29tCg== and read its rows with Python. For each of the first 3 rows, open https://httpbin.org/forms/post and fill in the customer name, telephone and email from that row. Before you submit the first one, ask me to check it and wait; after I hand the browser back, submit it and do the other rows the same way without asking again. At the end, tell me what the page said after each submit.", session: { shell: true },});// Watch the run. When it pauses, the agent is asking you: answer, then resume it.const rl = createInterface({ input: process.stdin, output: process.stdout });for await (const event of bx.agent.stream(run.id)) { if (event.type === "thought") console.log("…", event.text); if (event.type === "tool") console.log(event.name); if (event.type === "status" && event.status === "paused" && event.by !== "captcha") { const { liveUrl } = await bx.sessions.get(run.sessionId); // a fresh signed link: keep it private console.log("The agent needs you:", event.reason ?? "", "\nLive view:", liveUrl); await bx.agent.resume(run.id, { note: await rl.question("Your answer (Enter when done): ") }); } if (event.type === "done") console.log(event.status, event.result);}rl.close();from boxline import Boxlinebx = Boxline() # reads BOXLINE_API_KEY and BOXLINE_API_URLrun = bx.agent.run( "In the shell, download the spreadsheet at https://httpbin.org/base64/bmFtZSxwaG9uZSxlbWFpbApBZGEgTG92ZWxhY2UsKzEgNTU1IDAxMDAsYWRhQGV4YW1wbGUuY29tCkFsYW4gVHVyaW5nLCsxIDU1NSAwMTAxLGFsYW5AZXhhbXBsZS5jb20KR3JhY2UgSG9wcGVyLCsxIDU1NSAwMTAyLGdyYWNlQGV4YW1wbGUuY29tCg== and read its rows with Python. For each of the first 3 rows, open https://httpbin.org/forms/post and fill in the customer name, telephone and email from that row. Before you submit the first one, ask me to check it and wait; after I hand the browser back, submit it and do the other rows the same way without asking again. At the end, tell me what the page said after each submit.", session={"shell": True},)# Watch the run. When it pauses, the agent is asking you: answer, then resume it.for event in bx.agent.stream(run["id"]): if event["type"] == "thought": print("…", event["text"]) elif event["type"] == "tool": print(event["name"]) elif event["type"] == "status" and event["status"] == "paused" and event.get("by") != "captcha": live_url = bx.sessions.get(run["sessionId"]).live_url # a fresh signed link: keep it private print("The agent needs you:", event.get("reason") or "", "\nLive view:", live_url) bx.agent.resume(run["id"], note=input("Your answer (Enter when done): ")) elif event["type"] == "done": print(event["status"], event.get("result"))boxline run 'In the shell, download the spreadsheet at https://httpbin.org/base64/bmFtZSxwaG9uZSxlbWFpbApBZGEgTG92ZWxhY2UsKzEgNTU1IDAxMDAsYWRhQGV4YW1wbGUuY29tCkFsYW4gVHVyaW5nLCsxIDU1NSAwMTAxLGFsYW5AZXhhbXBsZS5jb20KR3JhY2UgSG9wcGVyLCsxIDU1NSAwMTAyLGdyYWNlQGV4YW1wbGUuY29tCg== and read its rows with Python. For each of the first 3 rows, open https://httpbin.org/forms/post and fill in the customer name, telephone and email from that row. Before you submit the first one, ask me to check it and wait; after I hand the browser back, submit it and do the other rows the same way without asking again. At the end, tell me what the page said after each submit.' --shell# When the agent asks you, open the live view it shows, then press Enter here to resume the run.The demo form only shows what it received.
Data out
Product page to JSON
Reads a product page into JSON with extract() and a schema, and saves it to output/product.json.
Needs a script, a shell.
Before you run it, replace env.URL with a product page.
// A product page as JSON: extract() reads it with the AI,// the schema says what comes back. Set URL in Variables and// credentials to use your own product page.const fs = require("fs");const url = env.URL || "https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html";await page.goto(url, { waitUntil: "domcontentloaded" });const product = await extract("the product on this page", { type: "object", properties: { title: { type: "string" }, price: { type: "number" }, currency: { type: "string" }, inStock: { type: "boolean" }, rating: { type: "string" }, }, required: ["title", "price"],});// A copy in /workspace/output (see Files).fs.mkdirSync("output", { recursive: true });fs.writeFileSync("output/product.json", JSON.stringify(product, null, 2));return product;# 1. A session with a browser and a shell (scripts run next to the browser).curl -X POST https://api.boxline.dev/v1/sessions \ -H "x-api-key: $BOXLINE_API_KEY" \ -H "content-type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{"shell": true}'# 2. Run script.js in it (use the session's id; jq puts the file into JSON). The output streams back, one JSON line at a time.jq -n --rawfile code script.js '{code: $code, env: { URL: "https://example.com" }}' | 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 @-# 3. Stop it.curl -X POST https://api.boxline.dev/v1/sessions/$SESSION_ID/stop \ -H "x-api-key: $BOXLINE_API_KEY"import { readFile } from "node:fs/promises";import { Boxline } from "@boxline/sdk";const bx = new Boxline(); // reads BOXLINE_API_KEY and BOXLINE_API_URLconst session = await bx.sessions.create({ shell: true });try { const result = await session.runScript(await readFile("script.js", "utf8"), { env: { URL: "https://example.com" }, onData: (_stream, text) => process.stdout.write(text), }); console.log("exit code", result.exitCode);} finally { await session.stop();}from boxline import Boxlinebx = Boxline() # reads BOXLINE_API_KEY and BOXLINE_API_URLwith bx.sessions.create(shell=True) as session: # leaving the block stops it code = open("script.js").read() with session.run_script(code, env={"URL": "https://example.com"}) as script: for stream, text in script: print(text, end="") print("exit code", script.result["exitCode"])Site to Markdown
Reads pages as clean Markdown in the browser and saves each one to output/ in the shell, ready for AI search.
Needs an AI task (agent run), a shell.
Read https://quotes.toscrape.com/ and https://quotes.toscrape.com/page/2/ as Markdown. Save them in the shell as output/page-1.md and output/page-2.md, then list the files with their sizes.# 1. Start the run (the task is the JSON's "task").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 @- <<'JSON'{ "task": "Read https://quotes.toscrape.com/ and https://quotes.toscrape.com/page/2/ as Markdown. Save them in the shell as output/page-1.md and output/page-2.md, then list the files with their sizes.", "session": { "shell": true }}JSON# 2. Follow it: status, steps and result (use its id).curl https://api.boxline.dev/v1/agent/runs/$RUN_ID \ -H "x-api-key: $BOXLINE_API_KEY"import { Boxline } from "@boxline/sdk";const bx = new Boxline(); // reads BOXLINE_API_KEY and BOXLINE_API_URLconst run = await bx.agent.run({ task: "Read https://quotes.toscrape.com/ and https://quotes.toscrape.com/page/2/ as Markdown. Save them in the shell as output/page-1.md and output/page-2.md, then list the files with their sizes.", session: { shell: true },});const done = await bx.agent.wait(run.id); // polls until it finishesconsole.log(done.status, done.result);// Each step, with the agent's thought behind it.for (const s of done.steps) console.log(s.type, s.name ?? "", s.thought ?? s.text ?? "");from boxline import Boxlinebx = Boxline() # reads BOXLINE_API_KEY and BOXLINE_API_URLrun = bx.agent.run( "Read https://quotes.toscrape.com/ and https://quotes.toscrape.com/page/2/ as Markdown. Save them in the shell as output/page-1.md and output/page-2.md, then list the files with their sizes.", session={"shell": True},)done = bx.agent.wait(run["id"]) # polls until it finishesprint(done["status"], done["result"])for s in done["steps"]: print(s["type"], s.get("name", ""), s.get("thought") or s.get("text", "")) # with the agent's thoughtboxline run 'Read https://quotes.toscrape.com/ and https://quotes.toscrape.com/page/2/ as Markdown. Save them in the shell as output/page-1.md and output/page-2.md, then list the files with their sizes.' --shellLink checker
Checks the links on a page of your site (up to 40) and lists the broken and the slow ones.
Needs a script, a shell.
// Broken and slow links on one page. Set SITE in Variables and// credentials to check your own page.const site = env.SITE || "https://books.toscrape.com";await page.goto(site, { waitUntil: "domcontentloaded" });const hrefs = await page.$$eval("a[href]", (as) => as.map((a) => a.href));const links = [...new Set(hrefs)] .filter((h) => h.startsWith("http")) .slice(0, 40);// One request at a time, to be polite to the site.const checked = [];for (const url of links) { const t0 = Date.now(); try { const res = await page.request.get(url, { timeout: 15000 }); checked.push({ url, status: res.status(), ms: Date.now() - t0 }); } catch (err) { const error = String(err.message || err).split("\n")[0]; checked.push({ url, status: null, error, ms: Date.now() - t0 }); }}const broken = checked.filter((l) => !l.status || l.status >= 400);const slow = checked.filter((l) => l.ms > 2000);console.log(`${checked.length} links: ${broken.length} broken, ${slow.length} slow`);return { checked: checked.length, broken, slow };# 1. A session with a browser and a shell (scripts run next to the browser).curl -X POST https://api.boxline.dev/v1/sessions \ -H "x-api-key: $BOXLINE_API_KEY" \ -H "content-type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{"shell": true}'# 2. Run script.js in it (use the session's id; jq puts the file into JSON). The output streams back, one JSON line at a time.jq -n --rawfile code script.js '{code: $code, env: { SITE: "https://books.toscrape.com" }}' | 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 @-# 3. Stop it.curl -X POST https://api.boxline.dev/v1/sessions/$SESSION_ID/stop \ -H "x-api-key: $BOXLINE_API_KEY"import { readFile } from "node:fs/promises";import { Boxline } from "@boxline/sdk";const bx = new Boxline(); // reads BOXLINE_API_KEY and BOXLINE_API_URLconst session = await bx.sessions.create({ shell: true });try { const result = await session.runScript(await readFile("script.js", "utf8"), { env: { SITE: "https://books.toscrape.com" }, onData: (_stream, text) => process.stdout.write(text), }); console.log("exit code", result.exitCode);} finally { await session.stop();}from boxline import Boxlinebx = Boxline() # reads BOXLINE_API_KEY and BOXLINE_API_URLwith bx.sessions.create(shell=True) as session: # leaving the block stops it code = open("script.js").read() with session.run_script(code, env={"SITE": "https://books.toscrape.com"}) as script: for stream, text in script: print(text, end="") print("exit code", script.result["exitCode"])SEO and metadata audit
Checks titles, descriptions, headings, Open Graph and canonical tags on your page and the pages it links to (up to 5).
Needs a script, a shell.
// Search and sharing metadata, page by page. Set SITE in// Variables and credentials to audit your own site.const site = env.SITE || "https://books.toscrape.com";await page.goto(site, { waitUntil: "domcontentloaded" });const origin = new URL(page.url()).origin;const next = await page.$$eval("a[href]", (as) => as.map((a) => a.href));const pages = [page.url(), ...new Set(next)] .filter((u) => u.startsWith(origin) && !u.includes("#")) .filter((u, i, all) => all.indexOf(u) === i) .slice(0, 6);const report = [];for (const url of pages) { await page.goto(url, { waitUntil: "domcontentloaded" }); const m = await page.evaluate(() => { const meta = (sel) => document.querySelector(sel)?.content?.trim() || ""; return { title: document.title.trim(), description: meta('meta[name="description"]'), h1: document.querySelectorAll("h1").length, ogTitle: meta('meta[property="og:title"]'), canonical: document.querySelector('link[rel="canonical"]')?.href || "", noAlt: [...document.images].filter((i) => !i.alt).length, }; }); const fixes = []; if (!m.title || m.title.length > 60) fixes.push("title missing or over 60 characters"); if (!m.description) fixes.push("no meta description"); if (m.h1 !== 1) fixes.push(`${m.h1} H1 headings (want 1)`); if (!m.ogTitle) fixes.push("no og:title"); if (!m.canonical) fixes.push("no canonical link"); if (m.noAlt) fixes.push(`${m.noAlt} images without alt text`); report.push({ url, ...m, fixes });}console.table(report.map((r) => ({ url: r.url, fixes: r.fixes.length })));return report;# 1. A session with a browser and a shell (scripts run next to the browser).curl -X POST https://api.boxline.dev/v1/sessions \ -H "x-api-key: $BOXLINE_API_KEY" \ -H "content-type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{"shell": true}'# 2. Run script.js in it (use the session's id; jq puts the file into JSON). The output streams back, one JSON line at a time.jq -n --rawfile code script.js '{code: $code, env: { SITE: "https://books.toscrape.com" }}' | 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 @-# 3. Stop it.curl -X POST https://api.boxline.dev/v1/sessions/$SESSION_ID/stop \ -H "x-api-key: $BOXLINE_API_KEY"import { readFile } from "node:fs/promises";import { Boxline } from "@boxline/sdk";const bx = new Boxline(); // reads BOXLINE_API_KEY and BOXLINE_API_URLconst session = await bx.sessions.create({ shell: true });try { const result = await session.runScript(await readFile("script.js", "utf8"), { env: { SITE: "https://books.toscrape.com" }, onData: (_stream, text) => process.stdout.write(text), }); console.log("exit code", result.exitCode);} finally { await session.stop();}from boxline import Boxlinebx = Boxline() # reads BOXLINE_API_KEY and BOXLINE_API_URLwith bx.sessions.create(shell=True) as session: # leaving the block stops it code = open("script.js").read() with session.run_script(code, env={"SITE": "https://books.toscrape.com"}) as script: for stream, text in script: print(text, end="") print("exit code", script.result["exitCode"])Download all images
Saves every image on a page into output/images in the workspace, then zips them in the shell.
Needs a script, a shell.
// Every image on a page, saved in /workspace/output/images and// zipped. Set URL in Variables and credentials for another page.const { execSync } = require("child_process");const fs = require("fs");const url = env.URL || "https://books.toscrape.com";await page.goto(url, { waitUntil: "load" });const srcs = await page.$$eval("img", (imgs) => imgs.map((i) => i.currentSrc || i.src),);const images = [...new Set(srcs)].filter((s) => s.startsWith("http"));fs.rmSync("output/images", { recursive: true, force: true });fs.mkdirSync("output/images", { recursive: true });let saved = 0;for (const [i, src] of images.slice(0, 100).entries()) { const res = await page.request.get(src); if (!res.ok()) continue; const ext = new URL(src).pathname.match(/\.(\w{2,4})$/)?.[1] ?? "img"; const name = `${String(i + 1).padStart(3, "0")}.${ext.toLowerCase()}`; fs.writeFileSync(`output/images/${name}`, await res.body()); saved += 1;}execSync("cd output && rm -f images.zip && zip -qr images.zip images");const kb = Math.round(fs.statSync("output/images.zip").size / 1024);return `${saved} images in output/images, zipped: output/images.zip (${kb} KB)`;# 1. A session with a browser and a shell (scripts run next to the browser).curl -X POST https://api.boxline.dev/v1/sessions \ -H "x-api-key: $BOXLINE_API_KEY" \ -H "content-type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{"shell": true}'# 2. Run script.js in it (use the session's id; jq puts the file into JSON). The output streams back, one JSON line at a time.jq -n --rawfile code script.js '{code: $code, env: { URL: "https://books.toscrape.com" }}' | 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 @-# 3. Stop it.curl -X POST https://api.boxline.dev/v1/sessions/$SESSION_ID/stop \ -H "x-api-key: $BOXLINE_API_KEY"import { readFile } from "node:fs/promises";import { Boxline } from "@boxline/sdk";const bx = new Boxline(); // reads BOXLINE_API_KEY and BOXLINE_API_URLconst session = await bx.sessions.create({ shell: true });try { const result = await session.runScript(await readFile("script.js", "utf8"), { env: { URL: "https://books.toscrape.com" }, onData: (_stream, text) => process.stdout.write(text), }); console.log("exit code", result.exitCode);} finally { await session.stop();}from boxline import Boxlinebx = Boxline() # reads BOXLINE_API_KEY and BOXLINE_API_URLwith bx.sessions.create(shell=True) as session: # leaving the block stops it code = open("script.js").read() with session.run_script(code, env={"URL": "https://books.toscrape.com"}) as script: for stream, text in script: print(text, end="") print("exit code", script.result["exitCode"])Security headers check
Checks your site's security headers and cookie flags and lists what is missing.
Needs a script, a shell.
// Security headers and cookie flags of one page. Set SITE in// Variables and credentials to check your own site.const site = env.SITE || "https://books.toscrape.com";const res = await page.request.get(site);const headers = res.headers();const wanted = { "strict-transport-security": "forces HTTPS", "content-security-policy": "limits scripts and frames", "x-content-type-options": "stops MIME sniffing", "referrer-policy": "limits what the Referer header shows", "permissions-policy": "turns off features the site doesn't use",};const missing = Object.keys(wanted).filter((h) => !headers[h]);const csp = headers["content-security-policy"] || "";if (!headers["x-frame-options"] && !csp.includes("frame-ancestors")) { missing.push("x-frame-options (or CSP frame-ancestors)");}const cookies = res .headersArray() .filter((h) => h.name.toLowerCase() === "set-cookie") .map((h) => ({ name: h.value.split("=")[0], secure: /;\s*secure/i.test(h.value), httpOnly: /;\s*httponly/i.test(h.value), sameSite: /samesite=(\w+)/i.exec(h.value)?.[1] ?? "not set", }));console.log(`${res.url()} answered ${res.status()}`);return { url: res.url(), status: res.status(), missing, cookies };# 1. A session with a browser and a shell (scripts run next to the browser).curl -X POST https://api.boxline.dev/v1/sessions \ -H "x-api-key: $BOXLINE_API_KEY" \ -H "content-type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{"shell": true}'# 2. Run script.js in it (use the session's id; jq puts the file into JSON). The output streams back, one JSON line at a time.jq -n --rawfile code script.js '{code: $code, env: { SITE: "https://books.toscrape.com" }}' | 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 @-# 3. Stop it.curl -X POST https://api.boxline.dev/v1/sessions/$SESSION_ID/stop \ -H "x-api-key: $BOXLINE_API_KEY"import { readFile } from "node:fs/promises";import { Boxline } from "@boxline/sdk";const bx = new Boxline(); // reads BOXLINE_API_KEY and BOXLINE_API_URLconst session = await bx.sessions.create({ shell: true });try { const result = await session.runScript(await readFile("script.js", "utf8"), { env: { SITE: "https://books.toscrape.com" }, onData: (_stream, text) => process.stdout.write(text), }); console.log("exit code", result.exitCode);} finally { await session.stop();}from boxline import Boxlinebx = Boxline() # reads BOXLINE_API_KEY and BOXLINE_API_URLwith bx.sessions.create(shell=True) as session: # leaving the block stops it code = open("script.js").read() with session.run_script(code, env={"SITE": "https://books.toscrape.com"}) as script: for stream, text in script: print(text, end="") print("exit code", script.result["exitCode"])Research a question
Searches the web, reads several sources and answers with citations.
Needs an AI task (agent run), web search.
Before you run it, replace <a question> with a question.
Search the web for <a question>, read the three best sources, and answer in 3 sentences with links to them.# 1. Start the run (the task is the JSON's "task").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 @- <<'JSON'{ "task": "Search the web for <a question>, read the three best sources, and answer in 3 sentences with links to them."}JSON# 2. Follow it: status, steps and result (use its id).curl https://api.boxline.dev/v1/agent/runs/$RUN_ID \ -H "x-api-key: $BOXLINE_API_KEY"import { Boxline } from "@boxline/sdk";const bx = new Boxline(); // reads BOXLINE_API_KEY and BOXLINE_API_URLconst run = await bx.agent.run({ task: "Search the web for <a question>, read the three best sources, and answer in 3 sentences with links to them.",});const done = await bx.agent.wait(run.id); // polls until it finishesconsole.log(done.status, done.result);// Each step, with the agent's thought behind it.for (const s of done.steps) console.log(s.type, s.name ?? "", s.thought ?? s.text ?? "");from boxline import Boxlinebx = Boxline() # reads BOXLINE_API_KEY and BOXLINE_API_URLrun = bx.agent.run( "Search the web for <a question>, read the three best sources, and answer in 3 sentences with links to them.",)done = bx.agent.wait(run["id"]) # polls until it finishesprint(done["status"], done["result"])for s in done["steps"]: print(s["type"], s.get("name", ""), s.get("thought") or s.get("text", "")) # with the agent's thoughtboxline run 'Search the web for <a question>, read the three best sources, and answer in 3 sentences with links to them.'Logins
Save a login once
Opens your site's sign-in page and hands the browser to you to sign in. When the browser stops, the browser profile keeps you signed in for next time.
Needs an AI task (agent run), a browser profile to sign in to.
Before you run it, replace <your sign-in page> with your sign-in page.
Open <your sign-in page> and ask me to sign in: I'll do it myself in the live browser. When I hand the browser back, check that I'm signed in and tell me the name shown on the page.# 1. A browser profile, and a session that keeps what happens in it (persist).curl -X POST https://api.boxline.dev/v1/profiles \ -H "x-api-key: $BOXLINE_API_KEY" \ -H "content-type: application/json" \ -d '{"name": "My site"}'curl -X POST https://api.boxline.dev/v1/sessions \ -H "x-api-key: $BOXLINE_API_KEY" \ -H "content-type: application/json" \ -d '{"keepAlive": true, "profile": {"id": "'"$PROFILE_ID"'", "persist": true}}'# 2. Start the run (the task is the JSON's "task").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 @- <<JSON{ "task": "Open <your sign-in page> and ask me to sign in: I'll do it myself in the live browser. When I hand the browser back, check that I'm signed in and tell me the name shown on the page.", "sessionId": "$SESSION_ID"}JSON# 3. Follow it: status, steps and result (use its id).curl https://api.boxline.dev/v1/agent/runs/$RUN_ID \ -H "x-api-key: $BOXLINE_API_KEY"# When status is "paused", the agent is asking you. Answer in the live view, or in the note, and resume the run:curl -X POST https://api.boxline.dev/v1/agent/runs/$RUN_ID/resume \ -H "x-api-key: $BOXLINE_API_KEY" \ -H "content-type: application/json" \ -d '{"note": "Done, carry on."}'# 4. Stop the session when you are done: this saves the profile.curl -X POST https://api.boxline.dev/v1/sessions/$SESSION_ID/stop \ -H "x-api-key: $BOXLINE_API_KEY"import { createInterface } from "node:readline/promises";import { Boxline } from "@boxline/sdk";const bx = new Boxline(); // reads BOXLINE_API_KEY and BOXLINE_API_URLconst profile = await bx.profiles.create({ name: "My site" });const session = await bx.sessions.create({ keepAlive: true, profile: { id: profile.id, persist: true } });const run = await bx.agent.run({ task: "Open <your sign-in page> and ask me to sign in: I'll do it myself in the live browser. When I hand the browser back, check that I'm signed in and tell me the name shown on the page.", sessionId: session.id,});// Watch the run. When it pauses, the agent is asking you: answer, then resume it.const rl = createInterface({ input: process.stdin, output: process.stdout });for await (const event of bx.agent.stream(run.id)) { if (event.type === "thought") console.log("…", event.text); if (event.type === "tool") console.log(event.name); if (event.type === "status" && event.status === "paused" && event.by !== "captcha") { const { liveUrl } = await bx.sessions.get(run.sessionId); // a fresh signed link: keep it private console.log("The agent needs you:", event.reason ?? "", "\nLive view:", liveUrl); await bx.agent.resume(run.id, { note: await rl.question("Your answer (Enter when done): ") }); } if (event.type === "done") console.log(event.status, event.result);}rl.close();await bx.sessions.stop(session.id); // this saves the profilefrom boxline import Boxlinebx = Boxline() # reads BOXLINE_API_KEY and BOXLINE_API_URLprofile = bx.profiles.create("My site")# Leaving the block stops the session, which saves the profile.with bx.sessions.create(keep_alive=True, profile=profile["id"], persist_profile=True) as session: run = bx.agent.run( "Open <your sign-in page> and ask me to sign in: I'll do it myself in the live browser. When I hand the browser back, check that I'm signed in and tell me the name shown on the page.", session_id=session.id, ) # Watch the run. When it pauses, the agent is asking you: answer, then resume it. for event in bx.agent.stream(run["id"]): if event["type"] == "thought": print("…", event["text"]) elif event["type"] == "tool": print(event["name"]) elif event["type"] == "status" and event["status"] == "paused" and event.get("by") != "captcha": live_url = bx.sessions.get(run["sessionId"]).live_url # a fresh signed link: keep it private print("The agent needs you:", event.get("reason") or "", "\nLive view:", live_url) bx.agent.resume(run["id"], note=input("Your answer (Enter when done): ")) elif event["type"] == "done": print(event["status"], event.get("result"))# In a session that saves the browser profile when it stops (start it as in the curl tab: the CLI has no browser-profile option).boxline run 'Open <your sign-in page> and ask me to sign in: I'\''ll do it myself in the live browser. When I hand the browser back, check that I'\''m signed in and tell me the name shown on the page.' --session $SESSION_IDboxline sessions stop $SESSION_ID # this saves the profileStop the browser when you're done: the sign-in is saved when it stops.
Hand over for a code
Signs in with your username and password from Variables and credentials; when the site asks for a code (SMS or an app), the AI asks you for it, then carries on.
Needs an AI task (agent run), values for %username%, %password%.
Before you run it, replace <your sign-in page> with your sign-in page.
Open <your sign-in page> and sign in with the username %username% and the password %password%. When the site asks for a verification code, ask me for it and wait, then type the code I give you. When I'm signed in, tell me the name shown on the page.# 1. Start the run (the task is the JSON's "task").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 @- <<JSON{ "task": "Open <your sign-in page> and sign in with the username %username% and the password %password%. When the site asks for a verification code, ask me for it and wait, then type the code I give you. When I'm signed in, tell me the name shown on the page.", "variables": { "username": "$SITE_USERNAME", "password": { "value": "$SITE_PASSWORD", "origins": [ "https://example.com" ] } }}JSON# 2. Follow it: status, steps and result (use its id).curl https://api.boxline.dev/v1/agent/runs/$RUN_ID \ -H "x-api-key: $BOXLINE_API_KEY"# When status is "paused", the agent is asking you. Answer in the live view, or in the note, and resume the run:curl -X POST https://api.boxline.dev/v1/agent/runs/$RUN_ID/resume \ -H "x-api-key: $BOXLINE_API_KEY" \ -H "content-type: application/json" \ -d '{"note": "Done, carry on."}'import { createInterface } from "node:readline/promises";import { Boxline } from "@boxline/sdk";const bx = new Boxline(); // reads BOXLINE_API_KEY and BOXLINE_API_URLconst run = await bx.agent.run({ task: "Open <your sign-in page> and sign in with the username %username% and the password %password%. When the site asks for a verification code, ask me for it and wait, then type the code I give you. When I'm signed in, tell me the name shown on the page.", variables: { username: process.env.SITE_USERNAME!, password: { value: process.env.SITE_PASSWORD!, origins: ["https://example.com"] }, // only fields on your site },});// Watch the run. When it pauses, the agent is asking you: answer, then resume it.const rl = createInterface({ input: process.stdin, output: process.stdout });for await (const event of bx.agent.stream(run.id)) { if (event.type === "thought") console.log("…", event.text); if (event.type === "tool") console.log(event.name); if (event.type === "status" && event.status === "paused" && event.by !== "captcha") { const { liveUrl } = await bx.sessions.get(run.sessionId); // a fresh signed link: keep it private console.log("The agent needs you:", event.reason ?? "", "\nLive view:", liveUrl); await bx.agent.resume(run.id, { note: await rl.question("Your answer (Enter when done): ") }); } if (event.type === "done") console.log(event.status, event.result);}rl.close();import osfrom boxline import Boxlinebx = Boxline() # reads BOXLINE_API_KEY and BOXLINE_API_URLrun = bx.agent.run( "Open <your sign-in page> and sign in with the username %username% and the password %password%. When the site asks for a verification code, ask me for it and wait, then type the code I give you. When I'm signed in, tell me the name shown on the page.", variables={ "username": os.environ["SITE_USERNAME"], "password": {"value": os.environ["SITE_PASSWORD"], "origins": ["https://example.com"]}, # only fields on your site },)# Watch the run. When it pauses, the agent is asking you: answer, then resume it.for event in bx.agent.stream(run["id"]): if event["type"] == "thought": print("…", event["text"]) elif event["type"] == "tool": print(event["name"]) elif event["type"] == "status" and event["status"] == "paused" and event.get("by") != "captcha": live_url = bx.sessions.get(run["sessionId"]).live_url # a fresh signed link: keep it private print("The agent needs you:", event.get("reason") or "", "\nLive view:", live_url) bx.agent.resume(run["id"], note=input("Your answer (Enter when done): ")) elif event["type"] == "done": print(event["status"], event.get("result"))Give the code in the box under the steps, or type it in the live browser yourself.
My invoices
Opens your billing page signed in with a browser profile and downloads the invoices of the last 3 months.
Needs an AI task (agent run), a browser profile, signed in.
Before you run it, replace <your billing page> with your billing page.
Open <your billing page>. I'm signed in already (browser profile). Find the invoices or billing history, download every invoice from the last 3 months, and tell me each file's name, date and amount. If you're asked to sign in, ask me for help and wait.# 1. A session signed in with your browser profile.curl -X POST https://api.boxline.dev/v1/sessions \ -H "x-api-key: $BOXLINE_API_KEY" \ -H "content-type: application/json" \ -d '{"keepAlive": true, "profile": {"id": "'"$PROFILE_ID"'"}}'# 2. Start the run (the task is the JSON's "task").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 @- <<JSON{ "task": "Open <your billing page>. I'm signed in already (browser profile). Find the invoices or billing history, download every invoice from the last 3 months, and tell me each file's name, date and amount. If you're asked to sign in, ask me for help and wait.", "sessionId": "$SESSION_ID"}JSON# 3. Follow it: status, steps and result (use its id).curl https://api.boxline.dev/v1/agent/runs/$RUN_ID \ -H "x-api-key: $BOXLINE_API_KEY"# When status is "paused", the agent is asking you. Answer in the live view, or in the note, and resume the run:curl -X POST https://api.boxline.dev/v1/agent/runs/$RUN_ID/resume \ -H "x-api-key: $BOXLINE_API_KEY" \ -H "content-type: application/json" \ -d '{"note": "Done, carry on."}'# 4. Stop the session when you are done.curl -X POST https://api.boxline.dev/v1/sessions/$SESSION_ID/stop \ -H "x-api-key: $BOXLINE_API_KEY"import { createInterface } from "node:readline/promises";import { Boxline } from "@boxline/sdk";const bx = new Boxline(); // reads BOXLINE_API_KEY and BOXLINE_API_URL// A session signed in with your browser profile (its id is in the console, or bx.profiles.list()).const session = await bx.sessions.create({ keepAlive: true, profile: { id: "PROFILE_ID" } });const run = await bx.agent.run({ task: "Open <your billing page>. I'm signed in already (browser profile). Find the invoices or billing history, download every invoice from the last 3 months, and tell me each file's name, date and amount. If you're asked to sign in, ask me for help and wait.", sessionId: session.id,});// Watch the run. When it pauses, the agent is asking you: answer, then resume it.const rl = createInterface({ input: process.stdin, output: process.stdout });for await (const event of bx.agent.stream(run.id)) { if (event.type === "thought") console.log("…", event.text); if (event.type === "tool") console.log(event.name); if (event.type === "status" && event.status === "paused" && event.by !== "captcha") { const { liveUrl } = await bx.sessions.get(run.sessionId); // a fresh signed link: keep it private console.log("The agent needs you:", event.reason ?? "", "\nLive view:", liveUrl); await bx.agent.resume(run.id, { note: await rl.question("Your answer (Enter when done): ") }); } if (event.type === "done") console.log(event.status, event.result);}rl.close();await bx.sessions.stop(session.id);from boxline import Boxlinebx = Boxline() # reads BOXLINE_API_KEY and BOXLINE_API_URL# A session signed in with your browser profile (its id is in the console, or bx.profiles.list()).with bx.sessions.create(keep_alive=True, profile="PROFILE_ID") as session: run = bx.agent.run( "Open <your billing page>. I'm signed in already (browser profile). Find the invoices or billing history, download every invoice from the last 3 months, and tell me each file's name, date and amount. If you're asked to sign in, ask me for help and wait.", session_id=session.id, ) # Watch the run. When it pauses, the agent is asking you: answer, then resume it. for event in bx.agent.stream(run["id"]): if event["type"] == "thought": print("…", event["text"]) elif event["type"] == "tool": print(event["name"]) elif event["type"] == "status" and event["status"] == "paused" and event.get("by") != "captcha": live_url = bx.sessions.get(run["sessionId"]).live_url # a fresh signed link: keep it private print("The agent needs you:", event.get("reason") or "", "\nLive view:", live_url) bx.agent.resume(run["id"], note=input("Your answer (Enter when done): ")) elif event["type"] == "done": print(event["status"], event.get("result"))# In a session signed in with your browser profile (start it as in the curl tab: the CLI has no browser-profile option).boxline run 'Open <your billing page>. I'\''m signed in already (browser profile). Find the invoices or billing history, download every invoice from the last 3 months, and tell me each file'\''s name, date and amount. If you'\''re asked to sign in, ask me for help and wait.' --session $SESSION_IDThe files land in the browser's downloads folder: open Files on the session page to get them.
Two-factor sign-in
Signs in with a password credential that has a 2FA key: the platform makes the current code for you (%NAME.otp%).
Needs an AI task (agent run).
Before you run it, replace <your sign-in page> with your sign-in page and <credential> with your credential's name.
Open <your sign-in page> and sign in with %<credential>.username%, %<credential>.password% and the code %<credential>.otp%.# 1. Start the run (the task is the JSON's "task").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 @- <<'JSON'{ "task": "Open <your sign-in page> and sign in with %<credential>.username%, %<credential>.password% and the code %<credential>.otp%."}JSON# 2. Follow it: status, steps and result (use its id).curl https://api.boxline.dev/v1/agent/runs/$RUN_ID \ -H "x-api-key: $BOXLINE_API_KEY"import { Boxline } from "@boxline/sdk";const bx = new Boxline(); // reads BOXLINE_API_KEY and BOXLINE_API_URLconst run = await bx.agent.run({ task: "Open <your sign-in page> and sign in with %<credential>.username%, %<credential>.password% and the code %<credential>.otp%.",});const done = await bx.agent.wait(run.id); // polls until it finishesconsole.log(done.status, done.result);// Each step, with the agent's thought behind it.for (const s of done.steps) console.log(s.type, s.name ?? "", s.thought ?? s.text ?? "");from boxline import Boxlinebx = Boxline() # reads BOXLINE_API_KEY and BOXLINE_API_URLrun = bx.agent.run( "Open <your sign-in page> and sign in with %<credential>.username%, %<credential>.password% and the code %<credential>.otp%.",)done = bx.agent.wait(run["id"]) # polls until it finishesprint(done["status"], done["result"])for s in done["steps"]: print(s["type"], s.get("name", ""), s.get("thought") or s.get("text", "")) # with the agent's thoughtboxline run 'Open <your sign-in page> and sign in with %<credential>.username%, %<credential>.password% and the code %<credential>.otp%.'Add the password and its 2FA key on the Credentials page, then pick it in Variables and credentials.
Coming soon
These examples need features that are not in the API yet. They will get their code here when they are.
- Watch a price: Checks a product's price and stock on a schedule and sends a webhook when it changes.
- Visual uptime check: Screenshots key pages on a schedule and flags errors or big visual changes.
Only for your own sites and accounts, demo sites, and content you may download. See the acceptable-use rules in the Terms of Service.
These docs describe a beta API. Endpoints, fields and SDK methods may change before general availability; breaking changes will be listed in the changelog.