Browser API
Connect Playwright or Puppeteer to Anakin's stealth browser
What is Browser API?
Browser API gives you a stealth browser in the cloud that you control with your own code. Connect Playwright, Puppeteer, or any CDP-compatible client to Anakin's anti-detection browser via a single WebSocket URL.
Unlike traditional scraping APIs where you submit a URL and get results back, Browser API gives you full browser control: navigate pages, click buttons, fill forms, take screenshots, extract data — all through your own automation scripts.
Why use Browser API?
- Anti-detection built in — Stealth browser with fingerprint masking, WebRTC leak prevention, and
navigator.webdriver = false. No configuration needed. - Playwright and Puppeteer support — Connect with
connect_over_cdp(Playwright) orbrowser.connect()(Puppeteer). No code changes beyond the connection URL. - Smart proxy selection — Per-domain proxy optimization via Thompson Sampling. The best proxy is automatically selected for each target site.
- No browser infrastructure — No managing headless browsers, displays, or containers. Just connect and scrape.
- Same API key — Uses your existing Anakin API key. No separate auth flow.
Quick Start
import asyncio
from playwright.async_api import async_playwright
API_KEY = "your_api_key"
async def main():
async with async_playwright() as p:
browser = await p.chromium.connect_over_cdp(
"wss://api.anakin.io/v1/browser-connect",
headers={"X-API-Key": API_KEY},
)
page = browser.contexts[0].pages[0]
# Navigate and extract data
await page.goto("https://books.toscrape.com", wait_until="domcontentloaded")
print("Title:", await page.title())
# Extract structured data
books = await page.evaluate("""
Array.from(document.querySelectorAll('article.product_pod')).slice(0, 5).map(el => ({
title: el.querySelector('h3 a')?.title,
price: el.querySelector('.price_color')?.textContent,
}))
""")
print("Books:", books)
# Take a screenshot
await page.screenshot(path="screenshot.png")
# Use locators
count = await page.locator("article.product_pod").count()
print(f"Found {count} products")
await browser.close()
asyncio.run(main())const { chromium } = require('playwright');
const API_KEY = 'your_api_key';
(async () => {
const browser = await chromium.connectOverCDP(
'wss://api.anakin.io/v1/browser-connect',
{ headers: { 'X-API-Key': API_KEY } }
);
const page = browser.contexts()[0].pages()[0];
await page.goto('https://books.toscrape.com', { waitUntil: 'domcontentloaded' });
console.log('Title:', await page.title());
const books = await page.evaluate(() =>
Array.from(document.querySelectorAll('article.product_pod')).slice(0, 5).map(el => ({
title: el.querySelector('h3 a')?.title,
price: el.querySelector('.price_color')?.textContent,
}))
);
console.log('Books:', books);
await page.screenshot({ path: 'screenshot.png' });
await browser.close();
})();const puppeteer = require('puppeteer-core');
const API_KEY = 'your_api_key';
(async () => {
const browser = await puppeteer.connect({
browserWSEndpoint: 'wss://api.anakin.io/v1/browser-connect',
headers: { 'X-API-Key': API_KEY },
});
const page = (await browser.pages())[0] || await browser.newPage();
await page.goto('https://books.toscrape.com', {
waitUntil: 'domcontentloaded',
timeout: 30000,
});
console.log('Title:', await page.title());
console.log('HTML:', (await page.content()).length, 'chars');
await page.screenshot({ path: 'screenshot.png' });
await browser.disconnect();
})();Supported Features
Playwright
| Feature | Status |
|---|---|
page.goto() | ✅ |
page.title() / page.content() | ✅ |
page.evaluate() | ✅ |
page.screenshot() | ✅ |
page.locator() — count, text, attributes | ✅ |
page.wait_for_selector() | ✅ |
page.wait_for_function() | ✅ |
page.inner_text() / page.text_content() | ✅ |
page.set_extra_http_headers() | ✅ |
| Mouse and keyboard input | ✅ |
| Scroll / pagination | ✅ |
| Cross-site navigation | ✅ |
Stealth (webdriver=false) | ✅ |
| Session keepalive (60s+) | ✅ |
page.route() — network interception | ❌ |
Puppeteer
| Feature | Status |
|---|---|
page.goto() | ✅ |
page.title() / page.content() | ✅ |
page.evaluate() | ✅ |
page.screenshot() | ✅ |
page.$$() — querySelectorAll | ✅ |
page.$eval() — scoped evaluate | ✅ |
page.waitForSelector() | ✅ |
page.setExtraHTTPHeaders() | ✅ |
| Mouse and keyboard input | ✅ |
| Scroll / pagination | ✅ |
| Cross-site navigation | ✅ |
browser.pages() / browser.newPage() | ✅ |
page.waitForNavigation() | ❌ |
Selenium
Selenium with ChromeDriver is not supported. Use Playwright or Puppeteer instead.
Known limitations
These are inherent to how remote CDP connections work, not specific to Browser API:
page.goto()response object isnull— when connecting viaconnect_over_cdp()/puppeteer.connect(), the HTTP response object is alwaysnull. Navigation itself works correctly (page loads, URL updates, content is accessible).page.route()does not fire (Playwright) — request interception requires a locally-ownedBrowserContext. It is not available when the context is owned by a remote browser.page.waitForNavigation()hangs (Puppeteer) — usepage.waitForSelector()orpage.waitForFunction()after clicks instead.
Scraping Example: Extract Product Data
import asyncio
from playwright.async_api import async_playwright
async def scrape_books():
async with async_playwright() as p:
browser = await p.chromium.connect_over_cdp(
"wss://api.anakin.io/v1/browser-connect",
headers={"X-API-Key": "your_api_key"},
)
page = browser.contexts[0].pages[0]
await page.goto("https://books.toscrape.com", wait_until="domcontentloaded")
# Wait for products to load
await page.wait_for_selector("article.product_pod")
# Extract all books on the page
books = await page.evaluate("""
Array.from(document.querySelectorAll('article.product_pod')).map(el => ({
title: el.querySelector('h3 a')?.title,
price: el.querySelector('.price_color')?.textContent,
rating: el.querySelector('.star-rating')?.className.replace('star-rating ', ''),
inStock: !!el.querySelector('.instock'),
link: el.querySelector('h3 a')?.href,
}))
""")
print(f"Scraped {len(books)} books")
for book in books[:3]:
print(f" {book['title']} — {book['price']}")
await browser.close()
asyncio.run(scrape_books())Billing
Browser API sessions are billed at 1 credit per 2 minutes (rounded up). A session that lasts 3 minutes costs 2 credits.
Sessions auto-disconnect when your credits reach 0.
Limits
| Parameter | Value |
|---|---|
| Max session duration | 2 hours |
| Idle timeout | 60 seconds (no messages) |
| Credit cost | 1 credit / 2 min |
Geo-targeting (?country=XX) | ✅ |
| Max message size | 50 MB |
| Authentication | X-API-Key header |
Idle disconnect: The connection is closed if no CDP messages are exchanged for 60 seconds. Keep your automation actively sending commands, or call
browser.close()when done rather than leaving the connection open.
Saved Sessions
Load a saved browser session to connect with pre-authenticated cookies and localStorage. This lets your scripts access pages that require login — without handling authentication in code.
Pass ?session_id=<uuid> or ?session_name=<name> as a query parameter:
import asyncio
from playwright.async_api import async_playwright
API_KEY = "your_api_key"
SESSION_ID = "your_session_id" # from dashboard or GET /v1/sessions
async def main():
async with async_playwright() as p:
browser = await p.chromium.connect_over_cdp(
f"wss://api.anakin.io/v1/browser-connect?session_id={SESSION_ID}",
headers={"X-API-Key": API_KEY},
)
page = browser.contexts[0].pages[0]
# Navigate to an authenticated page — cookies are pre-loaded
await page.goto("https://amazon.com/your-orders", wait_until="domcontentloaded")
print("Title:", await page.title())
await browser.close()
asyncio.run(main())const { chromium } = require('playwright');
const API_KEY = 'your_api_key';
const SESSION_ID = 'your_session_id';
(async () => {
const browser = await chromium.connectOverCDP(
`wss://api.anakin.io/v1/browser-connect?session_id=${SESSION_ID}`,
{ headers: { 'X-API-Key': API_KEY } }
);
const page = browser.contexts()[0].pages()[0];
await page.goto('https://amazon.com/your-orders', { waitUntil: 'domcontentloaded' });
console.log('Title:', await page.title());
await browser.close();
})();const puppeteer = require('puppeteer-core');
const API_KEY = 'your_api_key';
const SESSION_ID = 'your_session_id';
(async () => {
const browser = await puppeteer.connect({
browserWSEndpoint: `wss://api.anakin.io/v1/browser-connect?session_id=${SESSION_ID}`,
headers: { 'X-API-Key': API_KEY },
});
const page = (await browser.pages())[0] || await browser.newPage();
await page.goto('https://amazon.com/your-orders', {
waitUntil: 'domcontentloaded',
timeout: 30000,
});
console.log('Title:', await page.title());
await browser.disconnect();
})();You can also use the session name instead of ID:
wss://api.anakin.io/v1/browser-connect?session_name=my-amazon-loginHow it works
- The API loads your saved session from encrypted storage
- The browser launches with your cookies and localStorage pre-injected
- If the session was created with a static IP proxy, the same proxy is reused — preventing IP mismatch issues
- Billing is the same (1 credit / 2 min) — no extra charge for session loading
Session query parameters
| Parameter | Description |
|---|---|
session_id | UUID of the saved session |
session_name | Name of the saved session (must be unique per user) |
If both are provided, session_id takes precedence. If neither is provided, a fresh browser is launched (default behavior).
Error responses
Session validation happens before the WebSocket connection is established, so errors are returned as standard HTTP responses:
| Status | Error | Meaning |
|---|---|---|
| 404 | session not found | Session ID/name doesn't exist or doesn't belong to you |
| 422 | session has no stored data — save it first | Session was created but never saved — log in and save first |
| 409 | session is being automated | Session is currently being used by an automation job |
Saving Sessions
You can also save a session directly from Browser API. Pass ?save_session=<name> when connecting, and the session is automatically saved when you disconnect — no extra API calls needed.
wss://api.anakin.io/v1/browser-connect?save_session=my-amazon-login&save_url=https://amazon.comimport asyncio
from playwright.async_api import async_playwright
API_KEY = "your_api_key"
async def main():
async with async_playwright() as p:
browser = await p.chromium.connect_over_cdp(
"wss://api.anakin.io/v1/browser-connect?save_session=my-amazon-login&save_url=https://amazon.com",
headers={"X-API-Key": API_KEY},
)
page = browser.contexts[0].pages[0]
# Log in programmatically
await page.goto("https://amazon.com/signin")
await page.fill("#email", "user@example.com")
await page.fill("#password", "mypassword")
await page.click("#signIn")
await page.wait_for_url("**/your-account**")
# Disconnect — session is auto-saved
await browser.close()
asyncio.run(main())const { chromium } = require('playwright');
const API_KEY = 'your_api_key';
(async () => {
const browser = await chromium.connectOverCDP(
'wss://api.anakin.io/v1/browser-connect?save_session=my-amazon-login&save_url=https://amazon.com',
{ headers: { 'X-API-Key': API_KEY } }
);
const page = browser.contexts()[0].pages()[0];
// Log in programmatically
await page.goto('https://amazon.com/signin');
await page.fill('#email', 'user@example.com');
await page.fill('#password', 'mypassword');
await page.click('#signIn');
// Disconnect — session is auto-saved
await browser.close();
})();Next time, load the saved session with ?session_name=my-amazon-login — see Saved Sessions above.
Save query parameters
| Parameter | Required | Description |
|---|---|---|
save_session | Yes | Name for the saved session (must be unique per user) |
save_url | No | Website URL associated with the session (e.g. https://amazon.com) |
Save error responses
Session name is validated before the WebSocket connection starts:
| Status | Error | Meaning |
|---|---|---|
| 409 | session name already exists | Choose a different name |
| 503 | session saving not configured | Server-side encryption not set up |
How saving works
- You connect with
?save_session=my-name - The session name is validated (must be unique) before the WebSocket upgrades
- You automate login, navigate pages, etc.
- When you disconnect, the browser's cookies and localStorage are automatically extracted and encrypted
- The session appears in your dashboard and
GET /v1/sessions - If the session proxy was a static IP, it's saved too — reuse preserves the same IP
Note: Save is best-effort on disconnect. If you need guaranteed saves with visual confirmation, use the interactive browser session flow instead.
Session Recording
Record your Browser API session as a video. Pass ?record=true when connecting — a WebM video is automatically captured and saved when you disconnect.
wss://api.anakin.io/v1/browser-connect?record=trueYou can combine recording with other features:
wss://api.anakin.io/v1/browser-connect?record=true&save_session=my-login&save_url=https://amazon.comRetrieving recordings
After disconnecting, your recording is available via the API:
# List all recordings
curl https://api.anakin.io/v1/recordings \
-H "X-API-Key: your_api_key"
# Get a specific recording (includes video URL)
curl https://api.anakin.io/v1/recordings/rec-123 \
-H "X-API-Key: your_api_key"The response includes a presigned video URL (valid for 1 hour):
{
"id": "abc-123",
"connId": "rec-123",
"duration": 45,
"fileSize": 304205,
"status": "completed",
"videoUrl": "https://s3.amazonaws.com/...",
"createdAt": "2026-04-01T12:00:00Z"
}Recordings are also available in your dashboard.
How recording works
- You connect with
?record=true - Playwright's built-in video recording captures all page activity as WebM
- When you disconnect, the video is finalized, uploaded to encrypted storage, and linked to your account
- No extra credit cost — recording is included in the standard Browser API rate
Geo-Targeting
Pass ?country=XX (ISO 3166-1 alpha-2) when connecting to route your session through a proxy in that country:
wss://api.anakin.io/v1/browser-connect?country=INSupported codes include US, IN, GB, DE, FR, JP, SG, and others depending on proxy availability. The proxy is selected via Thompson Sampling from the pool scored for that country and target domain. Defaults to US if not specified or if the requested country has no available proxies.