ADSSPEED

Guides

Control Browser Profiles With a Local API: Selenium, Puppeteer and Playwright Examples

By ADSSPEED Team · Published · Updated · 7 min read

How does it work?

The local API is a small web service that runs on your own computer, alongside the ADSSPEED app. You call it from a script to open and close profiles, and it hands back the ports that let your automation tool attach to the profile's browser.

Because the tool attaches to the profile's own browser, your script runs inside that profile: its cookies, its login, its proxy and its device settings.

Before you start

  1. Open ADSSPEED and sign in. The API runs with the app, so closing the app stops it.
  2. In version 2.9.220 the API is off by default. Open Settings → API & Integrations and switch on the local API. Newer builds switch it on at startup.
  3. On the same page, note the port (19555 by default) and copy the API key. Pressing "change key" makes every script using the old key stop working.
  4. Check that it answers. This call needs no key:
import requests
print(requests.get("http://127.0.0.1:19555/status").json())

A healthy reply looks like {"code": 0, "data": {"version": "...", "ready": true, "api": "v1"}, "msg": "success"}. The version field tells you which documentation to read.

Open a profile in five lines

import requests
KEY = "API_KEY"
r = requests.get("http://127.0.0.1:19555/api/v1/browser/start",
                 params={"profile_id": 1}, headers={"X-API-Key": KEY}, timeout=120).json()
print(r["data"]["ws"])   # the addresses your tool attaches to

profile_id is the profile's number in the app (1, 2, 3 and so on). The reply contains data.ws.selenium and data.ws.puppeteer, which the next sections use.

Attach Selenium (Python)

import requests
from selenium import webdriver
from selenium.webdriver.chrome.options import Options

BASE, KEY = "http://127.0.0.1:19555", "API_KEY"
r = requests.get(f"{BASE}/api/v1/browser/start", params={"profile_id": 1},
                 headers={"X-API-Key": KEY}, timeout=120).json()
if r["code"] != 0:
    raise SystemExit(r["msg"])

opts = Options()
opts.add_experimental_option("debuggerAddress", r["data"]["ws"]["selenium"])
driver = webdriver.Chrome(options=opts)   # attaches to the running browser
driver.get("https://ip.me/")
print(driver.title)

Selenium needs a chromedriver with the same version as the profile's browser. To read that version, open http://127.0.0.1:<debug_port>/json/version and look at the Browser field. Selenium's automatic driver management may look at a Chrome installed on your computer instead, so if the versions don't match, pass the driver path yourself with Service(executable_path=...) from selenium.webdriver.chrome.service.

The debuggerAddress option takes a host:port address, which is exactly what data.ws.selenium contains.

Attach Puppeteer (Node.js)

// npm i puppeteer-core   ·   Node 18+, save as .mjs
import puppeteer from "puppeteer-core";

const BASE = "http://127.0.0.1:19555", KEY = "API_KEY";
const r = await (await fetch(`${BASE}/api/v1/browser/start?profile_id=1`,
                             { headers: { "X-API-Key": KEY } })).json();
if (r.code !== 0) throw new Error(r.msg);

// defaultViewport: null keeps the profile's own window size
const browser = await puppeteer.connect({ browserWSEndpoint: r.data.ws.puppeteer, defaultViewport: null });
const [page] = await browser.pages();
await page.goto("https://ip.me/");
console.log(await page.title());
browser.disconnect();   // detach; does NOT close the profile's browser

Attach Playwright (Python)

# pip install playwright requests
import requests
from playwright.sync_api import sync_playwright

BASE, KEY = "http://127.0.0.1:19555", "API_KEY"
r = requests.get(f"{BASE}/api/v1/browser/start", params={"profile_id": 1},
                 headers={"X-API-Key": KEY}, timeout=120).json()

with sync_playwright() as p:
    browser = p.chromium.connect_over_cdp(r["data"]["ws"]["puppeteer"])
    ctx = browser.contexts[0]                       # the profile's own context
    page = ctx.pages[0] if ctx.pages else ctx.new_page()
    page.goto("https://ip.me/")
    print(page.title())

Always reuse the profile's existing context and tab. Creating a new one starts a clean session without the profile's cookies, which is the most common reason for a blank page and a lost login. Playwright's documentation also notes that connecting over CDP works only with Chromium-based browsers and has lower fidelity than its own protocol connection.

Close the profile

When your script is done, detach the tool (driver.quit() closes Selenium's session, browser.disconnect() detaches Puppeteer), then close the profile itself:

requests.get(f"{BASE}/api/v1/browser/stop", params={"profile_id": 1}, headers={"X-API-Key": KEY})

Authentication and the response format

Every command except /status needs the key. Send it in a header (recommended) or as a parameter:

X-API-Key: ads-1a2b3c4d
/api/v1/profile/list?key=ads-1a2b3c4d

Every reply has the same shape, {code, data, msg}. Check code before you read data.

codeHTTPMeaning
0200Success
-1200A business error; msg explains it (for example an invalid proxy or a profile that wasn't found)
401401Missing or wrong key
404404No such path, or your app version doesn't have that command yet
429200A heavy command was called more than once per second

Newer builds also return 403 when the app isn't signed in yet.

Open many profiles safely

Opening a profile is a heavy command: at most one per second, otherwise you get 429. A first open with a proxy can take several seconds, so use a generous timeout (the docs advise at least 90 seconds). Once profiles are open you can drive them in parallel, with one driver or browser connection per profile. Wrap each profile in try/finally so it always closes:

import time, requests
BASE, KEY = "http://127.0.0.1:19555", "API_KEY"
H = {"X-API-Key": KEY}

def call(path, **params):
    r = requests.get(f"{BASE}{path}", params=params, headers=H, timeout=120).json()
    if r["code"] != 0:
        raise RuntimeError(f'{path}: {r["code"]} {r["msg"]}')
    return r["data"]

for pid in (1, 2, 3):
    data = call("/api/v1/browser/start", profile_id=pid)
    try:
        ...   # attach your tool to data["ws"]["selenium"] or data["ws"]["puppeteer"], do the work
    finally:
        call("/api/v1/browser/stop", profile_id=pid)
    time.sleep(1.1)    # heavy commands: one per second at most

Common problems

ProblemWhat to try
"Connection refused"The API is off, the app isn't open, or another program holds the port. Switch the API on in Settings → API & Integrations, or change the port and update your script.
401The key is missing or wrong. Copy it again from the same settings page; after "change key", update your scripts.
404 on a commandYour app version doesn't have it. Check GET /status, read the docs for that version, or update the app.
429 when openingWait one second between opens.
"This version of ChromeDriver only supports…"Use a chromedriver with the same version as the profile's browser (see /json/version).
Blank page, no login (Puppeteer or Playwright)You created a new context. Use browser.contexts[0] or browser.pages(), with defaultViewport: null in Puppeteer.
Node error ECONNREFUSED ::1Use 127.0.0.1 rather than localhost; newer Node versions can resolve localhost to the IPv6 address ::1.
Deleted a profile by mistakeDeleted profiles go to the trash and can be restored in the app. A permanent delete can't be undone.

What the API can and can't do

  • It does what the app allows on your plan, no more. Profiles created through the API count toward your plan like any other.
  • Secrets stay hidden. Proxy passwords are always returned as ***.
  • Fingerprints are built by the app for each profile, so the API doesn't edit them. For a custom setup, create a sample configuration in the app and pick it when you create profiles.
  • Phone profiles aren't in the API yet. It covers browser profiles.

In 2.9.220 the API has 27 commands in six groups: profile control, profile information, profile operations, proxies (including your own mobile proxies from a USB modem), extensions, and system status. Newer builds add scripts, campaigns, schedules, cookies and more. The full list, with a Postman collection, is in the API documentation.

Keep the key private

The API listens only on 127.0.0.1, so other computers on your network can't reach it. But other programs on your machine, including web pages open in your browser, can call it if they know the key. Don't paste the key into external services, bots or public repositories, and press "change key" the moment you suspect a leak.

Frequently asked questions

How do I connect Selenium to a browser profile that is already running?

Open the profile through the API, read the Selenium address from the response (data.ws.selenium), and pass it to Chrome options as debuggerAddress. Selenium then attaches to the running browser instead of launching a new one. You need a chromedriver that matches the profile's browser version.

How do I connect Puppeteer or Playwright?

Use the WebSocket address from the response (data.ws.puppeteer). Puppeteer connects with browserWSEndpoint, and Playwright with connect_over_cdp. Neither needs a separate driver.

Why does my script open a blank page without my login?

Because it created a new browser context. Use the profile's existing context and tab instead: browser.contexts[0] in Playwright, or browser.pages() in Puppeteer. A new context is a clean session without the profile's cookies.

Which version should I read the docs for?

Check the app version with GET /status; the data.version field needs no key. Commands differ between builds, so read the documentation for the version you run. This guide covers 2.9.220, and newer builds add many more commands.

Is the local API safe to leave on?

It listens only on 127.0.0.1, so other computers on your network can't reach it. Other programs on your own machine can still call it if they know the key, so keep the key private and change it if you think it leaked.

Do profiles I create through the API count toward my plan?

Yes. A profile created through the API is a profile of your plan, exactly like one created in the app.

Sources

  1. ADSSPEED API documentation (Quick start, authentication, error codes, FAQ)
  2. Chrome for Developers: ChromeDriver capabilities (debuggerAddress)
  3. Puppeteer: ConnectOptions (browserWSEndpoint, defaultViewport)
  4. Playwright: BrowserType (connectOverCDP)

Related guides

Run Android devices and browser profiles on your own PC

ADSSPEED manages many browser profiles and Android phone profiles from one app, each with its own device configuration, proxy and automation.