/llms.txt (index) and /llms-full.txt (every page).
The rules
- Patchright, not stock Playwright or Puppeteer. Install
patchright(npm or pip) and importchromiumfrom it. Same API as Playwright, so existing code works unchanged. Stock Playwright issues CDP commands (Runtime.enable, console and binding hooks) that anti-bot systems detect. Patchright removes them. See Why Patchright. - Never launch a browser. Connect to the
cdpUrlfrom the create call withchromium.connectOverCDP(cdpUrl). Nochromium.launch(),launchPersistentContext(),puppeteer.launch(), noplaywright install. - Reuse the browser’s context and tab.
browser.contexts()[0]andcontext.pages()[0](properties in Python:browser.contexts[0],context.pages[0]). Create new ones only if none exist. Extra contexts look like automation. - No broad CDP hooks.
page.route,context.route,page.on('request'),page.exposeFunction,page.addInitScript,context.addInitScript: only when the task can’t be done otherwise, and then as narrow as possible. They change how the browser behaves and that’s detectable. - Don’t patch fingerprints. It’s real Chrome on real hardware. Overriding
navigator, the user agent, WebGL, canvas, timezone or locale makes it look fake. Setcountry,timezoneandlanguageon the create call instead. - Always stop the session.
try / finally, withDELETE /v1/browser/session?sessionId=…in thefinally. Sessions are metered by bandwidth and end on their own atduration(an hour by default); until then a forgotten session holds a concurrency slot and itscdpUrlstays live.browser.close()doesn’t stop it. - Keep the
sessionId. Log it with the run. Every session has a record under Telemetry → Sessions with node, network, timing and cost. If the person wants to watch, printhttps://viewer.driver.dev?ws=<cdpUrl>as soon as the session exists. See Live view. - Report what a person must fix; retry only what time fixes.
402(out of credits) and403(concurrency or plan limit): report, never loop.503(no capacity;Retry-After: 2,"code": "browser_capacity_unavailable"): back off with jitter, cap the attempts, then report.504(Chrome didn’t start in time) and500: retry once.429: back off. Full table on Errors. - Stay under the concurrency limit.
GET /v1/account/billingreports it asplan.concurrent_browsers; sessions beyond it get 403. - Use a pool when start-up latency matters. A pool keeps browsers warm with fixed options.
POST /v1/browser/pools/{poolId}/acquirewith{ "waitMs": 10000 }answers in under a second with{ leaseId, session };sessionis an ordinary session from there. Release withPOST /v1/browser/pools/{poolId}/release{ leaseId }in the samefinallywhere you’d stop a session. Don’t create pools without asking: they hold warm browsers against an account-wide cap. - When a site challenges or blocks, escalate in this order. Remove every hook and tactic. Then
type: "hosted_stealth", then"hosted_privacy", thencaptchaSolver: true. When a step needs a person, give them the live view URL. See Browser types and CAPTCHA solving.
How to write the automation
- Minimal logic wins. Go to the page, do the action, read the result. No retries, waits, listeners, request routing or “stealth” helpers until a specific failure on a specific site demands one, and then the smallest thing that fixes it.
- Fast first. Before writing browser steps: is there a direct URL? Can one session do several pages? Can a persisted profile replace logging in every run? Then keep the steps short: no fixed sleeps, use the framework’s auto-waiting, stop the session the moment you have the result.
- Test for real, every time. Run against the live target through Driver and check the result, not the exit code: the data is right, the action happened, the session was stopped (
GET /v1/browser/session?sessionId=…showsstatus: "completed",stoppedAt,bandwidthBytes). A run that “worked” but read the wrong element is worse than one that failed loudly. - Unattended jobs clean up after earlier runs. A crash before
finallyleaves a session running until itsduration. At start-up, liststatus=activeandstatus=starting(listSessions/list_sessionsin the reference client), stop the ones your job created (match onnote), then start the new one. Exit non-zero when the result wasn’t verified. - Don’t pollute the session. Every CDP feature you switch on, every injected script, every overridden property is a signal. If you didn’t need it, take it out.
Handoff prompt
Give this to a coding agent with the target and the outcome you want. It’s enough to start; the rest of this page is for once it’s writing code.The three calls
Base URLhttps://api.driver.dev. Every call carries Authorization: Bearer $DRIVER_API_KEY. Keep the key in an environment variable named DRIVER_API_KEY. Node’s global fetch has no timeout, which suits the create call; if you add one, allow three minutes.
Also useful:
GET /v1/browser/session?sessionId=<id> for status and bandwidth, GET /v1/browser/sessions?status=active to list, PATCH /v1/browser/session?sessionId=<id> with { "note": "…" } to label a run. Every create option is on Create a session. With a pool, POST /v1/browser/pools/{poolId}/acquire replaces the create call and POST /v1/browser/pools/{poolId}/release the stop; pool ids go in the path. Dedicated IPs are reserved in the dashboard and named on a create call as proxyUrl: "dedicated://<ip>", or dedicated://any for whichever of yours is free.
Reference implementation
Checklist before you finish
-
patchrightis the dependency, notplaywrightorpuppeteer. - No
launch()anywhere; the only browser entry point isconnectOverCDP(cdpUrl). - The first context and page are reused.
- The session is stopped in a
finally. -
DRIVER_API_KEYcomes from the environment and isn’t committed. - 402 and 403 are reported; 503 is retried with a cap and a growing delay.
- The result was verified through
GET /v1/browser/session(statuscompleted), not assumed. - A browser from a pool is released (or stopped) in the same
finally; no pool was created without asking.
Machine-readable resources
https://docs.driver.dev/llms.txtlists every page;https://docs.driver.dev/llms-full.txtis all of them in one file. Any page is Markdown with.mdappended to its URL.https://api.driver.dev/docis the live OpenAPI document;https://api.driver.dev/scalarrenders it.