DLogin
Developer reference

XDLogin documentation

Set up your first profiles, then drive hundreds of them from the local REST API, the Chrome DevTools Protocol, or an AI agent over MCP. Everything here works on the free plan.

Getting started

From install to a running profile

XDLogin is local-first: profiles are folders on your disk, each launched as its own real Chrome process with its own fingerprint, cookies and proxy. The account only carries your plan.

  1. Install and sign in

    Grab the build for your platform from the download page and create a free account inside the app. On Windows, Linux and macOS, profiles launch in your installed Google Chrome, Edge, Brave or Chromium; XDLogin finds it automatically.

  2. Create a profile

    Click New profile, name it, and choose the device it should look like: Windows, macOS, Linux, or an Android phone such as a Pixel 7. Every fingerprint signal, from navigator.platform and client hints to the GPU string, screen and touch points, is derived from that one device model, so nothing contradicts anything. Templates save a setup you want to repeat.

  3. Attach a proxy

    Paste an HTTP, HTTPS, SOCKS4 or SOCKS5 proxy with its credentials. The proxy test shows the exit IP, country and latency, and WebRTC only ever exposes the proxied address. Set the profile's timezone and language to match that country yourself — the profile health check tells you when they disagree. Proxies are unmetered: bring any provider.

  4. Launch and verify

    Click Launch. The profile opens as a separate browser and the fingerprint is installed before the first page loads, in every tab and popup. Use the built-in Fingerprint test to see exactly what sites see.

  5. Organise

    Group profiles in folders, tag them, and search or filter the list. The activity log records launches, edits and deletions, and exports as CSV.

  6. Cookies and backups

    The cookie editor imports and exports cookies per profile. Backup and restore writes your profiles to a JSON file you control. Both work on every plan.

  7. Sync across machines (Pro and above)

    Cloud sync seals each profile with AES-256-GCM on your device before upload, using a passphrase that never leaves your machines; the server only stores ciphertext. Sync moves only what changed and resolves conflicts deterministically. It is off until you turn it on.

Local REST API

Endpoints on port 31337

Enable the API in Settings → API. It binds to 127.0.0.1:31337 (the port is configurable), answers { "success": true, "data": … } on every endpoint except health, and is available on every plan including Free. Profile ids are UUIDs: list them, or read data.id from a create call.

If you set an API key in Settings → API, every request needs it — /api/health included — as the header Authorization: Bearer <key>. Anything else, or nothing, gets 401 Unauthorized. With no key set the API is open to any program on your machine, so set one.

GET/api/health

Liveness check: answers { "status": "ok", "version": … }. Use it to confirm the API is enabled before scripting against it.

GET/api/profiles

Every profile with its id, name, device model, proxy summary, tags, folder and running state.

POST/api/profiles

Create a profile. Accepts the same fields as the profile editor: name, device model, proxy, tags, folder and notes. The new profile comes back with its id in data.id.

GET/api/profiles/{id} PATCH DELETE

Read, update or delete one profile. PATCH takes any subset of the create fields. DELETE removes the profile and its on-disk data.

POST/api/profiles/{id}/launch

Start the profile's browser with its fingerprint and proxy. Returns as soon as the launch is accepted.

GET/api/profiles/{id}/connect

The automation attach point for a running profile. Returns 409 until the browser has finished booting, so poll it after launch.

// 200 OK
{
  "success": true,
  "data": {
    "wsEndpoint": "ws://127.0.0.1:49152/devtools/browser/9a4f2c9e",
    "httpEndpoint": "http://127.0.0.1:49152",
    "debuggerAddress": "127.0.0.1:49152",
    "port": 49152,
    "browser": "Chrome/139.0.0.0"
  }
}
POST/api/profiles/{id}/stop

Close the browser and flush cookies and storage to disk.

POST/api/profiles/{id}/clone

Duplicate a profile's settings into a new profile with a fresh id and an empty session.

GET/api/license GET/api/stats

Your current plan and limits, and a summary of how many profiles exist and are running.

Automation

Puppeteer, Playwright, Python and cURL

Launch a profile through the API, wait for its DevTools endpoint, then attach whichever framework you already use. Selenium works the same way: pass data.debuggerAddress as Chrome's debuggerAddress option.

import puppeteer from 'puppeteer-core';

const BASE = 'http://127.0.0.1:31337';   // enable in Settings → API
const id = 'YOUR-PROFILE-ID';              // from GET /api/profiles
// Drop `headers` if you have not set an API key in Settings → API.
const headers = { Authorization: 'Bearer YOUR-API-KEY' };

async function connectEndpoint() {
  // /connect answers 409 until the browser has booted
  for (;;) {
    const r = await fetch(`${BASE}/api/profiles/${id}/connect`, { headers });
    if (r.status === 200) return (await r.json()).data;
    await new Promise(res => setTimeout(res, 500));
  }
}

await fetch(`${BASE}/api/profiles/${id}/launch`, { method: 'POST', headers });
const data = await connectEndpoint();
const browser = await puppeteer.connect({ browserWSEndpoint: data.wsEndpoint });
const page = await browser.newPage();
await page.goto('https://whoer.net');
console.log(await page.title());

await browser.disconnect();   // leave the profile running, or:
await fetch(`${BASE}/api/profiles/${id}/stop`, { method: 'POST', headers });
import { chromium } from 'playwright';

const BASE = 'http://127.0.0.1:31337';
const id = 'YOUR-PROFILE-ID';
const headers = { Authorization: 'Bearer YOUR-API-KEY' };   // omit if no key is set

await fetch(`${BASE}/api/profiles/${id}/launch`, { method: 'POST', headers });

let data;
while (!data) {                                    // 409 until the browser is ready
  const r = await fetch(`${BASE}/api/profiles/${id}/connect`, { headers });
  if (r.status === 200) data = (await r.json()).data;
  else await new Promise(res => setTimeout(res, 500));
}

const browser = await chromium.connectOverCDP(data.wsEndpoint);
const context = browser.contexts()[0];        // the profile's own context
const page = context.pages()[0] ?? await context.newPage();
await page.goto('https://whoer.net');
console.log(await page.title());
import time, requests
from playwright.sync_api import sync_playwright

BASE = "http://127.0.0.1:31337"
PROFILE = "YOUR-PROFILE-ID"
HEADERS = {"Authorization": "Bearer YOUR-API-KEY"}   # omit if no key is set

requests.post(f"{BASE}/api/profiles/{PROFILE}/launch", headers=HEADERS)

while True:                                         # 409 until the browser is ready
    r = requests.get(f"{BASE}/api/profiles/{PROFILE}/connect", headers=HEADERS)
    if r.status_code == 200:
        data = r.json()["data"]
        break
    time.sleep(0.5)

with sync_playwright() as p:
    browser = p.chromium.connect_over_cdp(data["wsEndpoint"])
    page = browser.contexts[0].pages[0]
    page.goto("https://whoer.net")
    print(page.title())

# Selenium instead? chrome_options.add_experimental_option(
#     "debuggerAddress", data["debuggerAddress"])
# drop the -H lines if you have not set an API key
AUTH="Authorization: Bearer YOUR-API-KEY"

# is the API on?
curl -H "$AUTH" http://127.0.0.1:31337/api/health

# list profiles and pick an id
curl -H "$AUTH" http://127.0.0.1:31337/api/profiles

# launch it
curl -H "$AUTH" -X POST http://127.0.0.1:31337/api/profiles/YOUR-PROFILE-ID/launch

# poll for the DevTools endpoint (409 until ready)
curl -H "$AUTH" http://127.0.0.1:31337/api/profiles/YOUR-PROFILE-ID/connect

# stop it and flush cookies to disk
curl -H "$AUTH" -X POST http://127.0.0.1:31337/api/profiles/YOUR-PROFILE-ID/stop
AI agents

MCP server for agents

The desktop app includes a Model Context Protocol server, off by default. Turn it on in Settings → MCP: it listens on http://127.0.0.1:8931 and speaks MCP's JSON-RPC 2.0 over HTTP POST. Configure your client with the streamable HTTP transport and that URL — the older HTTP+SSE transport is not served, so a client set to SSE gets a 405. If you set an API key, send it as Authorization: Bearer <key>. Point any MCP-capable agent at it and it can run profiles the way you would.

POSThttp://127.0.0.1:8931

Tools exposed to the agent, available on every plan:

  • profile_list
  • profile_get
  • profile_create
  • profile_clone
  • profile_launch
  • profile_stop
  • profile_delete
  • browser_navigate
  • browser_screenshot
  • fingerprint_get
  • proxy_test
  • cookies_export
  • cookies_import
  • health
  • stats

Profiles are also exposed as resources, so an agent can read a profile's configuration by id before acting on it.