CAPTCHA hand-over
Open a form behind a CAPTCHA, see the platform detect it, have a person solve it in the live view, then carry on and submit the form. With captcha: "ask" (the default) nothing is solved automatically. Only on sites you own or may automate.
When to use it: Your own site shows a CAPTCHA, and a person should solve it while the automation waits.
| Uses | sessions with captcha: "ask", attention, onCaptcha / on_captcha, waitForHuman / wait_for_human, captcha events |
|---|---|
| Needs | any plan; no model |
| Site | a form of your own site with a CAPTCHA (FORM_URL, required). The runner uses its stand-in form with a stand-in reCAPTCHA widget and plays the person |
| Output | output/result.json |
| Languages | Node and Python |
You take part: It prints the live view link when the CAPTCHA waits: solve it there; the example carries on by itself.
The code
The example as it is in its folder. Both versions do the same job and write the same output/, so one result check covers both.
/** * CAPTCHA hand-over: open a form behind a CAPTCHA, see the platform detect it, have a person solve it in the live * view, then carry on and submit the form. Only on sites you own or may automate (the Acceptable Use Policy). * * FORM_URL=https://… npx tsx node/index.ts (BOXLINE_API_KEY; NAME to type into the form's name field) * * The session's `captcha: "ask"` (the default) means: when a CAPTCHA waits for a person, say so and wait; nothing is * solved automatically. Writes output/result.json. */import { mkdirSync, writeFileSync } from "node:fs";import { join } from "node:path";import { Boxline } from "@boxline/sdk";const out = process.env.OUTPUT_DIR ?? "output";const formUrl = process.env.FORM_URL;if (!formUrl) throw new Error("Set FORM_URL to a form of your own site that shows a CAPTCHA");const name = process.env.NAME ?? "Ada Lovelace";const bx = new Boxline();const session = await bx.sessions.create({ captcha: "ask", keepAlive: true, timeout: 600, userMetadata: { example: "captcha-hand-over" } });console.log(`Session: ${session.id}`);let detected = false;const stop = session.onCaptcha( ({ state, kind, url }) => { if (state === "detected") { detected = true; console.log(`\nA CAPTCHA (${kind}) is waiting for a person on ${url}: solve it in the live view (the link works like a password):\n ${session.liveUrl}\n`); } else console.log(`The CAPTCHA (${kind}) was solved.`); }, { intervalMs: 1000 },);try { await session.goto(formUrl); // The platform looks twice, about 2 s apart, before it says a CAPTCHA waits for a person. const deadline = Date.now() + 30_000; while (!(await session.refresh()).data.attention && Date.now() < deadline) await new Promise((r) => setTimeout(r, 1000)); const attention = session.data.attention; if (attention) await session.waitForHuman({ timeoutMs: 5 * 60_000 }); // throws CaptchaTimeoutError if nobody solves it stop(); await session.fill('input[name="name"]', name); await session.click('form [type="submit"], form button'); await session.wait(1000); const { title, content } = await session.content("text"); console.log(`After submitting: "${title}": ${content.replace(/\s+/g, " ").slice(0, 120)}`); const events = (await session.events({ types: ["captcha"] })).data.map((e) => ({ state: e.data?.state, kind: e.data?.kind, waitedMs: e.data?.waitedMs ?? null })); mkdirSync(out, { recursive: true }); writeFileSync(join(out, "result.json"), JSON.stringify({ formUrl, attention, detected, events, title, pageSays: content.slice(0, 600) }, null, 2));} finally { stop(); await session.stop();}"""CAPTCHA hand-over: open a form behind a CAPTCHA, see the platform detect it, have a person solve it in the liveview, then carry on and submit the form. Only on sites you own or may automate (the Acceptable Use Policy). FORM_URL=https://… python python/main.py (BOXLINE_API_KEY; NAME to type into the form's name field)The session's captcha="ask" (the default) means: when a CAPTCHA waits for a person, say so and wait; nothing is solvedautomatically. Writes output/result.json."""import jsonimport osimport timefrom pathlib import Pathfrom boxline import Boxlineout = Path(os.environ.get("OUTPUT_DIR", "output"))form_url = os.environ.get("FORM_URL")if not form_url: raise SystemExit("Set FORM_URL to a form of your own site that shows a CAPTCHA")name = os.environ.get("NAME", "Ada Lovelace")bx = Boxline()with bx.sessions.create(captcha="ask", keep_alive=True, timeout=600, user_metadata={"example": "captcha-hand-over"}) as session: print(f"Session: {session.id}", flush=True) detected = False def on_captcha(change): global detected if change["state"] == "detected": detected = True print(f"\nA CAPTCHA ({change['kind']}) is waiting for a person on {change['url']}: solve it in the live view " f"(the link works like a password):\n {session.live_url}\n", flush=True) else: print(f"The CAPTCHA ({change['kind']}) was solved.", flush=True) stop = session.on_captcha(on_captcha, interval=1) # a background thread; stop() ends it try: session.goto(form_url) # The platform looks twice, about 2 s apart, before it says a CAPTCHA waits for a person. deadline = time.monotonic() + 30 while not session.refresh().data.get("attention") and time.monotonic() < deadline: time.sleep(1) attention = session.data.get("attention") if attention: session.wait_for_human(timeout=300) # raises CaptchaTimeoutError if nobody solves it finally: stop() session.fill('input[name="name"]', name) session.click('form [type="submit"], form button') session.wait(ms=1000) page = session.content("text") print(f'After submitting: "{page["title"]}": {" ".join(page["content"].split())[:120]}') events = [{"state": (e.get("data") or {}).get("state"), "kind": (e.get("data") or {}).get("kind"), "waitedMs": (e.get("data") or {}).get("waitedMs")} for e in session.events(types=["captcha"]).data] out.mkdir(parents=True, exist_ok=True) result = {"formUrl": form_url, "attention": attention, "detected": detected, "events": events, "title": page["title"], "pageSays": page["content"][:600]} (out / "result.json").write_text(json.dumps(result, indent=2, ensure_ascii=False))Inputs
Environment variables the example reads:
FORM_URL: your form with a CAPTCHA (required).NAME: what to type into its name field (defaultAda Lovelace).
Run it
Get the examples with git clone https://github.com/Boxline-dev/examples.git and open this one: cd examples/captcha-hand-over. Put your API key in BOXLINE_API_KEY (and BOXLINE_API_URL for another API), then run either version from the example’s folder. Both write to output/ and stop their sessions when they finish.
# Node 18 or newer, in the example's folderexport BOXLINE_API_KEY=… # your API keynpm install @boxline/sdk tsxnpx tsx node/index.ts# Python 3.9 or newer, in the example's folderexport BOXLINE_API_KEY=… # your API keypip install boxline-sdkpython python/main.pyThen check the result, from the same folder: npx tsx check.ts output. It exits with 0 only when the result is right.
What its check proves
check.ts reads what the example wrote and passes only when the result is right, not merely when the example ran.
The CAPTCHA was detected and cleared (with the time it waited), and the form went through with the answer.
Source
github.com/Boxline-dev/examples/captcha-hand-over on GitHub, with its README.md, check.ts and .env.example. The examples are open source. See all examples.
These docs describe a beta API. Endpoints, fields and SDK methods may change before general availability; breaking changes will be listed in the changelog.