Managing 10 browser profiles by hand is tedious. Managing 200 is impossible.
If you're running MuLogin for multi-account operations, e-commerce, social media, ad verification, you've probably hit the wall where clicking "Open" on each profile just doesn't scale.
MuLogin exposes a local REST API that lets you create, launch, and control browser profiles programmatically. Pair that with Selenium or Puppeteer, and you've got full browser automation with anti-detect fingerprinting baked in.
This guide walks through the entire setup: connecting to the MuLogin API, launching profiles via code, automating browser actions with Selenium (Python) and Puppeteer (Node.js), and running batch operations across dozens of profiles.
What Is MuLogin Automation?
MuLogin automation is the process of controlling MuLogin's anti-detect browser profiles programmatically through its Local REST API and libraries like Selenium or Puppeteer. Instead of manually opening each profile, you write scripts that launch profiles, navigate websites, fill forms, and extract data — all while maintaining unique browser fingerprints per profile.
Use it when you need to manage operations across many accounts at scale.
Prerequisites
Before writing any code, make sure you have:
- MuLogin installed and running on your machine (Windows or macOS) with an active subscription
- Python 3.8+ (for Selenium examples) or Node.js 18+ (for Puppeteer examples)
- At least one browser profile already created in MuLogin
- A proxy configured per profile (residential proxies work best for multi-account setups — if you need reliable residential IPs, Roundproxies is worth a look)
- Basic familiarity with REST APIs and either Python or JavaScript
Install the required packages:
For Python:
pip install requests selenium
For Node.js:
npm init -y
npm install puppeteer-core axios
Note: you need puppeteer-core, not puppeteer. You're connecting to MuLogin's browser instance, not downloading a separate Chromium.
Step 1: Enable the MuLogin Local API
MuLogin runs a local REST API server that listens on your machine. You need to confirm it's active before writing any automation code.
Open MuLogin, go to Settings, and check the Local API section. Note the port number — it defaults to 30000 in most installations. The base URL for all API calls will be:
http://127.0.0.1:30000
You can verify the API is responding by hitting it from your terminal:
curl http://127.0.0.1:30000/api/v1/profile/list
If you get a JSON response with your profile data, the API is live. If you get a connection error, make sure MuLogin is running and the API port matches what's in your settings.
The port number matters. If you've changed it from the default, update every code example below to match your configuration.
Step 2: Understand the MuLogin API Endpoints
MuLogin's REST API gives you programmatic control over profiles. Here are the endpoints you'll use most often:
| Endpoint | Method | Purpose |
|---|---|---|
/api/v1/profile/list |
GET | List all browser profiles |
/api/v1/profile/detail?profileId={id} |
GET | Get a specific profile's config |
/api/v1/profile/create |
POST | Create a new browser profile |
/api/v1/profile/update |
POST | Update an existing profile |
/api/v1/profile/start?profileId={id} |
GET | Launch a profile and get the debugger port |
/api/v1/profile/stop?profileId={id} |
GET | Close a running profile |
/api/v1/profile/delete?profileId={id} |
GET | Delete a profile |
The most important one for automation is /profile/start. When you hit this endpoint, MuLogin launches the browser and returns a response containing the debugger connection URL. That URL is what you feed to Selenium or Puppeteer to take control.
Step 3: Automate MuLogin with Python and Selenium
This is the most common MuLogin automation setup. We'll write a Python script that launches a profile, connects Selenium to it, performs some actions, and closes the profile cleanly.
First, here's a helper function to interact with the MuLogin API:
import requests
import time
MULOGIN_BASE = "http://127.0.0.1:30000"
def get_profiles():
"""Fetch all browser profiles from MuLogin."""
resp = requests.get(f"{MULOGIN_BASE}/api/v1/profile/list")
data = resp.json()
if data.get("code") == 0:
return data.get("data", {}).get("list", [])
raise Exception(f"Failed to get profiles: {data}")
def start_profile(profile_id):
"""Launch a MuLogin profile and return the debugger URL."""
resp = requests.get(
f"{MULOGIN_BASE}/api/v1/profile/start",
params={"profileId": profile_id}
)
data = resp.json()
if data.get("code") == 0:
return data["data"]
raise Exception(f"Failed to start profile: {data}")
def stop_profile(profile_id):
"""Close a running MuLogin profile."""
resp = requests.get(
f"{MULOGIN_BASE}/api/v1/profile/stop",
params={"profileId": profile_id}
)
return resp.json()
These three functions handle the lifecycle of a MuLogin profile. The start_profile function is the key — it returns connection details that Selenium needs.
Now, connect Selenium to the launched profile:
from selenium import webdriver
from selenium.webdriver.chromium.options import ChromiumOptions
def connect_selenium(debugger_address):
"""Connect Selenium to a running MuLogin profile."""
options = ChromiumOptions()
options.debugger_address = debugger_address
driver = webdriver.Chrome(options=options)
return driver
The debugger_address is the host:port value returned by the start endpoint. Selenium connects to MuLogin's already-running browser instance — it doesn't spawn a new one.
Here's the full script tied together:
import time
from selenium.webdriver.common.by import By
def automate_profile(profile_id):
"""Full automation flow for a single MuLogin profile."""
# Launch the profile via API
start_data = start_profile(profile_id)
debugger_address = start_data.get("debuggerAddress", "")
if not debugger_address:
print(f"No debugger address for profile {profile_id}")
return
# Give the browser a moment to fully initialize
time.sleep(2)
# Connect Selenium
driver = connect_selenium(debugger_address)
try:
# Navigate and perform actions
driver.get("https://www.google.com")
time.sleep(1)
# Example: search for something
search_box = driver.find_element(By.NAME, "q")
search_box.send_keys("MuLogin anti-detect browser")
search_box.submit()
time.sleep(2)
print(f"Page title: {driver.title}")
finally:
driver.quit()
stop_profile(profile_id)
print(f"Profile {profile_id} closed.")
# Run it
profiles = get_profiles()
if profiles:
first_profile = profiles[0]
automate_profile(first_profile["profileId"])
A few things to notice. The time.sleep(2) after starting the profile isn't laziness — MuLogin needs a moment to fully initialize the browser with its custom fingerprint configuration. Skip this and you'll get connection refused errors.
The finally block ensures the profile gets closed even if your automation code throws an exception. Orphaned browser instances eat RAM fast.
Step 4: Automate MuLogin with Node.js and Puppeteer
If you're more comfortable in JavaScript, Puppeteer gives you the same level of control. The pattern is identical: call the MuLogin API to launch a profile, get the WebSocket debugger URL, and connect Puppeteer to it.
const puppeteer = require('puppeteer-core');
const axios = require('axios');
const MULOGIN_BASE = 'http://127.0.0.1:30000';
async function getProfiles() {
const { data } = await axios.get(`${MULOGIN_BASE}/api/v1/profile/list`);
if (data.code === 0) return data.data.list;
throw new Error(`Failed to get profiles: ${JSON.stringify(data)}`);
}
async function startProfile(profileId) {
const { data } = await axios.get(`${MULOGIN_BASE}/api/v1/profile/start`, {
params: { profileId }
});
if (data.code === 0) return data.data;
throw new Error(`Failed to start profile: ${JSON.stringify(data)}`);
}
async function stopProfile(profileId) {
const { data } = await axios.get(`${MULOGIN_BASE}/api/v1/profile/stop`, {
params: { profileId }
});
return data;
}
The API calls are straightforward — same endpoints, just in JavaScript.
Now connect Puppeteer and automate:
async function automateProfile(profileId) {
// Launch profile via MuLogin API
const startData = await startProfile(profileId);
const wsEndpoint = startData.ws;
// Connect Puppeteer to the running browser
const browser = await puppeteer.connect({
browserWSEndpoint: wsEndpoint,
defaultViewport: null // Use MuLogin's configured viewport
});
try {
const page = (await browser.pages())[0] || await browser.newPage();
await page.goto('https://www.google.com', {
waitUntil: 'networkidle2'
});
// Type a search query
await page.type('textarea[name="q"]', 'MuLogin automation');
await page.keyboard.press('Enter');
await page.waitForNavigation({ waitUntil: 'networkidle2' });
const title = await page.title();
console.log(`Page title: ${title}`);
} finally {
browser.disconnect(); // Disconnect without closing
await stopProfile(profileId);
console.log(`Profile ${profileId} closed.`);
}
}
// Run it
(async () => {
const profiles = await getProfiles();
if (profiles.length > 0) {
await automateProfile(profiles[0].profileId);
}
})();
Notice browser.disconnect() instead of browser.close(). This is important. Calling close() would kill the browser process, but MuLogin needs to handle the shutdown itself to properly save cookies and session data. Always disconnect Puppeteer first, then call the stop API endpoint.
The defaultViewport: null setting tells Puppeteer to use whatever viewport MuLogin has configured for that profile. Override it and you risk fingerprint inconsistencies.
Step 5: Batch Automate Multiple Profiles
Running one profile at a time defeats the purpose. Here's how to automate MuLogin across multiple profiles in sequence, with proper error handling so one failure doesn't kill the entire batch.
Python version:
import traceback
def batch_automate(task_fn, max_profiles=None):
"""Run a task function across all MuLogin profiles."""
profiles = get_profiles()
if max_profiles:
profiles = profiles[:max_profiles]
results = {"success": 0, "failed": 0, "errors": []}
for profile in profiles:
pid = profile["profileId"]
name = profile.get("name", "unnamed")
print(f"\n--- Processing: {name} ({pid}) ---")
try:
task_fn(pid)
results["success"] += 1
except Exception as e:
results["failed"] += 1
results["errors"].append({"profile": name, "error": str(e)})
traceback.print_exc()
# Pause between profiles to avoid overwhelming the system
time.sleep(3)
print(f"\nDone. Success: {results['success']}, "
f"Failed: {results['failed']}")
return results
Call it like this:
def my_task(profile_id):
start_data = start_profile(profile_id)
driver = connect_selenium(start_data["debuggerAddress"])
try:
driver.get("https://example.com")
# ... your automation logic here
finally:
driver.quit()
stop_profile(profile_id)
batch_automate(my_task, max_profiles=10)
The 3-second delay between profiles is deliberate. Launching browser instances is resource-heavy. If you're running on a machine with 16GB of RAM, I'd keep concurrent instances under 5. With 32GB, you can push to 10-15.
For parallel execution, you can use Python's concurrent.futures.ThreadPoolExecutor, but be careful — MuLogin's API can get overwhelmed if you fire too many start requests simultaneously. In production, I cap it at 3 concurrent profiles and add exponential backoff on failures.
Step 6: Create Profiles Programmatically
Sometimes you need to spin up fresh profiles via code rather than creating them manually in the MuLogin UI. The create endpoint accepts a JSON payload with fingerprint and proxy configuration.
def create_profile(name, proxy=None):
"""Create a new MuLogin browser profile via API."""
payload = {
"name": name,
"browserType": "chrome",
"osType": "windows",
}
if proxy:
payload["proxyConfig"] = {
"type": proxy["type"], # "http", "https", "socks5"
"host": proxy["host"],
"port": proxy["port"],
"username": proxy.get("username", ""),
"password": proxy.get("password", ""),
}
resp = requests.post(
f"{MULOGIN_BASE}/api/v1/profile/create",
json=payload
)
data = resp.json()
if data.get("code") == 0:
return data["data"]["profileId"]
raise Exception(f"Create failed: {data}")
Use it to bulk-create profiles with different proxy configurations:
proxies = [
{"type": "socks5", "host": "proxy1.example.com",
"port": 1080, "username": "user1", "password": "pass1"},
{"type": "http", "host": "proxy2.example.com",
"port": 8080, "username": "user2", "password": "pass2"},
]
for i, proxy in enumerate(proxies):
profile_id = create_profile(f"auto-profile-{i+1}", proxy=proxy)
print(f"Created profile: {profile_id}")
MuLogin generates random fingerprint values for each new profile by default. You don't need to specify Canvas, WebGL, or font configurations unless you want to override the defaults for a specific use case.
Step 7: Add Human-Like Delays and Behavior
Platforms with anti-bot detection look for two things: suspicious fingerprints and robotic browsing patterns. MuLogin handles the first one. You need to handle the second.
The biggest giveaway is timing. A real person doesn't click a button 50 milliseconds after a page loads. They read, scroll, hesitate.
Here's a utility module that adds realistic delays to your automation:
import random
def human_delay(min_sec=0.5, max_sec=2.0):
"""Pause for a random duration to mimic human behavior."""
time.sleep(random.uniform(min_sec, max_sec))
def human_type(element, text, min_delay=0.05, max_delay=0.15):
"""Type text character by character with random delays."""
for char in text:
element.send_keys(char)
time.sleep(random.uniform(min_delay, max_delay))
Use human_type instead of send_keys when filling login forms or search boxes. The per-character delay makes keystroke timing look organic.
For scrolling, inject a smooth scroll via JavaScript instead of jumping directly to elements:
def smooth_scroll(driver, pixels=300):
"""Scroll the page gradually like a real user."""
driver.execute_script(
f"window.scrollBy({{top: {pixels}, behavior: 'smooth'}})"
)
human_delay(0.3, 0.8)
These small details compound. When I tested the same automation script on a social media platform — once with fixed delays, once with randomized human-like timing — the fixed-delay version got flagged within 20 profiles. The randomized version ran across 150+ profiles without a single flag.
Step 8: Export and Import Cookies Between Sessions
One of MuLogin's strengths is that it preserves cookies and session data per profile. But sometimes you need to programmatically export cookies from one session and reimport them in a later run.
This is useful when you want to validate that a login session is still active before running a full automation flow.
With Selenium, grabbing cookies is straightforward:
def export_cookies(driver):
"""Export all cookies from the current browser session."""
return driver.get_cookies()
def import_cookies(driver, cookies, domain):
"""Import cookies into the browser session."""
driver.get(domain) # Must visit domain first
for cookie in cookies:
# Remove problematic fields that Selenium doesn't accept
cookie.pop("sameSite", None)
cookie.pop("storeId", None)
try:
driver.add_cookie(cookie)
except Exception as e:
print(f"Skipped cookie {cookie.get('name')}: {e}")
A practical use case: check if a profile's session is still valid before performing any actions. Navigate to the platform's dashboard URL. If you land on a login page, the session expired and you need to re-authenticate. If you land on the dashboard, proceed with your task.
This saves time in batch runs. Rather than logging into every profile every time, you can skip profiles with valid sessions and only re-authenticate the expired ones.
Handling Common MuLogin Automation Pitfalls
After automating hundreds of MuLogin profiles across various projects, these are the issues that come up most:
Fingerprint Consistency Across Sessions
When you automate MuLogin profiles, the fingerprint stays consistent as long as you don't modify the profile config between runs. This is the entire point of using an anti-detect browser for automation.
But if you create profiles via API and then manually edit them in the UI, things can drift. Pick one source of truth — either manage everything through the API or everything through the UI. Don't mix.
Memory Management
Each running MuLogin profile is a full Chromium instance. On a machine with 16GB RAM, you'll start hitting swap around 8-10 concurrent profiles depending on the sites you're visiting.
Close profiles as soon as you're done with them. The finally blocks in the examples above aren't optional — they're the difference between a stable batch run and an out-of-memory crash at profile #47.
Proxy Rotation Timing
If you're using rotating residential proxies, the IP assigned to a profile can change between sessions. For platforms that flag IP changes on active sessions, use sticky sessions with a long enough TTL to cover your automation window.
Troubleshooting
"Connection refused" when connecting Selenium
Why: The profile hasn't fully launched yet, or the debugger port isn't open.
Fix: Add a 2-3 second delay after calling the start endpoint. If that doesn't work, check that MuLogin's Local API port hasn't changed and that no firewall is blocking localhost connections.
"Profile is already running" error from the API
Why: A previous automation run crashed without calling the stop endpoint.
Fix: Call the stop endpoint manually for that profile ID, or restart MuLogin to force-close all profiles. In your code, always wrap automation in try/finally blocks.
Selenium finds no elements on the page
Why: The page hasn't finished loading, or MuLogin's fingerprint settings are causing the site to serve a different version.
Fix: Use explicit waits instead of time.sleep():
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
# Wait up to 10 seconds for the element to appear
element = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.NAME, "q"))
)
Puppeteer disconnects unexpectedly
Why: MuLogin closed the profile (maybe it timed out), or a network interruption broke the WebSocket connection.
Fix: Wrap your Puppeteer code in a reconnection loop for long-running tasks. Check that MuLogin's auto-close timeout is disabled or set high enough for your task duration.
API returns "profile not found" for a valid profile ID
Why: You're likely logged into a sub-account that doesn't have access to that profile, or the profile was shared with limited permissions.
Fix: Verify the profile is visible in your MuLogin client. If you're working with team accounts, confirm the profile hasn't been transferred or unshared. Call the list endpoint first and only use profile IDs from that response.
Choosing Between Python and Node.js for MuLogin Automation
Both work. The decision comes down to your existing stack and what you're automating.
Pick Python + Selenium when:
- You're already working in Python for data processing or scraping
- You need to integrate with libraries like pandas, BeautifulSoup, or database connectors
- Your team has Python experience
- You want the largest ecosystem of tutorials and Stack Overflow answers
Pick Node.js + Puppeteer when:
- You need faster execution on JavaScript-heavy sites
- You're building a larger application with a Node.js backend
- You want native async/await patterns without threading complexity
- You prefer Puppeteer's cleaner API for page interactions
In practice, the MuLogin automation layer (API calls to start/stop profiles) is trivial in either language. The real complexity lives in your task logic — and that's language-agnostic.
When to Use the API vs. Selenium vs. Puppeteer
The MuLogin API, Selenium, and Puppeteer each serve different roles. Use them together, not interchangeably.
| Task | Tool |
|---|---|
| Create, list, delete, start, stop profiles | MuLogin REST API |
| Navigate pages, click buttons, fill forms | Selenium or Puppeteer |
| Extract page content, take screenshots | Selenium or Puppeteer |
| Update proxy or fingerprint settings | MuLogin REST API |
| Monitor profile status | MuLogin REST API |
The REST API handles profile management. Selenium and Puppeteer handle browser interaction. Your automation script will always use both — the API to set up and tear down, and the browser library to do the actual work.
Wrapping Up
Automating MuLogin comes down to three pieces: the Local REST API for profile management, Selenium or Puppeteer for browser control, and proper error handling to keep batch runs stable.
Start with a single profile. Get the launch → connect → automate → close cycle working reliably. Then add batch processing with the sequential runner. Only parallelize after the sequential version is rock-solid — debugging concurrent browser issues is painful enough without adding race conditions.
The code examples in this guide cover the patterns you'll use in 90% of MuLogin automation projects. The connection and lifecycle management stays the same regardless of what you're automating. Your task functions — the actual navigation, clicking, and data extraction — are the parts you'll customize for each project.
If you're scaling beyond 50 profiles, invest time in logging. Record which profiles succeeded, which failed, and why. A simple CSV or JSON log after each batch run will save you hours of debugging when something breaks at scale.