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.

Applies to Windows 3.5.8 Linux 3.5.6 macOS beta 3.5.5

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. Profiles launch in your installed Google Chrome, which XDLogin finds automatically. Without Chrome it falls back to Edge or Brave on Windows and macOS, or Chromium on Linux — but on Edge or Brave the profile still announces itself as Google Chrome, so install Chrome if you can.

  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 value XDLogin sets, from navigator.platform and client hints to the GPU string, screen and touch points, comes from that one device model, so those values agree. A few signals still come from your own machine, such as the TLS handshake, WebGL limits and font measurements; the test results page lists them all. Templates save a setup you want to repeat.

  3. Attach a proxy

    Enter an HTTP, HTTPS, SOCKS4 or SOCKS5 proxy. A username and password work with HTTP and HTTPS proxies; Chrome can’t send a password to a SOCKS proxy, so use one that allows your IP instead. The proxy test shows the exit IP, country and latency. Unless you switch WebRTC to Real, WebRTC can’t reveal your own IP. Set the profile's timezone and language to match that country yourself — the profile health check tells you when they disagree. From 3.5.8 the timezone can instead be Match proxy, looked up through the proxy at each launch; if that lookup fails, the profile doesn’t launch. Also from 3.5.8, a profile whose proxy is switched on but has no host or no valid port doesn’t launch, rather than browsing from your own IP, and the proxy test names the protocol the proxy speaks and why it failed. Proxies are unmetered: bring any provider.

  4. Launch and verify

    Click Launch. The profile opens as a separate browser, with the fingerprint installed before the first page loads, in every tab and popup. On Linux (3.5.6) and the macOS beta (3.5.5), a tab opened with a middle-click or Ctrl+click, and tabs Chrome restores from the last session, can load their first page without it; on the macOS beta, so can the first page of a launch, occasionally. See its known issues. Test fingerprint opens Pixelscan, BrowserLeaks, CreepJS and other checkers inside the profile, so you see what they 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. On Windows (3.5.8) that includes profiles that run in Chrome; on Linux (3.5.6) and the macOS beta (3.5.5), only profiles on the built-in engine. Backup and restore writes your profiles to a JSON file you control. Both are 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 & Auto; from then on it starts with XDLogin until you stop it there. 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. Errors answer { "success": false, "error": … } with a 4xx status; errors lists each one. Profile ids are UUIDs: list them, or read data.id from a create call.

If you set an API key in Settings → API & Auto, 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.

From 3.5.8 the API and the MCP server refuse web pages, key or not: a request whose Origin or Host names anything but 127.0.0.1, localhost or [::1] gets 403 before anything else runs. Scripts, cURL, Puppeteer, Playwright, Selenium and MCP clients send no Origin, so they’re unaffected. The Linux build (3.5.6) and the macOS beta (3.5.5) don’t check: with no key set, a website open in your browser could send them commands.

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 status: idle, running or error, and from 3.5.8 also starting or stopping while a launch or a Stop is under way.

POST/api/profiles#

Create a profile. Accepts the same fields as the profile editor: name, device model, timezone, proxy, tags, folder and notes (profile fields). Answers with the saved profile, so the new id is data.id. An unknown device id, or a timezone that isn’t an IANA name such as Europe/Berlin, is a 400; past your plan’s profile limit the answer is a 403 that says so. From 3.5.8, "fingerprint": { "timezoneMode": "proxy" } matches the timezone to the proxy’s exit IP at each launch (Match proxy in the app), and a switched-on proxy with no host or no valid port is a 400. The Linux build (3.5.6) and the macOS beta (3.5.5) check none of the fields. Linux answers before saving, with an empty data, and past your limit saves nothing; the macOS beta answers with the saved profile, and past your limit with a 500.

GETPATCHDELETE/api/profiles/{id}#

Read, update or delete one profile. PATCH takes any subset of the create fields. DELETE removes the profile and its on-disk data; on the macOS beta (3.5.5) it removes only the record, and the profile’s browser data stays on disk.

POST/api/profiles/{id}/launch#

Start the profile's browser with its fingerprint and proxy. Answers once the browser is up and the fingerprint is installed, or with an error saying why it could not be.

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. browser is the version of the Chrome actually running the profile.

// 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/152.0.7977.76"
  }
}

POST/api/profiles/{id}/stop#

Close the browser. Chrome is asked to close normally, which writes the cookies of a login made a moment ago to disk, and is ended only if it hasn’t closed within 10 seconds. The call answers once the browser has gone. The Linux build (3.5.6) and the macOS beta (3.5.5) end Chrome at once, losing cookies set in the last half minute or so.

POST/api/profiles/{id}/clone#

Duplicate a profile's settings into a new profile with a fresh id and an empty session. Answers with the copy; an unknown id is a 404, and past your plan’s profile limit it’s a 403.

GET/api/license#

Your current plan and its limits.

GET/api/stats#

How many profiles there are and how many are running: total, running and idle.

Profile fields

A profile with a proxy, from code

POST /api/profiles takes the same fields as the profile editor, and PATCH /api/profiles/{id} any subset of them. The Linux build (3.5.6) and the macOS beta (3.5.5) don’t check them. These are the ones that matter for automation:

FieldWhat it sets
nameThe name shown in the app. Required.
fingerprint.deviceIdThe device model. The user agent, client hints, screen, GPU, CPU and memory all come from it. The ids are under device ids.
fingerprint.timezoneAn IANA zone such as Europe/Berlin. Match it to the proxy’s country. Leave it out and the profile uses this computer’s zone; a name that isn’t an IANA zone is refused.
fingerprint.timezoneModemanual uses timezone; real uses this computer’s zone. From 3.5.8, proxy (Match proxy in the app) looks up the proxy’s exit IP at each launch and uses its zone, and the profile doesn’t launch if that lookup fails. The Linux build (3.5.6) and the macOS beta (3.5.5) don’t know proxy and use timezone, or this computer’s zone without one.
fingerprint.acceptLanguageThe language list, such as de-DE,de;q=0.9,en;q=0.8. The first entry also sets Chrome’s language.
fingerprint.webrtcModealtered (relay only, recommended), disabled or real.
proxyenabled, type (http, https, socks4 or socks5), host, port, username, password. It’s used once host and port are set; send "enabled": false to keep one without using it. Chrome can’t send a password to a SOCKS proxy, so use one that allows your IP.
tags, notesYour own labels, for search and filters in the app.
folderThe id of a folder made in the app. Copy it from a profile already in that folder.
startUrlThe page Chrome opens after launch, http or https.
{
  "name": "shop-de-01",
  "tags": ["shop", "de"],
  "notes": "Created through the API",
  "fingerprint": {
    "deviceId": "win-laptop-irisxe",
    "timezone": "Europe/Berlin",
    "acceptLanguage": "de-DE,de;q=0.9,en;q=0.8",
    "webrtcMode": "altered"
  },
  "proxy": {
    "enabled": true,
    "type": "http",
    "host": "proxy.example.com",
    "port": 8000,
    "username": "user",
    "password": "pass"
  }
}
curl -X POST http://127.0.0.1:31337/api/profiles \
  -H "Authorization: Bearer YOUR-API-KEY" \
  -H "Content-Type: application/json" \
  -d @profile.json

The answer is the saved profile, so the new id is data.id. A refusal says why; errors lists each status.

Device ids

One id per device model

Set fingerprint.deviceId to one of these. A profile’s user agent, client hints, screen, GPU, CPU and memory all come from one device model, so they agree. On Windows (3.5.8), an id the app doesn’t know is refused with a 400 that names it. A profile you set up in the app’s fingerprint editor shows its id in GET /api/profiles. The macOS beta (3.5.5) was built before these ids existed.

  • win-desktop-uhd630
  • win-desktop-gtx1650
  • win-desktop-rtx3060
  • win-desktop-rtx4070
  • win-desktop-rx6700xt
  • win-laptop-irisxe
  • win-laptop-uhd620
  • win-laptop-rtx4060
  • win-mini-radeon
  • mac-air13-m1
  • mac-pro14-m1pro
  • mac-air15-m2
  • mac-pro16-m2pro
  • mac-imac24-m1
  • mac-mini-m2-1080p
  • mac-pro13-2019-intel
  • linux-desktop-intel-cml
  • linux-ubuntu-gtx1060
  • linux-laptop-intel-kbl
  • android-pixel-7
  • android-pixel-8
  • android-pixel-6a
  • android-galaxy-s23
  • android-galaxy-s22
  • android-oneplus-11

What a profile doesn’t change

  • The TLS handshake (JA3/JA4) is your Chrome’s. It is the same for every profile on this computer, and a desktop handshake even for a phone profile.
  • WebGL limits and font measurements come from your hardware. The GPU name is the device model’s; the numbers behind it and the width of rendered text are your machine’s.
  • Some Web Workers see this computer. Workers built from in-page code (blob: and data: URLs), service workers and workers started by other workers report your real CPU cores, memory and platform.
  • On Edge or Brave, the client hints still say Google Chrome. XDLogin falls back to them when Chrome isn’t installed. Install Chrome to avoid the mismatch.
  • Your framework is its own layer. What Playwright or Puppeteer adds to a page, such as an exposed function or an init script, and the JavaScript ChromeDriver runs in it to find elements, are visible to that page. XDLogin doesn’t hide them.
  • Behavior is yours. The profile sets what a site can read about the device. How your script acts, its timing, clicks and request rate, is up to you.

The full list, with our own probe results, is on the test results page.

Errors

What each status means

A refused request answers { "success": false, "error": … }, and error says why. These are the statuses of the REST API on Windows (3.5.8):

StatusWhen
400The body isn’t valid JSON. A create without a name, with an unknown device id, with a timezone that isn’t an IANA name, or with a switched-on proxy that has no host or no valid port. A launch that failed, an unknown id included. A stop for a profile that isn’t running.
401You set an API key, and the request didn’t send it as Authorization: Bearer <key>. Every endpoint checks it, /api/health included.
403A create or clone past your plan’s profile limit. Also any request whose Origin or Host names anything but 127.0.0.1, localhost or [::1], as a web page’s requests do.
404A read, update or clone of a profile id that doesn’t exist, or a path the API doesn’t have.
405A method the path doesn’t take, such as GET on /launch. The MCP server answers 405 to anything but POST, which is what a client set to the older SSE transport gets.
409/connect for a profile that is still starting, isn’t running, runs in the built-in engine or doesn’t exist: poll it for about 30 seconds after a launch, then read error. A delete that couldn’t remove the profile’s data, for example while it is still locked on disk.

A 500 means something failed inside XDLogin itself. The Linux build (3.5.6) and the macOS beta (3.5.5) never answer 403. Linux answers every create with 200, even one past your plan’s limit, which saves nothing; the macOS beta answers that one with a 500.

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 too, through ChromeDriver: install the ChromeDriver that matches your Chrome and 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 & Auto
const id = 'YOUR-PROFILE-ID';              // from GET /api/profiles
// Drop `headers` if you have not set an API key in Settings → API & Auto.
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 → API & Auto, and it starts with XDLogin until you turn it off: 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#

Twenty-one tools, the same on every plan; MCP tools says what each one does. The six page tools work in a running profile’s first tab, the one browser_navigate loads. They are new in Windows 3.5.8: the Linux build (3.5.6) and the macOS beta (3.5.5) don’t have them.

Full agent setup →

MCP tools

What the agent can call

Twenty-one tools on Windows (3.5.8), the same on every plan. The six marked 3.5.8+ are new in it; the Linux build (3.5.6) and the macOS beta (3.5.5) have the other fifteen, with the differences noted in their rows. Profiles are also exposed as resources (xdlogin://profiles, xdlogin://profile/{id} and xdlogin://sessions/active), so an agent can read a profile before acting on it.

ToolWhat it does
profile_listEvery profile with its settings.
profile_getOne profile by id.
profile_createA new profile from the same fields as the REST API’s create: device model, timezone, languages, proxy, tags, folder and start page. Answers with the saved profile and its id. From 3.5.8, "timezoneMode": "proxy" matches the timezone to the proxy at each launch. On Linux (3.5.6) and the macOS beta (3.5.5) it takes only a name, a platform and a proxy; on Linux it answers before saving, with an empty object.
profile_cloneA copy of a profile’s settings under a new id, with an empty session.
profile_launchStarts a profile’s browser, and opens startUrl if you pass one.
profile_stopCloses a profile’s browser normally, so the cookies of a login a moment earlier are kept. On Linux (3.5.6) and the macOS beta (3.5.5) it ends Chrome at once.
profile_deleteDeletes a profile and its data on disk, stopping it first. Don’t pre-approve this one. On the macOS beta (3.5.5) only the record is removed; the profile’s browser data stays on disk.
browser_navigateLoads a URL in the profile’s first tab, launching the profile if it isn’t running.
browser_screenshotA JPEG of what the profile’s first tab shows, the tab browser_navigate uses. That tab is brought to the front first. On Linux (3.5.6) and the macOS beta (3.5.5) it returns a text description of the profile, not an image.
browser_snapshot3.5.8+ Lists what that tab shows, one line per control, heading and piece of text, each with a ref such as e12 that browser_click, browser_type, browser_scroll and browser_get_text take. Refs hold until the tab loads another page.
browser_click3.5.8+ Clicks with real mouse input, or taps on a phone profile: on a ref, on the first element a CSS selector matches, or at x/y in the browser_screenshot image.
browser_type3.5.8+ Types key by key with a person’s pauses, into a ref or selector (clicked first) or whatever has focus; can clear the field first or press Enter after.
browser_press_key3.5.8+ Presses one key, such as Enter, Tab, Escape or ArrowDown, or a single character, with Control, Shift, Alt or Meta held if you ask.
browser_scroll3.5.8+ Scrolls with the mouse wheel, or a swipe on a phone profile, or brings a ref or selector into view, and answers with where the page is scrolled to.
browser_get_text3.5.8+ The text the tab shows, laid out as on screen, or one element’s text, cut at 20,000 characters.
fingerprint_getThe profile’s fingerprint settings.
proxy_testTests a proxy and reports the exit IP, country, city and latency. From 3.5.8 it also names the protocol the proxy speaks and says why a proxy failed: a wrong password, used-up traffic or an unreachable port.
cookies_exportA profile’s cookies, read from its own Chrome store. On Linux (3.5.6) and the macOS beta (3.5.5), only for profiles on the built-in engine.
cookies_importAdds cookies to a profile’s own Chrome store, and answers with how many it holds afterwards. On Linux (3.5.6) and the macOS beta (3.5.5), only for profiles on the built-in engine.
healthThe MCP server’s status.
statsHow many profiles exist, how many run and how many are idle.