Afina

Download app

AppleWindows
EN
BlogBlog

May 15, 2026

Afina API: Antidetect Browser Automation via REST

Afina API: Antidetect Browser Automation via REST

One POST request - and you have 100 profiles created with unique fingerprints, attached proxies and browsers spun up in headless mode or with a CDP endpoint ready for Puppeteer. No cloud quotas, no tier-based tokens, no API rate limits: Afina's API is fully local, runs on 127.0.0.1:50778, and the only thing deciding how many requests you make is your own machine.

In this article we break down how the Afina antidetect browser API is built: 79 endpoints, the auth scheme, how to spin up your first profile and wire it into Puppeteer or Playwright in 5 minutes, which scenarios stop being painful once you move from the UI to the API, and where the boundary lies between the REST API and the MCP server for those wondering which one to pick.

Why You Need an Antidetect Browser API

If you work on multi-accounting across hundreds of profiles, UI routine quickly becomes the bottleneck:

  • Affiliate marketing. Creating 200 profiles for a campaign by hand - two hours. Through the API - one for loop and 30 seconds of work.
  • Account farming. Preparing profiles by geolocation with proxies, tags and auto-logins. Rotating proxies, warm-up routines - none of this exists without scripting.
  • Web scraping. Parallel data collection from dozens of sites - every profile has its own fingerprint, its own cookie jar, its own proxy. The UI was not built for this.
  • E-commerce and competitor monitoring. Prices, descriptions, promotions - impossible to gather manually, but through the API you can run as much as your infrastructure handles.
  • Bonus hunting and airdrop campaigns. Sophisticated launch schedules with time distribution to bypass anti-fraud clustering - this is all API + cron.
  • Betting and arbitrage. Reaction speed measured in seconds, synchronized actions across dozens of profiles. Programmatic only.

If you do the same thing more than twice - it is already a job for the API. Afina exposes a REST interface that covers 100% of the UI's capabilities - anything you can click can be triggered with a single HTTP call.

How Afina's API Differs From Cloud Alternatives

Most antidetect browsers (Octo, Multilogin, Dolphin) ship cloud APIs - your requests hit the vendor's server, pass through your plan's quota, and only then get relayed to the local application. That model has obvious downsides:

  • Subscription-based limits. 1000-5000 requests per day on top-tier plans - not enough for serious automation.
  • Network latency. Every call travels through the internet twice - to the cloud and back to the local client.
  • Tied to cloud availability. The vendor's server goes down - all of your automation goes down with it, even if your own hardware is healthy.
  • Data leaves your machine. Whatever the vendor promises, metadata about your profiles and your request parameters ends up in their infrastructure.

Afina's API is built differently: it is a local HTTP server on 127.0.0.1 that starts together with the desktop application. No cloud middlemen, no rate limits, no latency above a millisecond. Every request goes straight into the application's Rust backend, which does all the work anyway - the API just opens a second channel to it.

ParameterCloud API (Octo and others)Afina Local API
Requests per daySubscription-based (1k-10k)Unlimited
Latency50-300 ms1-10 ms
Depends on vendor cloudYesNo
Data leaves your machineYesNo
Works offlineNoYes (for local operations)
CostPer-planFree
Direct CDP accessNo, only wrapper commandsYes, wsEndpoint is returned in the response

This difference fundamentally changes how you architect your solutions. You can hammer tens of thousands of calls per hour without watching quotas, keep bots running on an internal network without internet egress (for anything that does not need external services), and connect directly to the Chrome DevTools Protocol for the most delicate scenarios.

5 Minutes to Get Started

Step 1. Grab the API key

Launch Afina, open Settings -> API and copy the key. It is a 32-character [A-Za-z0-9] string generated automatically on first launch and stored encrypted (libsodium sealed box) in the local database.

The key is passed in the X-API-Key header of every request. Without a valid key the server silently drops the connection - no response, no HTTP code. This is intentional: an attacker gets zero information about why the request failed and cannot distinguish "wrong key" from "server not responding".

Step 2. Check the server is alive

curl http://127.0.0.1:50778/api/health

Response:

{ "status": "ok", "version": "2.0.4" }

If you get Connection refused - Afina is not running. The health endpoint does not require a key.

Step 3. Fetch the profile list

curl http://127.0.0.1:50778/api/profiles/list \
  -H "X-API-Key: YOUR_KEY"

Response:

{
  "data": [
    { "accountId": "550e8400-...", "name": "Profile 1", "tags": ["test"], "isRunning": false },
    { "accountId": "660e8400-...", "name": "Profile 2", "tags": ["prod"], "isRunning": true }
  ],
  "count": 2
}

If that call worked - everything is in place. From here on it is only a matter of imagination.

API Anatomy: 79 Endpoints by Category

All endpoints are grouped by entity domain. The base URL is http://127.0.0.1:50778, every endpoint accepts Content-Type: application/json and the X-API-Key header. Full list below.

Profiles (Accounts) - 10 Endpoints

PathMethodPurpose
/api/profiles/listGETAll accounts with filters by tags/groups
/api/profiles/getGET, POSTA single account by ID or UUID
/api/profiles/createPOSTCreate a permanent profile
/api/profiles/one-timePOSTCreate a one-time profile (auto-deleted on stop)
/api/profiles/updatePOSTUpdate parameters
/api/profiles/startPOSTLaunch the browser -> returns wsEndpoint for CDP
/api/profiles/stopPOSTStop (graceful shutdown with cookie flush)
/api/profiles/deletePOSTSoft-delete (moved to trash, restorable)
/api/profiles/hard-deletePOSTFinal deletion with full data cleanup
/api/profiles/cookies/set, /cookies/exportPOSTCookie jar import/export

When you create a profile, all parameters travel in a single JSON - proxy, tags, groups, screen, languages, fingerprint noise, startup URLs, blocked ports, extraArgs, settings. The unique fingerprint (user agent, WebGL renderer, CPU/memory, fonts, time zone) is generated by the same backend the UI uses - these are not "random values", they are valid combinations that survive even sophisticated fingerprint checks.

Soft-delete and hard-delete are split intentionally: the first one moves the profile to trash (restorable with a single click or call), the second wipes everything - including the profile folder, cookies, cache and the token in the keychain. A running browser at hard-delete time is stopped gracefully, with no data loss.

Browser Control (CDP Commands) - 5 Endpoints

PathMethodPurpose
/api/profiles/evalPOSTRun JavaScript in the current tab
/api/profiles/screenshotPOSTPage screenshot -> base64 PNG
/api/profiles/cookies/setPOSTSet a cookie via CDP Network.setCookies
/api/profiles/cookies/exportPOSTRead all cookies via CDP

These are "quick touch" tools - for one-off operations without writing a full script. For serious automation (form filling, navigation, waiting for elements) you are better off using Puppeteer/Playwright via wsEndpoint - more on that below.

eval accepts a JavaScript expression, runs it in the context of the active page, auto-awaits promises and uses returnByValue. You can return a string, a number, an object - anything serializable to JSON.

RPA Scripts - 7 Endpoints

PathMethodPurpose
/api/scripts/listGETAll RPA scripts
/api/scripts/getGETA single script by ID
/api/scripts/createPOSTCreate an RPA script (JSON blocks)
/api/scripts/updatePOSTUpdate
/api/scripts/runPOSTRun a script on a profile -> task_uuid
/api/scripts/run-logsGETLogs of a direct run
/api/scripts/stopPOSTStop execution

Afina's RPA scripts are JSON structures with blocks (click, type, goto, condition, loop, executeModule and so on) and connections between them. Each block describes a step of the scenario, the executor walks them along the graph.

For programmatic script creation - pass a full JSON with elements and connections arrays to /api/scripts/create. The structure matches exactly what the visual editor sees, and any script created via API can be opened and edited in the UI.

RPA Modules - 7 Endpoints

PathMethodPurpose
/api/modules/listGETAll modules
/api/modules/getGETA single module + file list
/api/modules/createPOSTScaffold (index.js, utils_<id>.js, package.json, settings.json) + npm install
/api/modules/updatePOSTUpdate files or metadata
/api/modules/resignPOSTRecompute the Ed25519 signature
/api/modules/deletePOSTSoft-delete
/api/modules/hard-deletePOSTFinal deletion

When visual RPA blocks are not enough - you write a custom JavaScript module. The API does this end-to-end: creation (with automatic npm install for dependencies), file editing, re-signing.

Important detail: every module is signed with Ed25519 over an MD5 manifest of the folder. Any file change without a resign call breaks the signature - the executor blocks the run with a clear error. This protects against code tampering: you cannot run a module someone has modified behind your back.

Task Groups and Tasks - 17 Endpoints

PathMethodPurpose
/api/task-groups/listGETList of groups
/api/task-groups/getGETA group with its tasks
/api/task-groups/tasksGETJust the tasks of a group
/api/task-groups/createPOSTCreate a group with a schedule
/api/task-groups/updatePOSTUpdate schedule/name
/api/task-groups/startPOSTActivate (active=1)
/api/task-groups/stopPOSTDeactivate + stop running tasks
/api/task-groups/restartPOSTRestart error/finished tasks
/api/task-groups/deleteDELETE, POSTSoft-delete
/api/task-groups/hard-deletePOSTFinal deletion
/api/tasks/listGETAll tasks (filters by status, scriptId)
/api/tasks/activeGETOnly the ones running right now
/api/tasks/logsGETLogs by task_uuid
/api/tasks/createPOSTAdd tasks to a group (bulk)
/api/tasks/updatePOSTChange status/executeAt/tag
/api/tasks/deletePOSTSoft-delete (bulk)
/api/tasks/stopPOSTStop execution (with optional closeBrowser)

This is the most powerful category in the API. A group is a schedule configuration (time windows, repeats, timeouts, parallelism limits), a task is a single run of a script on a specific account.

The schedule supports natural date formats for the executeAt field:

  • "now" - immediately
  • "in 5m" - in 5 minutes
  • "+1h" - in an hour
  • "tomorrow 09:00" - tomorrow at 9:00 local time
  • "2026-05-15 14:30" - a specific moment
  • ISO 8601: "2026-05-15T14:30:00Z"
  • Unix epoch: 1715787000

Time windows and parallelism are configured at the group level:

{
  "schedule": true,
  "timeFrom": "09:00",
  "timeTo": "18:00",
  "scheduleTime": true,
  "startHour": 9.0,
  "endHour": 18.0,
  "activeSession": 5,
  "isRepeatable": true,
  "repeatCount": 2,
  "timeout": 300
}

The group will run at most 5 tasks in parallel, only inside the 09:00-18:00 window, will repeat each task 2 additional times, and every task auto-times-out after 5 minutes. This is the configuration for serious production campaigns - in the UI this is usually 10 minutes of clicks, but via the API it is a single POST.

Proxies - 3 Endpoints

PathMethodPurpose
/api/proxies/addPOSTAdd and validate
/api/proxies/checkPOSTCheck one or several
/api/proxies/check-allPOSTAudit every proxy in the table

All checks include a real network test, including a UDP test for SOCKS5 (critical, because Afina supports HTTP/3 over SOCKS5 with QUIC). Proxies that fail are not saved. That guarantees only live connections live in your database.

More on proxy types and scenarios - in the dedicated piece on proxies and their kinds.

Databases, Variables, Keys - 15 Endpoints

PathMethodPurpose
/api/databases/*GET/POSTCRUD for connections (SQLite/MySQL/PostgreSQL) used by the RPA database block
/api/global-vars/*GET/POSTCRUD for global variables ${name}, available in every script
/api/keys/*GET/POSTCatalog of keys and API tokens (OpenAI, Telegram, ChatGPT)
/api/accounts/varsGETAll variables of an account (plain + encrypted)
/api/accounts/vars/setPOSTWrite (plain or encrypted)
/api/accounts/vars/deletePOSTDelete

Pay special attention to account_vars. They are stored in two modes:

StorageWhereVisible via API
Plainaccount.settingsYes
Encryptedaccount_data_blobNo (write and read by key only)

Encrypted variables are encrypted with sealed box (libsodium). Via the API you can write a value and later read it back by the same key - but the plaintext itself is never returned over the API. Decryption happens only inside the script executor right before execution. This is the perfect store for 2FA secrets, passwords and crypto wallet private keys.

Email / IMAP - 2 Endpoints

PathMethodPurpose
/api/emails/listGETList of configured email addresses
/api/emails/togglePOSTEnable/disable IMAP monitoring

Management of IMAP connections for automated email parsing (confirmation codes, recovery links, internal notifications).

Language Choice: Node, Python or Something Else

Afina's API is plain HTTP+JSON. It does not care what you make requests with. The most common options:

LanguageWhen to useRecommended libraries
Node.jsIf you already work with Puppeteer/Playwright (they are Node too)axios, node-fetch, undici
PythonData science, scraping, ML pipelinesrequests, httpx, aiohttp
GoHigh-throughput parallel processingstandard net/http
Bash + curlDeployment scripts, orchestration, CIcurl + jq
PHP, Ruby, C#, JavaTo fit an existing team stackany HTTP library

If you have code in one language and need to port it to another - just ask ChatGPT or Claude. With plain REST this always works. The structure stays the same: URL, method, X-API-Key header, JSON body.

For Puppeteer/Playwright/Selenium you will still need Node or Python - but they are needed not for Afina's API, but for controlling the browser itself after start_browser. These three tools are the industry standard for CDP/WebDriver automation, and Afina plays well with them out of the box.

Wiring Puppeteer and Playwright Through wsEndpoint

The most powerful feature of Afina's API is direct access to the Chrome DevTools Protocol. When you launch a profile via /api/profiles/start, the response includes wsEndpoint - a fully featured CDP WebSocket you can connect any tool to.

Node.js + Puppeteer Example

import axios from 'axios';
import puppeteer from 'puppeteer';

const API = 'http://127.0.0.1:50778';
const KEY = process.env.AFINA_API_KEY;
const headers = { 'X-API-Key': KEY };

async function main() {
  // 1. Launch the profile via Afina's API
  const { data } = await axios.post(
    `${API}/api/profiles/start`,
    { profileId: '550e8400-e29b-41d4-a716-446655440000' },
    { headers }
  );

  console.log('Browser started, CDP:', data.wsEndpoint);

  // 2. Connect Puppeteer to the existing browser
  const browser = await puppeteer.connect({
    browserWSEndpoint: data.wsEndpoint,
    defaultViewport: null,
  });

  // 3. From here on  -  regular Puppeteer
  const page = (await browser.pages())[0] || await browser.newPage();
  await page.goto('https://example.com');
  await page.waitForSelector('h1');
  const title = await page.$eval('h1', el => el.innerText);
  console.log('H1:', title);

  // 4. Disconnect (the browser stays running)
  await browser.disconnect();

  // 5. Close the profile via the API
  await axios.post(`${API}/api/profiles/stop`,
    { profileId: '550e8400-e29b-41d4-a716-446655440000' },
    { headers }
  );
}

main().catch(console.error);

Python + Playwright Example

import os
import requests
from playwright.sync_api import sync_playwright

API = 'http://127.0.0.1:50778'
KEY = os.environ['AFINA_API_KEY']
HEADERS = {'X-API-Key': KEY}

profile_id = '550e8400-e29b-41d4-a716-446655440000'

# 1. Launch the profile
resp = requests.post(
    f'{API}/api/profiles/start',
    json={'profileId': profile_id},
    headers=HEADERS,
).json()
ws = resp['wsEndpoint']
print('CDP:', ws)

# 2. Playwright connect
with sync_playwright() as p:
    browser = p.chromium.connect_over_cdp(ws)
    context = browser.contexts[0]
    page = context.pages[0] if context.pages else context.new_page()
    page.goto('https://example.com')
    print('Title:', page.title())
    browser.close()

# 3. Close the profile
requests.post(
    f'{API}/api/profiles/stop',
    json={'profileId': profile_id},
    headers=HEADERS,
)

Selenium WebDriver

Selenium works too - via chromedriver with the --remote-debugging-port flag. Slightly less convenient (needs extra setup), but for legacy projects it is a viable option. Details in the browser action automation guide.

Ready-Made Recipes for Common Scenarios

Recipe 1. Create 100 Profiles With Proxies in a Minute

import axios from 'axios';
const API = 'http://127.0.0.1:50778';
const headers = { 'X-API-Key': process.env.AFINA_API_KEY };

const proxies = JSON.parse(await fs.readFile('proxies.json', 'utf8'));

for (let i = 0; i < 100; i++) {
  await axios.post(`${API}/api/profiles/create`, {
    name: `Campaign-1 #${i + 1}`,
    tags: ['campaign-1', 'phase-1'],
    proxy: proxies[i % proxies.length],
    settings: {
      languages: ['en-US', 'en'],
      startupURLs: ['https://example.com/landing'],
    },
  }, { headers });
}

100 profiles with unique fingerprints, attached proxies and tags - ready to launch in a minute. The same job in the UI would take an hour.

Recipe 2. Bulk-Run a Script on 50 Accounts With a Schedule

// 1. Create a group with a schedule
const group = (await axios.post(`${API}/api/task-groups/create`, {
  tag: 'warmup-batch-1',
  active: false,
  schedule: true,
  timeFrom: '09:00',
  timeTo: '18:00',
  activeSession: 5,
  isRepeatable: false,
}, { headers })).data;

// 2. Fetch the list of accounts with the tag
const accounts = (await axios.get(
  `${API}/api/profiles/list?tag=campaign-1`, { headers }
)).data.data;

// 3. Add a task for each one with time staggering
const tasks = accounts.map((acc, i) => ({
  accountId: acc.accountId,
  scriptId: 42,
  executeAt: `+${i * 3}m`,
}));

await axios.post(`${API}/api/tasks/create`, {
  groupId: group.id,
  tasks,
}, { headers });

// 4. Start the group
await axios.post(`${API}/api/task-groups/start`,
  { id: group.id }, { headers });

Warming up 50 accounts at 3-minute intervals, inside a 09:00-18:00 window, with at most 5 running in parallel - one file, 30 lines of code, exactly one run.

Recipe 3. Pull Logs of Every Failed Task for the Day

curl "http://127.0.0.1:50778/api/tasks/list?status=error&since=2026-05-14T00:00:00Z" \
  -H "X-API-Key: $AFINA_API_KEY" \
  | jq -r '.data[] | .taskUuid' \
  | while read uuid; do
      echo "=== $uuid ==="
      curl -s "http://127.0.0.1:50778/api/tasks/logs?uuid=$uuid" \
        -H "X-API-Key: $AFINA_API_KEY" \
        | jq -r '.data[] | "\(.time) [\(.level)] \(.message)"'
    done

Every error of the day in a single report. You can feed it to an LLM or parse with regexes to label failure causes.

Recipe 4. Eval JavaScript Against a Running Browser

curl -X POST http://127.0.0.1:50778/api/profiles/eval \
  -H "X-API-Key: $AFINA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "profileId": "550e8400-...",
    "code": "JSON.stringify({ url: location.href, title: document.title, cookies: document.cookie })"
  }'

Response:

{
  "result": "{\"url\":\"https://example.com\",\"title\":\"Example Domain\",\"cookies\":\"\"}"
}

The middle ground between nothing and full-blown Puppeteer - for quick checks and page probing.

Recipe 5. Back Up Cookies Of Every Profile With a Tag

import requests, json, os

API = 'http://127.0.0.1:50778'
H = {'X-API-Key': os.environ['AFINA_API_KEY']}

accounts = requests.get(f'{API}/api/profiles/list?tag=prod', headers=H).json()['data']

for a in accounts:
    cookies = requests.post(
        f'{API}/api/profiles/cookies/export',
        json={'profileId': a['accountId']},
        headers=H,
    ).json()
    with open(f'backup/{a["accountId"]}.json', 'w') as f:
        json.dump(cookies, f)

A full cookie-jar backup of all production profiles - use it on every CI/CD heartbeat, after a major update or simply as insurance.

Security

Afina's API was designed with the assumption that any local attack surface is an attack surface. Here is what is in place:

  • Localhost only. The server binds only to 127.0.0.1. From the outside it cannot be reached even in theory. If you need remote access - do it via SSH tunnel (ssh -L 50778:127.0.0.1:50778 user@host) and decide for yourself who you trust with that tunnel.
  • API key on every request. Without X-API-Key the server drops the connection silently, with no HTTP response. An attacker cannot even tell whether a working server is listening, let alone brute-force keys.
  • Encrypted account vars. Sensitive data (2FA, passwords, seed phrases) is encrypted with sealed box and never returned in plaintext over the API. Only the RPA executor at script-run time has access to it.
  • Signed modules. Every custom JS module is signed with Ed25519. Tampering with files without resign breaks the signature - the executor blocks the run. This protects against the scenario "someone reached disk and swapped index.js".
  • Profile isolation. A baseline Afina feature unrelated to the API: every profile gets its own cookie jar, localStorage, IndexedDB, cache. Plugging the API in does not loosen that isolation.
  • Connection limit. 128 concurrent requests at a time - a guard against accidental DoS from your own code.
  • Request size limit. 16 MB per request - blocks attacks via huge payloads.

API or MCP: Which One to Pick

Afina also has an MCP server - a separate product, a thin wrapper on top of the REST API that exposes the same capabilities to AI agents (Claude, ChatGPT, Cursor) over the Model Context Protocol. The natural question follows: which one do you use - MCP or the raw API?

CriterionDirect REST APIMCP Server
Who is the userDeveloper with codeAI agent or human in chat
Control over logicFullThrough prompt
Development speedSlower (you write code)Instant (a sentence in chat)
Reproducibility100% (code is deterministic)Depends on the model
Fit for CI/CDYesNo (no AI in the pipeline)
Integrates into existing codePerfectlyNot designed for that
Level of abstractionEndpoint levelIntent level "do X"

Pick the REST API if:

  • You have a team and a codebase where Afina integration is one of the modules.
  • You need full reproducibility and control (CI/CD, recurring jobs, production campaigns).
  • You want to wrap the API in your own UI or API gateway.

Pick MCP if:

  • You are in a Claude/ChatGPT chat and want to "just say what you need".
  • You are prototyping ideas, exploring possibilities.
  • The team has no programmers, but an AI agent is already at hand.

Most serious teams end up using both: MCP - for research and quick experiments, REST API - for production.

Bottom Line

An antidetect browser without an API is a tool for solo operators. The UI is fine while you are doing tens of operations. At hundreds it slows you down; at thousands it becomes impossible.

The API turns Afina into a platform. The same backend serves both the UI buttons and the REST calls from your scripts - nothing gets lost in translation, no cloud middlemen, no request limits. Plug Puppeteer or Playwright in via wsEndpoint, orchestrate work through task groups, encrypt sensitive data in encrypted variables, sign your own JS code - and you end up with full-blown infrastructure for multi-accounting at any scale.

If your current browser (Octo, Multilogin, Dolphin) constrains you with cloud quotas and 1000 requests per day - moving to Afina with its local API gives you two orders of magnitude in scale and removes the dependency on someone else's servers.

Install Afina Browser for free, grab the API key in settings in 30 seconds, and try your first call - start with /api/health, the rest will follow. In an hour you will have automation that used to take you days in the UI.

FAQ — Frequently Asked Questions

Is keeping the API open safe? Can someone steal data through the API?

The API binds only to 127.0.0.1 - no external access, no local firewall needed. Inside the machine - any process that knows the key can call the API. If you have untrusted processes on the machine (you run third-party code) - that is not the kind of machine to run an antidetect browser on anyway. Encrypted variables are not returned even with a valid key.

Do local API calls count against any limit?

No. The API is fully local, no quotas on the plan and no vendor-side limits. You can make a million requests per hour and the only thing you hit is your machine's hardware.

Can the server port be changed?

In the current version port 50778 is hardcoded in feature_flags.rs. You can change it before compiling. At runtime - not yet, but if 50778 is busy the server automatically tries 50779-50787.

How do I know the profile is actually up and ready for CDP?

The response from /api/profiles/start only comes back after the browser has launched and opened the CDP endpoint. If you received wsEndpoint - you can connect immediately. If you got an error - the browser failed to start, read error in the response.

What happens with very heavy concurrent API traffic?

The limit is 128 simultaneous connections. Anything above that waits in queue. If you need more - batch your requests (most endpoints support bulk operations: tasks/create accepts an array, proxies/check too, and so on).

Can the API be called from another machine on the local network?

Not directly (127.0.0.1 only). But you can forward through an SSH tunnel or stand a local nginx/Caddy in front as a reverse proxy - at your own risk. The best option is to keep everything on a single machine or to use Afina's team access with role separation.

Are webhooks supported - for the API to push events on its own?

In the current version - via polling (/api/tasks/active, /api/tasks/list?status=error). Webhooks are on the roadmap.

What if I would rather not deal with raw HTTP and grab a ready library?

For Node there is the afina-mcp package - alongside the MCP server side, it exports a typed client for the REST API. You can use it as a regular library without the MCP bridge. For Python a ready-made SDK does not exist yet, but a wrapper around requests takes about an hour to write.

How long does wsEndpoint live?

As long as the browser is running. After /api/profiles/stop or a manual browser close - the endpoint becomes invalid. If the process crashed and the browser restarted, the new wsEndpoint must be fetched via another start call (it will return alreadyRunning: true together with the current wsEndpoint without restarting).

Does it behave the same on macOS, Windows and Linux?

Yes. Afina's API is an HTTP server inside the desktop application, and it is built for all three platforms from a single Rust codebase. There are no platform differences in the API.

Related terms

Continue reading onAutomation scripts — Browser profiles | Afina Browser
Mykyta Korniienko

Hi! I’m Mykyta Korniienko, a developer and the founder of Afina Browser. I enjoy breaking down complex things and turning them into practical, easy-to-use solutions. I work on realistic browser profiles, network authenticity (UDP / QUIC / HTTP-3), automation, and scalable SaaS infrastructure.

Over the years, I’ve worked across backend, frontend, and deep inside Chromium forks, which helps me understand how things work in real-world conditions — not just on paper. In my projects, I focus on security, performance, and reliably passing real antifraud checks.

When I’m not coding, I’m usually testing new ideas, improving Afina, or thinking about how to make tools for automation, privacy, and multi-account workflows even more convenient for professionals.