Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Connects to Oxylabs remote headless browsers via Chrome DevTools Protocol (CDP) using Playwright or Puppeteer. Provides anti-detection, CAPTCHA handling, residential proxies, and geo-targeting built in. Use when browser automation needs remote execution, stealth capabilities, rendered pages, screenshots, PDFs, or complex JavaScript interaction.
.claude/skills/oxylabs-headless-browser/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 83% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 252% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 77% | 0% |
| case-04 | ✗→✓ | ▲ Improved | 81% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 90% | 0% |
Remote Chrome sessions with anti-detection, proxy rotation and geo-targeting built in. Nothing runs locally: you connect over a WebSocket, drive the browser with the CDP library you already use, and close the session when done. This file holds the rules; the detail lives next to it: scripts/ (copyable templates), parameters.md, errors.md, examples.md, targets.md.
| Item | Value | |------|-------| | Endpoint | wss://USERNAME:PASSWORD@hb.oxylabs.io | | Credentials | OXY_UNBLOCKER_USERNAME / OXY_UNBLOCKER_PASSWORD (aliases: OXY_HB_USERNAME / OXY_HB_PASSWORD) | | Options | URL query parameters only, e.g. ?p_cc=US&session_name=job-42 (see parameters.md) | | Libraries | Playwright chromium.connectOverCDP (recommended), Puppeteer puppeteer.connect, any CDP client | | Dashboard / support | https://hb.oxylabs.io/dashboard · support@oxylabs.io |
Rules that prevent the most common 401:
wss://. Plain ws:// is accepted but sends your password unencrypted.new URL() or urllib.parse: they percent-encode the password and authentication fails.
_ab12.: cannot be sent in the URL. Ask for a new password or send theAuthorization: Basic header yourself (see examples.md).
401 before looking at anything else.Minimal shape (Playwright, JavaScript):
javascriptconst { chromium } = require("playwright"); const url = `wss://${process.env.OXY_UNBLOCKER_USERNAME}:${process.env.OXY_UNBLOCKER_PASSWORD}@hb.oxylabs.io?p_cc=US`; const browser = await chromium.connectOverCDP(url, { timeout: 60000 }); try { const page = await browser.contexts()[0].newPage(); // default context: backed by fingerprint, proxy, o_profile await page.goto("https://example.com", { waitUntil: "domcontentloaded", timeout: 30000 }); console.log(await page.content()); } finally { await browser.close(); // always: an unclosed session keeps its concurrency slot }
For real work copy scripts/playwright_scrape.js or scripts/playwright_scrape.py whole instead of reimplementing. They add the five behaviours everything else in this file assumes:
429, 5xx,CDP_SESSION_IN_USE, CDP_NO_BROWSERS_AVAILABLE, CDP_BROWSER_OVERWORKED, CDP_BAD_PROXY, CDP_GENERAL_ERROR, timeouts. 400/401/403 mean the request is wrong: fix, never retry unchanged.
image, stylesheet, media, font by default; they cost time and are not needed for data extraction.X-Error-Description response header marks an Oxylabs-sideerror on page traffic.
browser.close() in finally, and wrap the job in an overall deadline so a wedged session still gets there.Puppeteer, Python async, raw CDP, session hand-over, profiles, recording and fan-out: examples.md.
| Limit (account defaults) | Value | When exceeded | |--------------------------|-------|---------------| | New sessions per second | 10 | 429 CDP_SESSION_RATE_LIMIT_REACHED (space launches >= 150 ms) | | Concurrent sessions | 100 | 429 CDP_MAX_CONCURRENT_SESSIONS_REACHED | | Named (resumable) sessions | 5 | 429 CDP_MAX_PERSISTENT_SESSIONS_REACHED | | Stored profiles (o_profile) | 5 | 403 profile limit reached (5 profiles maximum) | | Recordings | 10 | 403 recording limit reached (10 recordings maximum) | | Concurrent inspection viewers | 10 | CDP_VNC_MAX_CONCURRENT_SESSIONS_REACHED |
session_name (^[A-Za-z0-9-]{3,36}$) makes a session resumable for 10 minutes after disconnect.keep_alive is implied by it; never send keep_alive=true alone (400 keep_alive requires session_name).
429 CDP_SESSION_IN_USE: close it first.as an unrelated 429 CDP_MAX_CONCURRENT_SESSIONS_REACHED. Closing the Playwright/Puppeteer object is enough.
browser.close() wipes open pages and cookies even though a named session stays resumable. To hand a sessionover use Puppeteer browser.disconnect() (see examples.md, "Resume a named session"). State that must outlive a session (logins, clearance cookies) belongs in o_profile, not keep-alive.
503 queue timeout after about a minute: back off and retry.Higher limits via support.
Three channels. Handshake: HTTP status plus a short body (Playwright: WebSocket error: <URL with password> <status> then the body; Puppeteer: Unexpected server response: <status>). Post-connect: the WebSocket closes with code 3000 and a CDP_* reason that only raw clients see; Playwright/Puppeteer just report Target closed, so treat any disconnect in the first seconds of a session as retryable. In-page: CDP error 1337 for one refused command. On page traffic, a response with X-Error-Description is an Oxylabs network error (retry); a block page without it is the target's decision (change approach, do not retry).
textconnect failed? ├─ 401 ............ fix credentials/scheme, do not retry ├─ 400/403/409 .... fix the named parameter, do not retry unchanged (409: wait 30 s+ for the other session) ├─ 429 ............ backoff; if MAX_CONCURRENT: hunt for unclosed sessions └─ 5xx/503 ........ backoff, up to ~2 min total session dropped (close 3000)? └─ new session with backoff; rotate sticky id on CDP_BAD_PROXY navigate failed with 1337 Invalid target? └─ stop; restricted target (section 7) page shows block / 403 wall? ├─ X-Error-Description present .... Oxylabs network issue: backoff + retry └─ absent ......................... target decision: change identity, geo, device, pacing (section 5)
Every message text with cause and fix: errors.md.
Default parameter set for most jobs: p_cc, nothing else. Every session already gets a fresh fingerprint and a fresh residential IP, which is what one-shot fetches and fan-outs of independent pages need. Sticky IPs and stored profiles are opt-in tools for a specific need, never a baseline.
Work order for a protected target. First write a plain script and make it pass: one fresh session per page, the right geo and device, human pacing, then the escalation ladder below. Only when that script still fails after the ladder do you recommend persistent profiles to the user (the setup/consumer pattern below, with why it should help and what it costs: a setup step, the profile cap of 5) and implement them only on their go-ahead. Never add a profile or sticky id on your own initiative.
| Need | Add | Not for | |------|-----|---------| | Several connections must look like one visitor (login, cart, a flow that outlives one session) | proxy_resi_ses_id + proxy_resi_ses_time | one page per session | | Cookies or a login must survive between jobs (DataDome clearance, authenticated scraping) | o_profile, prepared once by a setup run, after the user agreed | a first attempt; targets that serve without a block | | Resume the same browser within 10 minutes | session_name | everything else |
When you do use them, the combination is one identity. Keep it consistent:
textsetup, exactly once : ?o_profile=acme-us-01&o_profile_save=true&p_cc=US&proxy_resi_ses_id=acmeus01&proxy_resi_ses_time=30 consumers, any number: ?o_profile=acme-us-01&p_cc=US&proxy_resi_ses_id=acmeus01&proxy_resi_ses_time=30
o_profile_save=true: it earns the cookies (clears the entry page, logs in), verifies the page, closes. Consumer runs send o_profile=<name> alone: read-only, no write lock, no 409. Never "top up" a profile from a consumer; when it stops working, run setup again under a new name. In production this is a setup service that prepares and validates profiles and a consumer service that only uses them (examples.md, "Profile setup and consumer runs").
proxy_resi_ses_id + proxy_resi_ses_time pin the exit IP (max 1440 min). A pinned id disables automaticproxy retry: on CDP_BAD_PROXY rotate to a new id.
p_cc/p_city/p_state for an identity that has cookies. Start a new profile and sticky id.p_device: mobile = taps, small scrolls, no hover; desktop (default) = the opposite.Never set viewport or device metrics yourself; the service owns the fingerprint.
Run parallel identities, not parallel tabs.
p_city) →p_device=mobile → slow down → inspect (section 6) → recommend persistent profiles to the user → stop and report. Repeating an identical request is never a rung.
Block signatures per vendor, do/don't table and starting values for a new protected target: targets.md.
fails, tell them about both: live inspection (fetch the session id with the CDP command __session_id, open https://hb.oxylabs.io/novnc/?id=<id> and watch the session as it runs) and recordings (record=true& record_name=<job> saves a video of the session to replay later in https://hb.oxylabs.io/dashboard; cap 10, delete old ones there). Both are off by default. Use them yourself after 3 consecutive failures on one target to confirm what the page actually shows. Snippet in examples.md, "Session id, live inspection and recording".
browser.contexts()[0]. A newContext() is isolated from profile storage and fingerprint tuning.Blocked by default; access requires a short KYC via your account manager: entertainment and streaming, banking and finance, government sites, gaming platforms, ticketing, webmail, ad networks, third-party IP checkers. Use https://ip.oxylabs.io/location to verify your exit IP and geo. A blocked target fails Page.navigate with CDP error 1337 Invalid target.
See also: scripts/ (full Playwright templates, JS and Python), parameters.md (every parameter and its validation), errors.md (every message), examples.md (Puppeteer, Python async, raw CDP, reconnection, profiles, recording, fan-out), targets.md (block detection, DataDome playbook).
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 47,481 | 32,954 | -31% | 1 | 1 | 0% | 3,707 | 6,769 | +83% | 0 | 0 | — |
case-02 | fail→pass | 57,392 | 36,733 | -36% | 1 | 1 | 0% | 2,104 | 7,407 | +252% | 0 | 0 | — |
case-03 | fail→pass | 26,977 | 21,503 | -20% | 1 | 1 | 0% | 3,905 | 6,931 | +77% | 0 | 0 | — |
case-04 | fail→pass | 37,810 | 8,545 | -77% | 1 | 1 | 0% | 2,281 | 4,133 | +81% | 0 | 0 | — |
case-05 | fail→pass | 15,551 | 52,758 | +239% | 1 | 1 | 0% | 2,390 | 4,545 | +90% | 0 | 0 | — |
case-06 | fail→pass | 14,455 | 5,873 | -59% | 1 | 1 | 0% | 1,925 | 4,099 | +113% | 0 | 0 | — |
case-07 | pass→pass | 28,695 | 21,345 | -26% | 1 | 1 | 0% | 1,767 | 4,846 | +174% | 0 | 0 | — |
case-08 | pass→pass | 15,830 | 8,249 | -48% | 1 | 1 | 0% | 2,127 | 4,461 | +110% | 0 | 0 | — |
case-09 | pass→pass | 20,608 | 18,400 | -11% | 1 | 1 | 0% | 2,211 | 4,420 | +100% | 0 | 0 | — |
case-10 | fail→pass | 30,125 | 6,222 | -79% | 1 | 1 | 0% | 2,455 | 4,004 | +63% | 0 | 0 | — |
case-11 | fail→pass | 18,972 | 45,757 | +141% | 1 | 1 | 0% | 2,459 | 5,222 | +112% | 0 | 0 | — |
case-12 | pass→pass | 55,831 | 9,125 | -84% | 1 | 1 | 0% | 2,002 | 4,143 | +107% | 0 | 0 | — |
case-13 | pass→pass | 17,001 | 16,533 | -3% | 1 | 1 | 0% | 2,314 | 4,377 | +89% | 0 | 0 | — |
case-14 | fail→pass | 33,919 | 34,264 | +1% | 1 | 1 | 0% | 2,357 | 5,035 | +114% | 0 | 0 | — |
case-15 | fail→pass | 16,855 | 10,188 | -40% | 1 | 1 | 0% | 2,436 | 4,731 | +94% | 0 | 0 | — |
case-16 | pass→pass | 14,806 | 9,518 | -36% | 1 | 1 | 0% | 2,106 | 4,708 | +124% | 0 | 0 | — |
case-17 | fail→pass | 14,240 | 9,847 | -31% | 1 | 1 | 0% | 1,806 | 4,565 | +153% | 0 | 0 | — |
case-18 | fail→pass | 14,640 | 16,063 | +10% | 1 | 1 | 0% | 1,870 | 4,141 | +121% | 0 | 0 | — |
case-19 | pass→pass | 13,680 | 7,258 | -47% | 1 | 1 | 0% | 1,680 | 4,270 | +154% | 0 | 0 | — |
case-20 | pass→pass | 7,380 | 2,568 | -65% | 1 | 1 | 0% | 1,084 | 3,504 | +223% | 0 | 0 | — |
case-21 | pass→pass | 13,759 | 12,456 | -9% | 1 | 1 | 0% | 2,271 | 5,263 | +132% | 0 | 0 | — |
case-22 | pass→pass | 13,803 | 13,074 | -5% | 1 | 1 | 0% | 2,542 | 5,350 | +110% | 0 | 0 | — |
case-23 | pass→fail | 31,622 | 16,535 | -48% | 1 | 1 | 0% | 2,496 | 6,122 | +145% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted. The headline lift of +48 percentage points is the difference between those two pass rates over the 23 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.