Skip to main content
Written for an AI coding agent (Claude Code, Cursor, Codex) that’s been asked to integrate Driver into a project. Humans welcome. The docs also serve /llms.txt (index) and /llms-full.txt (every page).

The rules

  1. Patchright, not stock Playwright or Puppeteer. Install patchright (npm or pip) and import chromium from 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.
  2. Never launch a browser. Connect to the cdpUrl from the create call with chromium.connectOverCDP(cdpUrl). No chromium.launch(), launchPersistentContext(), puppeteer.launch(), no playwright install.
  3. Reuse the browser’s context and tab. browser.contexts()[0] and context.pages()[0] (properties in Python: browser.contexts[0], context.pages[0]). Create new ones only if none exist. Extra contexts look like automation.
  4. 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.
  5. 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. Set country, timezone and language on the create call instead.
  6. Always stop the session. try / finally, with DELETE /v1/browser/session?sessionId=… in the finally. Sessions are metered by bandwidth and end on their own at duration (an hour by default); until then a forgotten session holds a concurrency slot and its cdpUrl stays live. browser.close() doesn’t stop it.
  7. 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, print https://viewer.driver.dev?ws=<cdpUrl> as soon as the session exists. See Live view.
  8. Report what a person must fix; retry only what time fixes. 402 (out of credits) and 403 (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) and 500: retry once. 429: back off. Full table on Errors.
  9. Stay under the concurrency limit. GET /v1/account/billing reports it as plan.concurrent_browsers; sessions beyond it get 403.
  10. Use a pool when start-up latency matters. A pool keeps browsers warm with fixed options. POST /v1/browser/pools/{poolId}/acquire with { "waitMs": 10000 } answers in under a second with { leaseId, session }; session is an ordinary session from there. Release with POST /v1/browser/pools/{poolId}/release { leaseId } in the same finally where you’d stop a session. Don’t create pools without asking: they hold warm browsers against an account-wide cap.
  11. When a site challenges or blocks, escalate in this order. Remove every hook and tactic. Then type: "hosted_stealth", then "hosted_privacy", then captchaSolver: 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=… shows status: "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 finally leaves a session running until its duration. At start-up, list status=active and status=starting (listSessions / list_sessions in the reference client), stop the ones your job created (match on note), 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 URL https://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

  • patchright is the dependency, not playwright or puppeteer.
  • No launch() anywhere; the only browser entry point is connectOverCDP(cdpUrl).
  • The first context and page are reused.
  • The session is stopped in a finally.
  • DRIVER_API_KEY comes 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 (status completed), 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.txt lists every page; https://docs.driver.dev/llms-full.txt is all of them in one file. Any page is Markdown with .md appended to its URL.
  • https://api.driver.dev/doc is the live OpenAPI document; https://api.driver.dev/scalar renders it.