What the ixBrowser API Is and Where It Runs
The ixBrowser API is a local HTTP service that the ixBrowser desktop client exposes on 127.0.0.1:53200 under /api/v2/. The official Python SDK, ixbrowser-local-api, wraps it: you build an IXBrowserClient, list profiles with get_profile_list(), open one with open_profile(), and attach Selenium to the debugging address it returns. This tutorial takes you from a clean virtual environment to a script that finds a profile by name, opens it, drives a page and closes cleanly.
📌 TL;DR Executive Summary
- Core Takeaway: The SDK is a thin Python wrapper over a localhost service. Enable Local API in the desktop client, install the package, and every call returns data or None with client.code and client.message explaining the failure.
- Key Risk/Challenge: One client object holds one HTTP session and cannot be shared across threads, and profile-creation and open quotas are metered per day.
- Recommended Solution: Wrap every call, run one client per worker, give open_profile a longer read timeout than list calls, and run the repo’s simple_test.py before you write anything custom.
Prerequisites Before You Write a Line of Code
Most first attempts fail before any Python runs. Work through this list in order, because each item removes a whole class of error later.
- ixBrowser desktop client installed and running. The Local API is served by the client process, not by the cloud, so it only answers while the app is open on that machine. The SDK sources document the client for Windows; other platforms are not covered.
- Local API service switched on inside the client. There is a setting for it in the app. With it off, every request is refused on the port.
- Python 3.8 or newer. The SDK’s CI matrix runs from 3.8 through 3.14, so anything modern works, but 3.7 and below do not.
- The SDK installed:
pip install ixbrowser-local-api, or clone the repository and runpip install .. - Selenium installed if you want the attach steps:
pip install selenium. The repo ships its examples in the same style. - A profile you own or are authorised to manage. An API does not exempt you from a platform’s rules about multiple accounts; keep the profile list limited to accounts your team legitimately operates.
Step 1: Install the SDK and Construct the Client
Install the package, then build the client explicitly instead of relying on defaults. The defaults are correct for a standard install, but writing the host, port and timeout out makes the script readable six months later and gives you one place to change the port when the client picks a different one.
import sys
from ixbrowser_local_api import IXBrowserClient
if sys.version_info < (3, 8):
raise SystemExit("ixbrowser-local-api needs Python 3.8 or newer")
client = IXBrowserClient(
target="127.0.0.1", # the Local API service runs on localhost
port=53200, # default Local API V2.0 port
timeout=(2, 20), # (connect, read) in seconds
)
The timeout tuple matters more than it looks. Two seconds to connect is generous for a loopback socket, so a connect timeout means the service is not listening at all. The read timeout is the one you will raise first, because opening a profile takes far longer than listing profiles. If you point the client at a different host or port, pass them here rather than editing the package source.
Step 2: Prove Connectivity With a Single Read Call
Start with the cheapest call in the API: listing profiles. It tells you three things at once — the service is up, the request path is right, and your code reaches the client. Every method returns None on failure and stores the reason on the client instance, so the check is always the same shape.
data = client.get_profile_list()
if data is None:
print("Request failed ->", client.code, client.message)
else:
print("Profile records:", len(data) if isinstance(data, list) else data)
If you get a connection error instead of None, the client process is not running, the Local API service is not enabled, or the service is listening on a different port. Fix that in the app first; no amount of Python will help.
Step 3: Turn Every None Into an Exception
The failure convention is the single most common source of confusing bugs in this API. A method that fails does not raise — it returns None and sets client.code and client.message. Those two attributes describe only the latest call, so if you keep calling after a failure, the original reason is gone. Wrap the client once and that problem disappears.
def call(client, method, *args, **kwargs):
"""Call a client method and raise instead of returning None."""
result = getattr(client, method)(*args, **kwargs)
if result is None:
raise RuntimeError(
f"{method} failed: code={client.code} message={client.message}"
)
return result
Every snippet below uses this helper. It also gives you one place to add logging, retries or a Slack alert later, which is cheaper than scattering if result is None through a dozen functions.
Step 4: List Profiles and Pick the Right profile_id
Everything downstream needs a profile_id, so the reliable pattern is list, inspect the shape, then match. Do not hardcode an id you copied out of the app — it is tied to one installation and will break the moment you run the script on another machine.
profiles = call(client, "get_profile_list")
if not profiles:
raise SystemExit("No profiles returned; create one in the client first")
print("Total:", len(profiles))
print("Keys on the first record:", sorted(profiles[0].keys()))
target_name = "QA storefront EU"
match = next((p for p in profiles if p.get("profile_name") == target_name), None)
if match is None:
raise SystemExit(f"No profile named {target_name!r}")
profile_id = match["profile_id"]
print("Using profile:", profile_id)
Print the keys once before you filter on a field name. The SDK repository was published in 2023 and field names can shift between client builds, so read the record you actually get rather than trusting a blog post — including this one. Target the profiles that already hold the logins and cookies your team uses by hand; the general pattern for that is covered in this walkthrough of profile management API patterns.
Step 5: Open a Profile and Attach Selenium
open_profile() is the call that does real work. It launches the selected profile and returns a result containing a webdriver value and a debugging_address. Selenium attaches to that address instead of starting its own browser, which is the whole point: the session keeps the profile’s saved cookies, storage and site sessions.
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
opened = call(
client,
"open_profile",
profile_id,
cookies_backup=False,
load_profile_info_page=False,
)
options = Options()
options.debugger_address = opened["debugging_address"]
service = (
Service(executable_path=opened["webdriver"])
if opened.get("webdriver")
else Service() # let Selenium Manager resolve a matching driver
)
driver = webdriver.Chrome(service=service, options=options)
try:
driver.get("https://example.com/")
print("Title:", driver.title)
print("URL:", driver.current_url)
finally:
driver.quit()
The two options shown are the ones you will change most. cookies_backup=False skips a cookie backup step when you do not need one, which shortens launch time on a batch. load_profile_info_page=False stops the client opening its own info tab, so your first driver.get() is the only page you have to reason about. If the result carries an empty webdriver value, let Selenium resolve the driver — forcing a system chromedriver that does not match the Chromium build inside the client is the usual cause of “session not created”. The attach mechanics are similar across vendors, and the session handling in this GoLogin API and Selenium guide is a useful comparison if you maintain more than one vendor’s scripts.
Step 6: Open Several Profiles Without Sharing One Client
Batch runs are where the single-session design bites, because the client reuses one HTTP session and its code and message fields belong to whichever call finished last. The fix is simple: each worker builds its own IXBrowserClient, opens its own profile, and reports back. Keep the pool small — every opened profile is a full Chromium instance competing for RAM and CPU on one machine.
from concurrent.futures import ThreadPoolExecutor
from ixbrowser_local_api import IXBrowserClient
HOST, PORT = "127.0.0.1", 53200
def open_quietly(profile_id):
client = IXBrowserClient(target=HOST, port=PORT, timeout=(2, 60))
result = client.open_profile(
profile_id,
cookies_backup=False,
load_profile_info_page=False,
)
if result is None:
return profile_id, f"failed: {client.code} {client.message}"
return profile_id, result["debugging_address"]
if __name__ == "__main__":
listing = IXBrowserClient(target=HOST, port=PORT, timeout=(2, 20))
data = listing.get_profile_list()
if data is None:
raise SystemExit(f"get_profile_list failed: {listing.code} {listing.message}")
ids = [p["profile_id"] for p in data][:3]
with ThreadPoolExecutor(max_workers=3) as pool:
for profile_id, status in pool.map(open_quietly, ids):
print(profile_id, status)
As a starting point, two to four workers on a 16 GB machine is reasonable. Raise it only after you watch memory during a run, because the failure mode of over-parallelising is not a clean error — it is a machine that swaps and a batch that stalls halfway.
Where the localhost design changes your deployment
Because the service lives on loopback, the machine running the script must be the machine running the desktop client. A scheduled task on the Windows box that hosts the client works; a Linux container in your CI pipeline cannot reach 127.0.0.1:53200 unless the client itself runs there. Plan for that early: put the scheduler on the same host, keep the client running as a service, and make the script fail loudly when the port stops answering rather than retrying silently for an hour.
Common ixBrowser API Errors and How to Fix Them
- Connection refused on 127.0.0.1:53200. The desktop client is closed, the Local API service is off, or the port changed. Start the app, enable the service, and pass the correct
port=to the constructor. - Methods return None with no traceback. That is the documented failure path, not a bug. Read
client.codeandclient.messageimmediately, before the next call overwrites them. - The error message does not match the call you just made. The client is being shared across threads or tasks. Give each worker its own instance.
- Selenium cannot create a session. Driver and browser mismatch, or an empty
webdrivervalue passed intoService(executable_path=...). Fall back to a plainService()and let Selenium Manager pick. - open_profile times out while list calls are instant. Raise the read timeout to 60 and retry once; the first launch of an untouched profile is slower than the tenth.
- A batch fails after a fixed number of runs. Check the daily creation and open quotas for your tier before assuming a code fault — see the table below.
If a call succeeds but returns fields you did not expect, compare the example scripts against your installed client build. The example set covers profile create, update, copy, reset of the open state and cloud-data clearing, plus profile transfer codes and tag and proxy-tag management, so it is usually faster to copy a working payload than to reconstruct one.
The Full Script, End to End
Everything above, in one file. It finds a profile by name, opens it, loads a page, reads the title, and quits — no leftover windows, no silent failures.
"""ixBrowser Local API V2.0 smoke test: find, open, attach Selenium, close."""
from ixbrowser_local_api import IXBrowserClient
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
HOST, PORT = "127.0.0.1", 53200
TARGET_NAME = "QA storefront EU"
def call(client, method, *args, **kwargs):
result = getattr(client, method)(*args, **kwargs)
if result is None:
raise RuntimeError(f"{method} failed: code={client.code} message={client.message}")
return result
def find_profile_id(client, name):
for record in call(client, "get_profile_list"):
if record.get("profile_name") == name:
return record["profile_id"]
raise SystemExit(f"Profile {name!r} not found")
def attach(client, profile_id):
result = call(
client, "open_profile", profile_id,
cookies_backup=False,
load_profile_info_page=False,
)
options = Options()
options.debugger_address = result["debugging_address"]
service = (
Service(executable_path=result["webdriver"])
if result.get("webdriver")
else Service()
)
return webdriver.Chrome(service=service, options=options)
def main():
client = IXBrowserClient(target=HOST, port=PORT, timeout=(2, 60))
profile_id = find_profile_id(client, TARGET_NAME)
print("Opening profile:", profile_id)
driver = attach(client, profile_id)
try:
driver.get("https://example.com/")
print("Title:", driver.title)
print("URL:", driver.current_url)
finally:
driver.quit()
if __name__ == "__main__":
main()
Run it once by hand before you put it in a scheduler. If the profile stays marked as open after a crash — a hard kill, a closed terminal — the repo ships a separate example that resets the open state; run that rather than deleting and recreating the profile, which throws away its stored logins.
What Daily Quotas Mean for a Script
Profiles are unlimited on every ixBrowser tier, but creation and open calls are metered per day. That turns a loop into a budget: a script that opens 200 profiles in a smoke test will exhaust a small plan before lunch, and the failure can look like an API error rather than a limit.
| Plan | Price | Team seats | Profile creations / day | Profile opens / day |
|---|---|---|---|---|
| Free | $0/mo | 2 | 10 | 100 |
| Professional | $3.99/mo | 20 | — | — |
| Business | $9.99/mo | 50 | — | — |
| Enterprise | $19.99/mo | 150 | 1,500 | 15,000 |
| Basic Developer | $9.99/yr | 10 | — | — |
The vendor pricing page states the Free and Enterprise caps explicitly; the middle tiers sit between those, so read your own portal before you size a nightly run. API access, profile transfer, batch import and expanded cloud storage are listed as paid-tier features, which is the practical reason most scripting starts above Free. For a straight read on the entry tier, this breakdown of what the free plan really includes covers the limits worth knowing.
When You Do Not Need an API at All
Scripting pays off when the same sequence runs on a schedule: QA checks across regions, opening the same profile on a second machine, attaching a reporting step to a launch. It pays off much less when a person simply needs to open a client’s profile, sign in as that client and get on with the work — at which point you are maintaining Python for something a launcher already does.
That second case is what Send.win is built for. It is a browser profile manager with a desktop app for Windows, macOS and Linux, plus a cloud browser that runs profiles on an EU server in Helsinki from any device. Sharing a profile with a paid teammate opens it already signed in, so no password changes hands, and residential proxies are included on the paid plans with timezone, locale and geolocation following the exit IP. Be clear on the trade-off, though: Send.win is a hands-on tool for people working in the browser, not a scripting platform, so the Python in this tutorial has no Send.win equivalent. If you are weighing the two, this comparison of ixBrowser alternatives puts the options side by side.
🏆 Send.win Verdict
A scripted setup pays off only when your team genuinely repeats the same launch sequence on a schedule. If your reality is five people opening a dozen client profiles by hand each morning, the code above adds a maintenance job nobody asked for. Send.win covers that case with no Python at all: isolated profiles with their own cookies, storage, fingerprint settings and proxy, teammate sharing that opens the profile already signed in, and a cloud browser for the days you are not at your desk.
See Send.win plans — Pro from $19/mo or $83.88/yr, Team from $49/mo, a 7-day money-back guarantee on your first card purchase, and a free cloud demo that gives you 10 minutes a day in the cloud browser.
Frequently Asked Questions
How do I install the ixBrowser Python API?
Run pip install ixbrowser-local-api on Python 3.8 or newer, or clone the official repository and run pip install . from its root. Then import the client with from ixbrowser_local_api import IXBrowserClient. If you also want the Selenium examples, add pip install selenium.
How Send.win Helps With Ixbrowser 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 port does the ixBrowser Local API use?
The default endpoint is http://127.0.0.1:53200/api/v2/, which is what the SDK targets when you construct a client with no arguments. If your client build listens elsewhere, pass target= and port= to the constructor instead of editing the package. The service only answers while the desktop client is running with Local API enabled.
Why does IXBrowserClient return None instead of data?
Returning None is how the SDK reports a failed call — it does not raise an exception. The reason is stored on the client instance in code and message, and those values only describe the most recent call. Wrap each call so it raises immediately, otherwise a later successful call overwrites the details of the failure you are chasing.
How do I fix connection refused on 127.0.0.1:53200?
Three causes cover almost every case: the ixBrowser desktop client is not running, the Local API service is switched off inside the app, or the service is bound to a different port than the one in your script. Start the client, enable the service, and confirm the port before changing any code.
How do I connect Selenium to an ixBrowser profile?
Call open_profile() and read the returned debugging_address and webdriver values. Set Options().debugger_address to the address, build a Service from the driver path, and start a normal webdriver.Chrome. If the driver path is empty, let Selenium Manager resolve a matching driver rather than borrowing a system chromedriver.
Can I create profiles with a proxy through the API?
Yes — the SDK’s example set covers profile create, update and copy, plus proxy and tag management, so proxy settings are part of the payload you send. Read the example script for your installed client build and copy its field names, because the examples were published in 2023 and names can drift. Only attach proxies you are entitled to use and accounts you own or manage.
Which ixBrowser plan do I need to use the API?
The pricing page lists API access among the paid-tier features, while profiles themselves are unlimited on every tier. What really constrains a script is the daily quota: Free allows 10 profile creations and 100 opens per day, Enterprise allows 1,500 and 15,000. Check your own portal for the middle tiers before sizing a batch.
Can I run many ixBrowser profiles in parallel from one script?
Yes, but not with one client object. The client reuses a single HTTP session and its code and message fields belong to the last call, so each worker needs its own IXBrowserClient. Keep the worker count low enough for your RAM and CPU, and remember every open counts against the daily quota.