How Do You Drive Nstbrowser Profiles From Code?
You create a profile, start it, and read a CDP WebSocket URL β the nstbrowser api v2 exposes each step as a documented endpoint, and both official SDKs wrap them. Install the SDK, authenticate with an API key, create a profile with its user agent, timezone and screen settings, launch it, then attach Playwright, Puppeteer or Selenium to the returned debugger URL and run checks against sites and accounts you own or manage.

π TL;DR Executive Summary
- Core Takeaway: Nstbrowser API v2 covers the full lifecycle β create a profile, start it, read webSocketDebuggerUrl, attach Playwright or Puppeteer, stop it and clear local data β through the Browsers, Profiles, Groups, Proxy, Tags and Locals services.
- Key Risk/Challenge: Payloads sit one level down under data, the Node example points at a local client address, and startup on a cold host can outlast the default 60-second timeout, so naive scripts fail without saying why.
- Recommended Solution: Keep the API key in an environment variable, unwrap responses with a small helper, retry start calls with backoff, and stop the browser in a finally block so a crash never leaves profiles running against your launch quota.
What the Nstbrowser API v2 Covers
The v2 API groups calls into six services β Browsers, Profiles, Groups, Proxy, Tags and Locals β plus CDP connection endpoints. Nstbrowser points new users at v2 rather than v1, describing it as the more stable and standardised surface with v1’s functionality plus additions. Each service maps to a class in the SDK: client.browsers starts and stops instances, client.profiles creates and pages through them, client.locals clears cache and cookies.
Two official SDKs wrap those REST endpoints: pip install nstbrowser for Python and npm install nstbrowser-sdk-node for Node. Both are thin. Anything the SDK hasn’t exposed yet, runtime proxy switching among them, you can still call directly against the Nstbrowser API reference with requests or fetch.
Two caveats before you build. The SDK docs are dated early 2025, so a parameter name may have moved since; call each endpoint once against a scratch profile and print the raw response before wrapping it in abstractions. And Nstbrowser’s privacy policy states that using the product collects account information, access IP, operating system and browser type β worth passing to whoever signs off on vendor tooling.
Not into code? Nstbrowser also ships a no-code RPA tool with visual workflows, scheduled tasks, concurrent runs and JSON, Excel or CSV export; its RPA page describes that tool as free to use.
If you’re weighing how much of the profile lifecycle to automate at all, this browser profile management API tutorial runs the same createβstartβclean-up pattern against another vendor, which makes the trade-offs easier to see side by side.
Prerequisites: SDK Install, API Key and Client Setup
Get these in place before the first call. Everything after this section assumes them.
- Python 3.10+ with pip install nstbrowser playwright, or Node 18+ with npm install nstbrowser-sdk-node puppeteer-core.
- An API key from the Nstbrowser dashboard, stored as NSTBROWSER_API_KEY in your environment or secret manager β never in the file you commit.
- At least one profile in the account, or permission to create one. Profile ids are per account, so a colleague’s id won’t resolve in your workspace.
- No local Chromium download if you connect over CDP: Playwright and Puppeteer attach to the browser Nstbrowser already launched, so playwright install is optional for this workflow.
- A target URL you control β a staging site, your own storefront, a client portal you administer.
# Python: pip install nstbrowser
import os
from nstbrowser import NstbrowserClient
client = NstbrowserClient(api_key=os.environ["NSTBROWSER_API_KEY"])
def unwrap(res):
"""Return the payload whether the SDK hands back a dict or an object."""
body = res if isinstance(res, dict) else getattr(res, "data", None)
if isinstance(body, dict) and "data" in body:
return body["data"]
return body
// Node: npm install nstbrowser-sdk-node
const { NstBrowserV2 } = require('nstbrowser-sdk-node');
const nstbrowser = new NstBrowserV2(process.env.NSTBROWSER_API_KEY, {
timeout: 60000,
apiAddress: 'http://localhost:8848/api/v2',
});
The apiAddress in the Node docs points at localhost:8848, the local Nstbrowser client. If you call the hosted API instead, change that base URL and confirm the exact path in the reference β the SDK docs don’t spell out both cases clearly. The unwrap helper exists because responses mirror the REST envelope, with the useful payload one level down under data; it keeps every later step short and makes a changed response shape fail loudly instead of silently.
Step 1: Create a Profile That Matches the Device You’re Testing
Profiles are the unit of persistence, so create them deliberately rather than on the fly. A profile takes a name, a platform, a kernel milestone and a fingerprint object, and that object sets the user agent, timezone and screen dimensions the profile presents.
res = client.profiles.create_profile(data={
"name": "qa-checkout-eu",
"platform": "windows",
"kernelMilestone": "124",
"fingerprint": {
"userAgent": (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36"
),
"timezone": "Europe/Helsinki",
"screen": {"width": 1920, "height": 1080},
},
})
profile_id = unwrap(res)["profileId"]
print(profile_id)
platform picks the operating system the kernel presents, and kernelMilestone pins the Chromium major version. Keep the milestone identical across a test suite, or your screenshots and CSS assertions drift between runs for reasons that have nothing to do with your code. The fingerprint block changes the values the browser reports to a page β user agent, timezone, screen size β so match the device and market you actually support rather than something exotic. Your goal is a repeatable test case, and an unrealistic device profile makes failures harder to read.
The response carries the new profileId. Store it; every later call needs it, and hunting for it by name across pages of profiles is the most common way a script starts flaking after three months of use.
Step 2: Start the Browser and Read the Debugger URL
Starting a saved profile returns connection details for that browser instance. The value you need is webSocketDebuggerUrl β the Chrome DevTools Protocol endpoint your automation library attaches to.
res = client.browsers.start_browser(data={"profileId": profile_id})
info = unwrap(res)
print(info["webSocketDebuggerUrl"]) # ws://127.0.0.1:PORT/devtools/browser/...
Start errors here are almost always about identifiers or timing. A wrong profileId, a profile still running from a previous crashed job, or a slow cold start on a busy host will each fail differently β and the raw SDK exception rarely says which. Log the profile id, the call name and the elapsed time before you re-raise, and you’ll separate identifier mistakes from timeout mistakes at a glance.
The Node client timeout shown in the docs is 60000 ms. That covers a warm start comfortably; if your CI logs show intermittent failures on the first job of a pipeline, raise it rather than adding a blanket sleep.
Step 3: Attach Playwright or Puppeteer Over CDP
With the WebSocket URL in hand, the browser behaves like any Chromium instance your framework can attach to. Playwright’s connect_over_cdp avoids downloading browser binaries entirely, which keeps CI images small and version drift out of the picture.
from playwright.sync_api import sync_playwright
ws_endpoint = info["webSocketDebuggerUrl"]
with sync_playwright() as p:
browser = p.chromium.connect_over_cdp(ws_endpoint)
context = browser.contexts[0] # the profile's default context
page = context.pages[0] if context.pages else context.new_page()
page.goto("https://your-staging-site.example", wait_until="domcontentloaded")
print(page.title())
browser.close() # drops your CDP connection
contexts[0] is the default context the profile opened with, so anything the profile kept β cookies, a signed-in session, stored local data β is already there. browser.close() drops your client’s connection; it does not reliably terminate the remote process, which is why the full script calls stop_browser in a finally block. Treat “disconnect” and “stop” as two separate steps and your launch counts stay honest.
Puppeteer in Node
Puppeteer takes the same URL under a different parameter name. Pass defaultViewport: null so the page keeps the profile’s window size instead of being resized to the library default, which would quietly invalidate any layout assertion you wrote.
const puppeteer = require('puppeteer-core');
async function run(wsEndpoint) {
const browser = await puppeteer.connect({
browserWSEndpoint: wsEndpoint,
defaultViewport: null,
});
const [page] = await browser.pages();
await page.goto('https://your-staging-site.example', { waitUntil: 'domcontentloaded' });
console.log(await page.title());
await browser.disconnect();
}
run(process.env.NSTBROWSER_WS).catch((err) => {
console.error('CDP attach failed:', err.message);
process.exit(1);
});
Selenium follows the same idea: set debuggerAddress in ChromeOptions to the debugging address the start call returned and let the driver attach. That route is fussier about matching the driver version to the kernel milestone you pinned, so keep one Chromium major version per environment. If you want to compare remote-browser endpoints before committing to CDP plumbing, call a browserless API from Python covers the other shape of the same problem.
Step 4: Set Proxies and Choose Standard or Once Profiles
Proxy settings attach at launch, so the exit IP is in place before the first page loads rather than being swapped mid-session. The one-off launcher shows this most clearly: start_once_browser takes name, platform, kernelMilestone, headless, proxy and startupUrls in a single call.
res = client.browsers.start_once_browser(
name="regional-check",
platform="windows",
kernelMilestone="124",
headless=False,
proxy={
"type": "socks5",
"host": "proxy.example.net",
"port": 1080,
"username": os.environ["PROXY_USER"],
"password": os.environ["PROXY_PASS"],
},
startupUrls=["https://your-staging-site.example"],
)
print(unwrap(res)["webSocketDebuggerUrl"])
Because a once profile isn’t saved, the next run starts clean β no stale cookies from last week’s test and no leftover session. For a saved profile, set the proxy through the Proxy service as part of the profile configuration so every launch inherits it. The CDP reference also lists Network.updateContextProxy for changing a context’s proxy while a session runs; confirm its parameter shape in the reference before depending on runtime rotation, since the endpoint list is newer than parts of the SDK examples.
| Behaviour | Standard profile | Once profile |
|---|---|---|
| Cookies and login sessions | Kept between launches | Cleared when the session ends |
| Fingerprint settings | Kept | Cleared |
| Local data (storage, cache) | Kept | Cleared |
| Typical use | Repeatable QA logins, client accounts you manage | One-off checks that must start signed out |
Step 5: Batch Launches, Stopping and Clearing Profile Data
Teams use the API in one of two shapes. Per-profile automation starts a single browser per test: slower, but isolated and easy to reason about when something fails. Batch launches start several profiles together: faster for cross-region checks, but they punish sloppy cleanup because open browsers keep consuming launches.
Paging through profiles first keeps a batch explicit rather than accidental, and the stop call takes a list of ids so you don’t fire one request per profile.
listing = unwrap(client.profiles.get_profiles(data={"page": 1, "pageSize": 20})) or {}
rows = listing.get("rows") or listing.get("list") or []
profile_ids = [row["profileId"] for row in rows]
print(len(profile_ids), "profiles on page 1")
# stop a group in one call
client.browsers.stop_browsers(data={"profileIds": profile_ids[:3]})
# wipe local state for one profile before a run that must start signed out
client.locals.clear_profile_cache(profile_id=profile_id)
client.locals.clear_profile_cookies(profile_id=profile_id)
clear_profile_cache and clear_profile_cookies both take a single profile_id from the Locals service and wipe local state for that profile only. Run them while the browser is stopped β clearing files under a running instance is how you get half-deleted storage and a profile that behaves strangely for the rest of the week.
Cap how many profiles you start at once, and remember the limit is per account, not per script: two CI jobs each launching their own batch still add up against the same daily allowance. Stop everything in a finally block so a failed assertion doesn’t leave ten browsers running and the next job failing for a reason that looks unrelated.
Common Nstbrowser API Errors and How to Fix Them
Most failures fall into four buckets: authentication, wrong identifiers, timing and plan limits.
| Symptom | Likely cause | Fix |
|---|---|---|
| 401 or “invalid api key” | Key missing, rotated, or copied with a trailing space | Read it from an env var, strip whitespace, re-issue from the dashboard |
| “profile not found” | Id from another account or team, or the profile was deleted | List profiles with get_profiles and use the exact profileId |
| Start call times out | Cold start longer than the client timeout (60 s by default in the Node docs) | Raise the timeout, retry with backoff, cap at three attempts |
| CDP attach refused | You passed an HTTP address instead of the ws:// URL | Use webSocketDebuggerUrl exactly as returned |
| Second start fails | Profile already running from a crashed job | Call stop_browser first, or check get_browsers |
| Launch limit or credit errors | Plan allowance reached, or extra usage not enabled | Reduce concurrency, check the plan limits, buy overage if it’s expected |
Retry policy matters more than retry code. Retry start calls with exponential backoff and a hard cap of three attempts, and never retry a 401 β a bad key will fail identically every time and multiplies your request count. Log the response body on the first failure of each run; the SDK’s exception message is often shorter than the server’s.
Then there are limits, which fail quietly in CI until someone checks the invoice. The tiers listed in a review of Nstbrowser’s pricing page dated 8 October 2026 were:
| Plan | Price | Profiles | Launches per day | Seats |
|---|---|---|---|---|
| Free | $0 | 500 | 30 | 3 |
| Starter | $29.90/mo | 20,000 | 3,000 | 20 |
| Growth | $49.90/mo | 100,000 | 20,000 | 50 |
| Scale | $89.90/mo | 500,000 | 50,000 | 100 |
Overage usage bills separately, and downgrades aren’t supported during an active term, so pick a tier from measured launch volume rather than a guess. Nstbrowser pricing and launch limits breaks down how tiers and overages interact before a team commits to one. Confirm the numbers on the vendor’s own pricing page first β third-party snapshots age quickly.
The Full Script: Create, Start, Attach, Clean Up
Put together, the workflow is about sixty lines. Save it as run_profile.py, set NSTBROWSER_API_KEY and optionally TARGET_URL, and run it against a site you control.
"""Create or reuse a profile, run a CDP session, then stop the browser.
pip install nstbrowser playwright
Environment: NSTBROWSER_API_KEY (required), TARGET_URL (optional).
"""
import os
import time
from nstbrowser import NstbrowserClient
from playwright.sync_api import sync_playwright
client = NstbrowserClient(api_key=os.environ["NSTBROWSER_API_KEY"])
TARGET_URL = os.environ.get("TARGET_URL", "https://example.com")
def unwrap(res):
body = res if isinstance(res, dict) else getattr(res, "data", None)
if isinstance(body, dict) and "data" in body:
return body["data"]
return body
def rows_of(listing):
# print one raw response first: the collection key has changed between releases
for key in ("rows", "list", "items"):
if isinstance(listing, dict) and key in listing:
return listing[key]
return []
def with_retry(fn, attempts=3, base_delay=2.0):
for i in range(attempts):
try:
return fn()
except Exception: # the SDK raises on non-2xx responses
if i == attempts - 1:
raise
time.sleep(base_delay * (2 ** i))
def get_or_create_profile(name):
listing = unwrap(client.profiles.get_profiles(data={"page": 1, "pageSize": 50})) or {}
for row in rows_of(listing):
if row.get("name") == name:
return row["profileId"]
created = unwrap(client.profiles.create_profile(data={
"name": name,
"platform": "windows",
"kernelMilestone": "124",
"fingerprint": {
"userAgent": (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36"
),
"timezone": "Europe/Helsinki",
"screen": {"width": 1920, "height": 1080},
},
}))
return created["profileId"]
def main():
profile_id = get_or_create_profile("qa-checkout-eu")
started = with_retry(
lambda: unwrap(client.browsers.start_browser(data={"profileId": profile_id}))
)
ws_endpoint = started["webSocketDebuggerUrl"]
try:
with sync_playwright() as p:
browser = p.chromium.connect_over_cdp(ws_endpoint)
context = browser.contexts[0]
page = context.pages[0] if context.pages else context.new_page()
page.goto(TARGET_URL, wait_until="domcontentloaded")
print("title:", page.title())
print("url:", page.url)
browser.close()
finally:
try:
client.browsers.stop_browser(data={"profileId": profile_id})
print("stopped", profile_id)
except Exception as exc:
print("stop_browser failed:", exc)
if __name__ == "__main__":
main()
The script is idempotent by name: rerun it and it reuses the existing profile instead of piling up duplicates. The finally block is the part people strip out first and regret later, because a stopped browser is the only thing standing between an exception and a leaked launch.
When You Don’t Need Code at All
Plenty of teams reach for an automation library when their real problem is handing a logged-in profile to a colleague. Those are different jobs. If the work is “open the client’s storefront as them, in an isolated profile, without emailing a password”, a script is overhead you maintain forever.
That’s the case Send.win is built for: isolated browser profiles in a Chromium-based desktop app for Windows, macOS and Linux, or in a cloud browser running from Helsinki, with per-profile fingerprint protection and residential proxies on paid plans. You share a profile with a paid teammate and it opens already signed in β no password changes hands, nothing to maintain. Send.win is a browser you open and work in by hand, one profile at a time, so the coded pipeline above and Send.win are two separate choices rather than layers of one setup. For a closer look at how the two approaches line up, this Nstbrowser alternative comparison covers the practical differences.
π Send.win Verdict
If the underlying job is getting isolated client profiles in front of the right people β opened, shared and already signed in β Send.win covers that ground without a build, retry logic or launch accounting. Each profile keeps its own cookies, storage, sessions and consistent fingerprint settings, teammates open a shared profile already signed in, and profiles run either in the desktop app or in the EU cloud browser from any device. It is a browser people work in directly, by hand, so treat it as an alternative to building the pipeline above rather than a layer inside it.
See Send.win plans β Pro from $19/mo (or $83.88/yr) with 150 profiles, 5 GB of proxy bandwidth and 6 seats, or try the free cloud demo first: a 3-minute preview with no signup, and 10 minutes a day on a free account.
Frequently Asked Questions
How do I install and initialise the Nstbrowser API SDK?
For Python, run pip install nstbrowser and create the client with NstbrowserClient(api_key=”…”). For Node, run npm install nstbrowser-sdk-node and call new NstBrowserV2(apiKey, { timeout: 60000, apiAddress: ‘http://localhost:8848/api/v2’ }). Load the key from an environment variable rather than pasting it into source.
How Send.win Helps With Nstbrowser Api
Send.win is a browser profile manager for professionals and teams: every account gets its own isolated browser profile.
- Isolated profiles β separate cookies, storage and sessions per profile
- Fingerprint protection β each profile keeps its own consistent browser fingerprint, so profiles never share one browser identity
- Desktop app + cloud browser β native app for Windows, macOS and Linux, or run profiles in the cloud with no install
- Residential proxies included β on Pro and Team, with timezone and locale matched to the proxy
- Team sharing β share a signed-in profile with a teammate without sharing the password
Try the free cloud demo β no install β or download the desktop app. Paid plans start at $19/month, and your first card purchase is covered by a 7-day money-back guarantee (see pricing).
What is the difference between Nstbrowser API v1 and v2?
v2 is the version Nstbrowser recommends for new work: it covers v1’s functionality plus additions, with services for Browsers, Profiles, Groups, Proxy, Tags and Locals plus CDP connection endpoints. The docs steer new users to v2, so older v1 snippets you find online may use parameters that no longer apply.
How do I start a browser profile with the Nstbrowser API?
Call StartBrowser through the SDK β client.browsers.start_browser(data={“profileId”: …}) β and read webSocketDebuggerUrl from the response payload. For a browser you don’t want to save, use start_once_browser with a name, platform, kernel milestone, headless flag, proxy and startup URLs instead.
How do I connect Puppeteer to Nstbrowser over CDP?
Take the value at cdpResponse.data.webSocketDebuggerUrl and pass it to puppeteer.connect({ browserWSEndpoint: url, defaultViewport: null }). When the job finishes, call browser.disconnect() to close your side, then stop the profile through the API β disconnecting doesn’t reliably shut the remote browser down.
How do I set or change a proxy for a profile?
Pass a proxy object (type, host, port, username, password) at launch β start_once_browser accepts one directly β or configure it on the saved profile through the Proxy service so every launch inherits it. The CDP reference also lists Network.updateContextProxy for changing a context’s proxy at runtime; confirm its parameter shape in the reference before relying on it.
Can I clear cookies and cache through the API?
Yes. The Locals service exposes clear_profile_cache and clear_profile_cookies, both taking a profile_id, which wipes local state for that profile alone. Run them while the browser is stopped so you don’t delete files under a running instance.
What’s the difference between Standard and Once profiles?
Standard profiles keep cookies, login sessions, fingerprint settings and local data between launches, so a test can resume signed in. Once profiles clear that data when the session ends, which suits one-off checks that must start from a clean slate. Pick per workflow, not per team.
How much does Nstbrowser cost, and what do overages mean?
Tiers listed on 8 October 2026 were Free, Starter at $29.90/mo, Growth at $49.90/mo and Scale at $89.90/mo, each with different profile counts, daily launch limits and seat counts. Usage beyond plan limits bills separately as overage, and downgrades aren’t supported during an active term β so size the plan from measured launches and check the vendor’s own pricing page before committing.