> ## Content Index
> Fetch the complete content index at: https://roundproxies.com/blog/llms.txt
> Use this file to discover other available public pages before exploring further.

# How to Automate MuLogin with Selenium and Puppeteer
- URL: https://roundproxies.com/blog/automate-mulogin/
- Published: 2026-04-14T12:26:13.000Z
- Updated: 2026-08-30T18:20:42.000Z
- Author: Marius Bernard
- Tags: #dated-69dca6c1f1b8cae22148ca23

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](https://www.mulogin.com/) 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](https://www.roundproxies.com/) is worth a look)
- Basic familiarity with REST APIs and either Python or JavaScript

Install the required packages:

For Python:

```bash
pip install requests selenium

```

For Node.js:

```bash
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:

```bash
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:

```python
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:

```python
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:

```python
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.

```javascript
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:

```javascript
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:

```python
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:

```python
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.

```python
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:

```python
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:

```python
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:

```python
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:

```python
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()`:

```python
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.