Run a real Chrome browser, on our infrastructure, through your proxy.
The Scraping Browser API spins up a remote, fingerprint-hardened Chrome session routed through your NodeMaven proxy, then hands you plain REST endpoints to configure it, drive it, and pull back whatever the page gave you - HTML, a screenshot, a PDF, or the return value of a script you ran on it.
api.browser.nodemaven.com- sessions & automationBearer token - request auth
dashboard - where your token lives
JSON everywhere
Quickstart flow
Four steps, start to finish - from opening your dashboard to reading back scraped data.
1. Copy your token | 2. Configure & launch |
Open the Scraping Browser page in the NodeMaven dashboard and copy the API token shown there. Every call below is authenticated with it. | Pick a country/city, stealth options, and proxy protocol, then create a session. |
|
|
3. Automate it | 4. Read the results |
Navigate, click, type, or run a script - or connect your own Playwright/Selenium/Puppeteer script directly. | Pull back the page's HTML, a screenshot, a PDF, or your script's return value - then close the session. |
|
|
This is the whole surface. Everything else in this reference - locations, profiles, page actions - exists to support these four steps: proxy/location endpoints feed step 2, profiles let step 2 remember a setup across sessions, and page actions (click/type/scroll) are the fine-grained half of step 3.
Authentication
One token, one header. Your token is shown in the dashboard - copy it and send it as a Bearer token on every request.
Where to get it | How to send it |
The Scraping Browser page in the NodeMaven dashboard displays your API token. Copy it from there - there's nothing to generate or exchange. |
|
# Every request uses the token from your dashboard
curl https://api.browser.nodemaven.com/api/v2/browser/sessions \
-H "Authorization: Bearer YOUR_BROWSER_TOKEN"
Keep it secret. This token authorizes browser sessions on your account and proxy traffic billed to it. Treat it like a password: keep it server-side, out of client-side code and version control.
Errors & status codes
Every error response is a flat JSON object with a single error string. A few endpoints attach extra machine-readable fields - noted below.
|
Status | Meaning | Notes |
400 | Bad request | Malformed JSON, or a value outside what's supported (unknown protocol, out-of-range limit, ...). |
401 | Unauthorized | Missing or invalid |
404 | Not found | Also returned for a session/profile you don't own - ownership never leaks as a distinct error. |
429 | Too many requests | Concurrency limit hit. Body adds |
502 | Bad gateway | The browser/proxy upstream failed or was unreachable for this call. |
503 | Service unavailable | A feature is temporarily disabled. Body adds |
Limits
Defaults below - configurable per account; ask support if your workload needs more.
Limit | Default |
Max session lifetime | 30 minutes |
Idle timeout | 10 minutes |
Concurrent sessions per account | 10 |
Saved profiles per account | 10 |
API token lifetime | 7 days |
Proxy & location configuration
Everything a session's country/region/city/isp/zip fields accept, and a way to check a combination before you spend a session on it.
|
The static vocabulary a session's proxy targeting understands - connection types, session types, protocols, and the drill-down order for the location endpoints below |
200 Response
{
"data": {
"connection_types": [
{ "value": "residential", "label": "Residential", "default": true },
{ "value": "mobile", "label": "Mobile" }
],
"session_types": [
{ "value": "sticky", "label": "Sticky (keep the same IP)", "default": true },
{ "value": "rotating", "label": "Rotating (new IP each connection)" },
{ "value": "no_rotating", "label": "No rotating (pin IP, never rotate)" }
],
"protocols": [
{ "value": "http", "label": "HTTP", "default": true },
{ "value": "socks5", "label": "SOCKS5 (recommended for anti-detection)" }
],
"filter_modes": [
{ "value": "quality", "label": "Quality (default) - highest quality IP, any speed", "default": true },
{ "value": "quality_speed", "label": "Quality + Speed - best quality and fast IPs" },
{ "value": "speed", "label": "Speed - fastest IPs, any quality" },
{ "value": "none", "label": "Max pool size - no filters applied" }
],
"filter_levels": { "quality": "medium", "speed": "fast" },
"quality_filter": {
"description": "DEPRECATED - use filter_mode. true is equivalent to filter_mode \"quality\", false to \"none\".",
"default": true, "deprecated": true
},
"location_levels": ["countries", "regions", "cities", "isps", "zip-codes"]
}
}
Build your IP-filter dropdown from filter_modes rather than hardcoding it - these are the same four modes, in the same order, with the same default as the IP filter mode selector on the main proxy dashboard. filter_levels shows what the quality/speed tokens currently resolve to; those values are platform config, not something a request chooses.
|
Drill down through real, currently-available targeting data - the same reference geo-data the main NodeMaven proxy dashboard uses. Filter each level by the one above it. |
Query param |
| Description |
| optional |
|
| optional | Required by |
| optional | Required by |
| optional | Narrows |
| optional |
|
| optional | Standard filtering/pagination. |
Request:
GET /api/v2/browser/locations/cities?country=us®ion=california
200 Response
{
"data": [
{
"code": "los-angeles",
"name": "Los Angeles",
"availability": "high",
"country_code": "us",
"region_code": "california"
}
]
}503 location data not configured for this deployment
|
Checks a targeting config before you spend a session creating it, and previews the resulting proxy username - using the exact same logic session-create uses, so a config that validates here will create identically. |
Request body
{
"connection_type": "residential",
"session_type": "sticky",
"protocol": "http",
"country": "us",
"region": "california",
"filter_mode": "quality_speed"
}200 Response
{
"data": {
"valid": true,
"normalized": {
"country": "us", "region": "california",
"protocol": "http", "connection_type": "residential",
"session_type": "sticky", "filter_mode": "quality_speed",
"quality_filter": true, "ipv4_only": false
},
"issues": null,
"proxy_username_preview":
"user123-country-us-region-california-sid-8f3a2b-filter-medium-speed-fast-source-browser"
}
}An invalid config still returns 200 with valid: false and an issues array of {code, field, message, suggestion} - this endpoint reports problems, it doesn't error on them. A bad filter_mode, for example, comes back as {"code": "unsupported_filter_mode", "field": "filter_mode", ...} with the four valid values in suggestion.
Check valid, not issues.length. When a config is clean, issues is null rather than an empty array - iterating it without a null check will throw.
What each filter mode puts in the proxy username
Use this endpoint to see it for your own config - the preview is generated by the same code that creates sessions, so what you see here is exactly what a session will use.
filter_mode | Tokens added |
|
|
|
|
|
|
|
|
medium/fast above are the current filter_levels from proxy/options; they're platform config and can change, so read them from there rather than assuming.
Sessions
A session is one live, disposable Chrome browser, already routed through your proxy. Everything under Automation & results operates on a session's id.
|
Launches a browser and returns its connection URL immediately. This is step 3 of the quickstart flow. |
Field |
| Description |
| required | Your NodeMaven proxy password. A session can't be created without an active proxy attached. |
| optional |
|
| optional | From the locations endpoints. Omit for a broad, unrestricted exit. |
| optional |
|
| optional |
|
| optional | Which IP filter to apply: |
| deprecated | Superseded by |
| optional | Pin the session to IPv4 exit IPs. Defaults to off. |
| optional | Anti-detection hardening (masks automation tells, spoofs a real browser fingerprint). |
| optional | Booleans, self-describing. |
| optional | 0 (default) uses the account limit - see Limits. |
| optional | Reuse a saved profile - its saved-cookies/cache and (if enabled) pinned exit IP. |
Request body
{
"proxy_password": "YOUR_PROXY_PASSWORD",
"country": "us",
"region": "california",
"session_type": "sticky",
"extra_stealth": true
}200 Response
{
"data": {
"id": "ses_8f2a41c9",
"cdp_url":
"wss://browser.nodemaven.com/sessions/ses_8f2a41c9?token=...",
"status": "running"
}
}
|
|
|
|
|
Lists your sessions. Filter with |
200 Response
{
"data": [
{
"id": "ses_8f2a41c9", "status": "running",
"created_at": "2026-08-07T09:14:02Z",
"config": { "country": "us", "region": "california", "protocol": "http", "...": "..." }
}
]
}The list omits cdp_url on purpose - fetch a single session or its connection info when you're ready to connect to one.
|
Full detail on one session, including its |
200 Response
{
"data": {
"id": "ses_8f2a41c9", "status": "running",
"created_at": "2026-08-07T09:14:02Z", "ended_at": null,
"config": {
"country": "us", "region": "california",
"protocol": "http", "connection_type": "residential", "session_type": "sticky",
"filter_mode": "quality_speed", "quality_filter_enabled": true,
"ipv4_only": false,
"profile_name": "checkout-flow", "profile_id": "prof_a12f"
},
"cdp_url": "wss://browser.nodemaven.com/sessions/ses_8f2a41c9?token=..."
}
}config is the resolved targeting the session actually used, with server defaults already applied - so it's how you confirm after the fact what a session ran with, including which filter_mode was in effect. It never contains your proxy password or the raw proxy username. Location fields are omitted when empty; quality_filter_enabled is the deprecated boolean view of filter_mode (true for the quality-bearing modes).
|
|
Everything needed to connect a script or a browser tab to this session - see Connecting to a session for how each field is used. |
200 Response
{
"data": {
"id": "ses_8f2a41c9", "status": "running",
"cdp_url": "wss://browser.nodemaven.com/sessions/ses_8f2a41c9?token=...",
"debugger_address": "7f3a9c....browser.nodemaven.com",
"live_view_url": "https://browser.nodemaven.com/live/7f3a9c...",
"config": { "...": "..." }
}
}
|
Lists the tabs/pages currently open in the session. |
200 Response
{ "data": [ { "id": "9A3F...", "title": "Example Domain", "type": "page", "url": "https://example.com/" } ] }
|
Video replay of the session. Empty while the session is still running - recordings are only published once it ends. |
200 Response
{
"data": [
{
"id": "rec_1", "is_primary": true,
"duration_seconds": 42.6, "size_bytes": 1889302,
"video_url": "https://...mp4?Expires=...&Signature=...",
"video_url_expires_at": "2026-08-07T09:24:11Z"
}
]
}video_url is short-lived (~10 minutes) - re-fetch this endpoint at play time rather than caching the URL.
|
Ends the session immediately. Sessions also auto-end on idle timeout or max lifetime, so this is for when you're done early. |
200 Response
{ "data": { "status": "success" } }Creating many sessions at once? POST /api/v2/browser/sessions-async queues a create and returns a request_id immediately; poll GET /api/v2/browser/sessions-async/{request_id} for the result. Same fields as the synchronous create above - use it when you're launching a batch and don't want to hold a connection open per session.
Profiles
A profile is a saved browser identity - cookies and cache, and optionally a pinned exit IP - that a new session can reattach to instead of starting cold every time.
|
Field |
| Description |
name | required | Unique to your account. |
description | optional | Free text. |
store_cache | optional | Persist cookies/local storage/cache across sessions using this profile. |
pin_ip | optional | Lock the exit IP a session using this profile gets, from its first use onward - useful for sites that re-challenge on IP change. |
200 Response
{
"data": {
"id": "prof_a12f", "name": "checkout-flow", "description": "",
"created_at": "2026-08-07T09:14:02Z", "pin_ip": true
}
}Once a session has used this profile, GET/LIST also return pinned_country, pinned_region, pinned_city, pinned_isp, pinned_zip, pinned_connection_type - where it's actually pinned to.
|
|
Lists every profile on your account. Same shape as a single profile, in a |
|
Fetches one profile by id. |
|
Renames a profile / updates its description. Body: |
|
Deletes a profile. Sessions already using it are unaffected. |
Automation & results
Server-side actions on a running session - no client library required, just POST a JSON body. Every result comes back inline in the response.
POST |
Loads a URL in the session's active page. |
Request
{ "session_id": "ses_8f2a41c9", "url": "https://example.com/search?q=nike" }200 Response
{ "data": { "status": "ok" } }POST |
Returns the current page's full rendered HTML. |
Request
{ "session_id": "ses_8f2a41c9" }200 Response
{ "data": { "content": "<html>...</html>" } }POST |
Captures the current page as a PNG. |
Request
{ "session_id": "ses_8f2a41c9" }200 Response
{ "data": { "screenshot": "iVBORw0KGgo...", "format": "png" } }screenshot is base64-encoded PNG bytes - decode and write to a file, or drop straight into an <img src="data:image/png;base64,...">.
POST |
Runs JavaScript in the page and returns its value - this is "pass automation as a script." |
Request
{
"session_id": "ses_8f2a41c9",
"code": "document.querySelectorAll('.price').length"
}200 Response
{ "data": { "result": 48 } }result is whatever your script returns - a number, string, array, or object - passed through as native JSON.
POST |
Renders the current page to a PDF. |
Request
{ "session_id": "ses_8f2a41c9" }200 Response
{ "data": { "pdf": "JVBERi0xLjQK...", "format": "pdf" } }POST |
Fine-grained page actions - the session id is in the path here, not the body, since each hangs off one specific session. |
Route | Body |
|
|
|
|
|
|
All three return {"data": {"status": "ok"}} on success.
|
|
|
Connecting to a session
The /tools/* endpoints cover most automation without any client library. For full control, connect directly using whatever's already in your stack.
Playwright / Puppeteer
Use cdp_url as-is with connectOverCDP - full CDP control over the exact same browser.
// playwright
const browser = await chromium.connectOverCDP(cdpUrl);
Selenium
Set debugger_address as Chrome's remote-debugging address - Selenium discovers and attaches automatically.
# selenium
opts.debugger_address = debugger_address
Live view
Open live_view_url in any browser tab (or embed it in an <iframe>) to watch the session render in real time - handy while developing a script.
Recording playback
Once a session ends, its recordings give you a video replay - useful for auditing what an automated run actually did.
Full walkthrough
Every step above, chained into one script: launch a session in California, load a page, read back the price data, screenshot it, then close it down.
#!/bin/bash
# Your token, copied from the dashboard's Scraping Browser page.
TOKEN="$NM_BROWSER_TOKEN"
# 1. create a session in California, over HTTP proxy, extra stealth on
SESSION=$(curl -s https://api.browser.nodemaven.com/api/v2/browser/sessions \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"proxy_password":"'"$NM_PROXY_PASSWORD"'","country":"us","region":"california","extra_stealth":true}')
SESSION_ID=$(echo $SESSION | jq -r .data.id)
# 2. navigate
curl -s https://api.browser.nodemaven.com/api/v2/browser/tools/navigate \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"session_id":"'"$SESSION_ID"'","url":"https://example-shop.com/search?q=nike+air+max+95"}'
# 3. read results - extracted data as JSON, plus a screenshot
curl -s https://api.browser.nodemaven.com/api/v2/browser/tools/execute-code \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"session_id":"'"$SESSION_ID"'","code":"[...document.querySelectorAll(\".price\")].map(e=>e.textContent)"}'
curl -s https://api.browser.nodemaven.com/api/v2/browser/tools/screenshot \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"session_id":"'"$SESSION_ID"'"}' | jq -r .data.screenshot | base64 -d > result.png
# 4. done
curl -s -X DELETE https://api.browser.nodemaven.com/api/v2/browser/sessions/$SESSION_ID \
-H "Authorization: Bearer $TOKEN"
For any questions contact: [email protected]