Sites that demand a session, a consent click or a solved challenge need a browser you can drive — and a residential exit instead of a datacenter one.
A logged-in page fails for one of four reasons, and each has its own answer. None of them is a flag you flip and hope — every one is a surface you call.
A form, a redirect, a cookie set three hops later. Open a CDP connection and the login is ordinary Playwright code — fill, click, wait for the URL. The browser context keeps its cookies for the rest of the run.
wss://browser.wayfern.com/ws
Consent overlays swallow clicks and scroll-lock the document. On screenshot and PDF captures Wayfern clicks the accept control it recognises, hides whatever is left with CSS and unlocks scrolling — on by default.
capture.blockCookieBanners: true
reCAPTCHA, hCaptcha, FunCaptcha, GeeTest, Turnstile or a plain image. Post the sitekey to the captcha endpoint, get the provider’s solution back, and write it into the field the form posts.
POST /api/captcha/solve
A datacenter exit a site has already made up its mind about. In auto mode a 401, 402, 403 or 407 — or a redirect onto a known challenge path — is retried once through a residential exit. Set residential to start there.
proxy: 'auto' | 'residential'
Connect over CDP with a link token and you get a full browser you control: multi-step logins, second factors typed from your own secret store, consent clicks, pagination behind the wall. A session is created when the connection opens — there is no endpoint to call first, and nothing to install beyond the driver you already use.
import { chromium } from "playwright";
// One CDP connection is one browser session. Exit selectors ride on the query
// string: "country" turns on a residential exit, "session" pins it for the run.
const browser = await chromium.connectOverCDP(
`wss://browser.wayfern.com/ws?token=${LINK_TOKEN}&country=us&session=run4711`,
);
const context = browser.contexts()[0];
const page = await context.newPage();
await page.goto("https://app.example.com/login");
await page.fill("#email", process.env.TARGET_EMAIL);
await page.fill("#password", process.env.TARGET_PASSWORD);
// A Turnstile widget guarding the form: solve it, then write the token into
// the field the form actually posts.
const widget = page.locator(".cf-turnstile[data-sitekey]").first();
if ((await widget.count()) > 0) {
const websiteKey = await widget.getAttribute("data-sitekey");
const solved = await fetch("https://api.wayfern.com/api/captcha/solve", {
method: "POST",
headers: {
// An API token (wf_...) carrying the "captcha" scope.
Authorization: `Bearer ${WAYFERN_API_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
task: {
type: "TurnstileTaskProxyless",
websiteURL: page.url(),
websiteKey,
},
}),
});
// { "solution": { ... } } — the fields depend on the task type.
const { solution } = await solved.json();
await page.evaluate((token) => {
const field = document.querySelector('input[name="cf-turnstile-response"]');
if (field instanceof HTMLInputElement) field.value = token;
}, solution.token);
}
await page.click("button[type=submit]");
await page.waitForURL("**/dashboard");
// Logged in. Every later navigation reuses this context — same cookies, same
// sticky exit — until you close the connection.
await page.goto("https://app.example.com/reports/q1");
console.log(await page.title());
await browser.close();Both endpoints in that script are real: the CDP gateway at wss://browser.wayfern.com/ws and POST /api/captcha/solve on https://api.wayfern.com. The
captcha response is { "solution": { … } },
whose fields depend on the task type — see the API reference.
A cookie issued to one IP and replayed from another is the cheapest signal a site has. If the run is logged in, the exit has to hold still.
Add country and a session id
(letters and digits) to the gateway URL and that id is encoded into the upstream
credentials with a 24-hour TTL, so a multi-step flow keeps one exit IP for as long as the
session lasts. Narrow it further with region, city or isp.
?token=…&country=us&session=run4711
A one-shot call resolves a residential exit without a session id, so the IP rotates per connection. That is the right default for independent page fetches and the wrong tool for a multi-step logged-in flow: for those, either drive a session, or send the credential yourself on every call with a per-request header.
proxy: 'residential', country: 'us'
POST /api/captcha/solve takes
an anti-captcha task object and returns the solution. Submit it from inside a session, or
from anywhere else you happen to be holding a sitekey.
Rules Only proxyless task variants are accepted — proxy fields and softId are rejected, and websiteURL must be an
absolute http(s) URL. A solve is debited on success only: if no solution comes back,
nothing is charged.
If you already hold the credential — a session cookie, a bearer token, an API key the page accepts — the Web Data API will carry it for you. A scrape is 1 credit per page at that point, with no session to babysit.
curl -X POST https://api.wayfern.com/api/v1/scrape \
-H "Authorization: Bearer $WAYFERN_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"url": "https://app.example.com/reports/q1",
"proxy": "residential",
"country": "us",
"headers": { "Cookie": "sid=<YOUR_SESSION_COOKIE>" },
"formats": ["markdown"]
}'Up to 20 custom headers per call, an authorization cookie among them. Wayfern rejects
the four it must own itself — Host, Content-Length, Connection and Transfer-Encoding —
rather than dropping them silently and leaving you to debug the wrong page.
proxy: 'auto', the
default, tries a datacenter exit and retries once through a residential one when the
response reads as a block. Other failures surface immediately — retrying a timeout on
a slower path just spends the budget twice.
An authenticated run spends on three separate meters, all drawn from the same credits. Plans start at $19 a month with 1,520 credits included, and non-expiring packs top up from there.
A browser session meters the seconds each page is open, pro-rated from 8 credits per open-page hour, with a 60-second floor on every page opened. Close the connection and the meter stops.
A solve is debited once the provider returns a solution. A solve that errors, or that never comes back before the timeout, debits nothing — you are billed for answers, not attempts.
Datacenter egress is not metered. Residential bytes are, per byte, at 320 credits per GB — so in auto mode you only pay for the traffic that a bot wall actually forced onto a residential exit.
Every per-unit rate is published on the pricing page, and each /api/v1 response hands back the exact charge in its credits field.
Log in once, hold the session, and hand the challenges to an endpoint. The same balance covers the browsers, the solves and the bytes.