Оновлено:

Afina HTTP API#

Локальний HTTP-сервер для зовнішніх скриптів, автоматизації та AI-агентів через MCP. Запускається разом із десктопним застосунком Afina.

  • Base URL: http://127.0.0.1:50778
  • Default port: 50778; якщо зайнятий, сервер пробує наступні порти.
  • JSON: усі відповіді мають application/json, крім /api/tasks/logs і /api/scripts/run-logs (text/plain).
  • Source of truth: src-tauri/src/browser/server.rs.

Аутентифікація#

Усі /api/* потребують x-api-key із Налаштування → Основні → API ключ. Якщо ключ не задано або він неправильний: 401 Unauthorized / тихе закриття з'єднання.

bash

Без ключа доступні лише GET /api/health, GET /, GET /oauth/callback?code=.... CORS дозволено тільки для localhost-origin (http://localhost, http://127.0.0.1, tauri://); методи GET, POST, DELETE, OPTIONS; headers Content-Type, X-API-Key.

Конвенції#

  • id = numeric PK у SQLite; accountId = UUID профілю. Більшість write-endpoints приймають обидва.
  • Boolean: true/false або 0/1; datetime: ISO-8601, default datetime('now').
  • */delete = soft delete (isDeleted=1); */hard-delete = фізичне видалення з БД/диска.
  • */update = PATCH лише переданих полів.
  • Write-endpoints емітять reload-event для UI refresh.

Health#

GET /api/health
Heartbeat без API key. Відповідь: { "status": "ok", "running": 2 }, де running = кількість запущених браузерів.

Profiles - акаунти (CRUD)#

GET /api/profiles/list
Список акаунтів; до кожного додаються isRunning, tags:[{id,name,color}], groups:[{id,name}].

Query params (опційні, AND):

ПараметрТипОпис
isRunningtrue/falseЗапущені/зупинені
groupIdintФільтр за групою
tagIdintФільтр за тегом

Відповідь: { message, count, accounts:[{ id, accountId, name, ua, proxyId, os, macChip, isRunning, tags, groups }] }.

GET /api/profiles/get / POST /api/profiles/get
Один акаунт за UUID: query/body { accountId }. Відповідь: { message, profile:{...} }.

POST /api/profiles/create
Створює профіль. fingerprint: не передано → генерується; повний → береться як є з нормалізацією; частковий → генерується база й мерджиться за правилом incoming-wins.

Канонічні поля fingerprint: userAgent, platform, platformVersion, vendor, productSub, CPUcores, deviceMemory, language, languages, AcceptLanguage, Brand, BrandVersion, BrandFullVersion, WebGLRenderer, WebGLVendor, Arch, Bitness, wow64, плюс NoiseCanvas/Audio/Rects/GL.

Завжди ігноруються:

ПолеЧому
userAgentМає збігатися з Afina-Chromium build
BrandFullVersionКонстанта build, пов'язана з UA
deviceMemoryДля ua<147 браузер віддає максимум 8; 16/32 дозволені з ua>=147

Аліаси fingerprint:

ПереданоСтає
uaPlatformVersion, platform_versionplatformVersion
uaArchitecture, architectureArch
uaBitnessBitness
webGLVendor, webglVendor, unmasked_vendorWebGLVendor
webGLRenderer, webglRenderer, unmasked_rendererWebGLRenderer
hardwareConcurrency, hardware_concurrency, cpuCoresCPUcores
device_memorydeviceMemory
user_agentuserAgent
product_subproductSub
accept_language, acceptLanguageAcceptLanguage
brandVersion / brandFullVersion / uaFullVersionBrandVersion / BrandFullVersion
ua_full_versionBrandFullVersion
  • ua береться з локального {data_dir}/browser/UA*; клієнтське значення ігнорується.
  • os для генерації: payload → fingerprint.platformmacos; допустимо macos, windows10, windows11.
  • os у БД: Windows10 / Windows11 / x86 / arm64; Intel WebGL дає os="x86", macChip="", platform="x86", Arch="".
  • macChip: WebGLRenderer (Apple M2m2) → payload.chip/macChipfp.chip"".
  • osArch: arm64/x86 для macOS; Windows ігнорує.

Інші параметри (name, proxy*, tags*, settings, шуми, screenSize тощо) наведені нижче.

Body (усе опційно, крім вимог конкретного сценарію):

ПолеТипОпис
namestringНазва
accountIdstringФорсувати UUID
osmacos/windows10Генерація FP; default macos
osArcharm64/x86Арх macOS; default arm64
chipstringm1/m2/m3/m4/intel
uastringІгнорується; береться локальний UA
browserTypestringafina/mimic/octo/vision/ads/dolphin; default afina
notestringНотатка
proxyIdintSaved proxy; взаємовиключно з proxyData
proxyTypesaved/set/without_proxyТип проксі
proxyDataobjectInline proxy.
Обов'язкові: host, port, type.
Опційні: username, password, changeIpUrl, remark, country, countryCode, timezone, visible_ip, isActive, isUDPSupported
tagIds / tagNamesarrayТеги за id/name; нові створюються
accountGroupIds / accountGroupNamesarrayГрупи за id/name
languagesarrayOverride мов
language / timezonestringЯкщо *_from_ip=false
timezone_from_ip / language_from_ip / languages_from_ipboolАвто з IP; default true
languageInterfaceTypefrom_language/real_valueDefault real_value
screenSizestring1920x1080; avail auto
useScreenDefaultboolДефолтний екран
availWidth/availHeight/colorDepth/pixelDepthintЕкран
blockedPorts[int]`--afina-fp
blockOnProxyCountryChangebool/nullnull=global, true=block, false=allow
localCacheModedefault/no_cacheno_cache--disk-cache-size=0
startupUrls[string]Відкрити під час першого запуску
extraArgsstringChromium CLI args
settingsobjectPer-account KV ${key}
isNoiseCanvasEnabled / isNoiseAudioEnabled / isNoiseRectsEnabled / isNoiseGLEnabledboolFP noise
fingerprintobjectПовний/частковий FP
teamUuidstringКоманда ліцензії

Успіх: { "status": "success", "id": 50, "accountId": "<uuid>", "account": {...} }.

POST /api/profiles/update
PATCH за { id } або { accountId }. Приймає поля create, isDeleted, skipServerSync, теги tagIds + selection(replace|append|delete|clear), групи accountGroupIds + selectionGroups(replaceGroup|appendGroup|deleteGroup|clearGroup).

POST /api/profiles/delete
Soft-delete. Body { id } або { accountId } (UUID-string). Успіх: { "message": "Account successfully deleted" }.

POST /api/profiles/hard-delete
Безповоротно видаляє account/profile/junction, proxy_usage і файли профілю; синхронізує Afina server DELETE /profiles/:uuid; перед видаленням закриває браузер. Body { id } / { ids:[...] } або { accountId } / { accountIds:[...] }. Успіх: { "status":"success","deleted":3 }.

Browser control - керування браузером#

POST /api/profiles/start
Запускає браузер. Body { "profileId": "<UUID>" }. Відповідь містить wsEndpoint і data.port; якщо вже запущений: alreadyRunning:true. wsEndpoint підходить для Puppeteer/Playwright/CDP.

POST /api/profiles/stop
Закриває запущений браузер через CDP Browser.close, потім graceful kill після таймауту. Body { "profileId": "<UUID>" }. 404, якщо профіль не запущено.

One-time profiles - одноразові профілі#

POST /api/profiles/one-time
Створює disposable profile, одразу запускає й hard-delete після зупинки. Body = поля /api/profiles/create; name default one-time-<ts>.

  • Створення йде через /api/profiles/create з partial-fingerprint merge і внутрішнім __oneTime_internal__.
  • Повертає id, accountId, wsEndpoint, port, isOneTime:true.
  • Зупинка: POST /api/profiles/stop, browser.close() або crash; після цього профіль зникає з /api/profiles/list і Trash.
  • Якщо start_browser впав, створений one-time профіль видаляється синхронно.
  • RPM/RPH: один виклик = create + start + stop + delete; не частіше приблизно 1/сек.

isOneTime не можна виставити через /api/profiles/create або /api/profiles/update; hard-delete в exit-handler спрацьовує лише після SQL-перевірки account.isOneTime=1.

Browser introspection - eval / screenshot#

POST /api/profiles/eval
Виконує JS у поточній видимій вкладці запущеного профілю. Body { profileId, code }; код обгортається в IIFE, promises await, результат returnByValue. 404, якщо браузер не запущено. Приклад відповіді: { "value": "https://example.com" }.

POST /api/profiles/screenshot
Скриншот поточної видимої вкладки. Body { profileId, format:"png" }; відповідь { mimeType:"image/png", data:"iVBORw0..." }.

Cookies - імпорт/експорт cookies#

POST /api/profiles/cookies/set
Кладе cookies у чергу імпорту {data_dir}/cookies/{uuid}/cookies_{ts}.json; під час наступного старту браузера сервер інжектить їх через CDP. Неприйнятий cookies_*.json перезаписується.

Body:

ПолеТипОпис
accountIdint/UUIDaccount.id або UUID; alias id
cookiesarrayCookie objects: domain/name/value/path/expirationDate/secure/httpOnly/sameSite/...

Успіх: { "status":"success", "data": { "count": 1 } }.

Акаунт не має бути запущений: cookies застосуються під час наступного /api/profiles/start. Для гарячого вводу використовуйте CDP Network.setCookies через /api/profiles/eval або wsEndpoint.

POST /api/profiles/cookies/export
Експортує розшифровані cookies із відкритої папки профілю, потім .zip, потім .afbk. Формат Chrome extensions: domain/name/value/path/expirationDate/hostOnly/httpOnly/sameSite/secure/session/storeId.

Body:

ПолеТипОпис
idintОдин account.id
ids[int]Bulk ids
accountIdUUIDОдин UUID
accountIds[UUID]Bulk UUID
pathstringФайл або папка
  • Single без path → cookies у data.cookies.
  • Single + path на .json → один файл.
  • Bulk або path без .json → папка; файли cookie_{accountName}_{ts}.json.
  • Відповідь: { status:"success", data:{ id?, accountName?, count?, cookies?, path?, exported?, errors? } }.

Потрібна розблокована сесія: cookie key береться з vault. Інакше 500 Cookie key not available - decrypt keys first.

Account vars - змінні акаунта#

Змінні доступні в RPA як ${key}:

  • plain = account.settings, JSON без шифрування.
  • encrypted = account_data_blob, sealed-box; потребує master-password, executor розшифровує перед запуском.

GET /api/accounts/vars?accountId=N (або ?accountUuid=UUID)
Повертає { accountId, plain:{...}, encrypted:{...} }.

POST /api/accounts/vars/set
Один ключ. Body { accountId | accountUuid, key, value, encrypted?:bool }; encrypted=false пише в account.settings і реєструє ключ у catalog; encrypted=true decrypt → merge → encrypt. value може бути string/number/bool/object/array.

POST /api/accounts/vars/delete
Видаляє один ключ. Body { accountId | accountUuid, key, encrypted?:bool }; відповідь містить removed.

Proxies - проксі#

POST /api/proxies/check
Перевіряє проксі акаунтів тим самим checker (ipapicom/ipinfoio), для socks5 додатково UDP. У разі успіху оновлює proxy (visible_ip, country, timezone, UDP, active) і per-account proxy_usage; враховує country-block.

Body (фільтри AND):

ПолеТипОпис
accountIds[int]account.id
groupIdintАкаунти групи
tagIdintАкаунти з тегом
(нічого)-Усі non-deleted з proxy

Відповідь: { message, checked, results:[{ accountId, accountName, accountUuid, proxyId, host, port, type, result }] }; result.status = success / error / no_proxy.

POST /api/proxies/check-all
Перевіряє всі записи proxy; оновлює proxy і proxy_usage усіх акаунтів, які використовують кожну проксі. Відповідь: { message, checked, results:[...] }.

POST /api/proxies/add
Додає proxy після прогрівальної перевірки; при fail не зберігає. Body { host, port, type?, username?, password?, remark?, changeIpUrl? }, type default http. Відповідь success { added:true, proxyId, result } або fail { added:false, result:{status:"error",message} }.

Databases - підключення до БД#

Підключення для RPA-блока database; таблиця connections.

GET /api/databases/list
Список БД: { message, count, databases:[{ id, name, type, filePath?|host?|port?|user?|database?|ssl? }] }.

GET /api/databases/get?id=N
Одна БД: { message, database:{...} }.

POST /api/databases/create

Body:

ПолеТипОпис
namestringНазва
typesqlite/postgres/mysql/mssql/mongodb/redisDefault sqlite
filePathstring.db для sqlite
host/port/user/password/database-Мережеві БД
sslboolTLS
uristringConnection URI override
folderIdint/nullUI-папка
isCreateFileboolДля sqlite створити .db у <dataDir>/databases/

Успіх: { "status":"success", "data": { "id":3, "name":"scratch", "type":"sqlite", "filePath":"..." } }.

POST /api/databases/update
PATCH за id; поля create + isFavorite, isDeleted, tagIds + selection.

POST /api/databases/delete
Soft-delete. Body { id } або { ids:[...] }.

POST /api/databases/hard-delete
Видаляє connections, connections_tags_tag і файл із диска, якщо є filePath. Body { id } або { ids:[...] }.

Global vars - глобальні змінні#

Name/value з Налаштування → Змінні середовища, доступні в RPA як ${name}; таблиця settings.

GET /api/global-vars/list
Відповідь: { message, count, vars:[{ id, name, value, enable, isRestricted, owner }] }.

POST /api/global-vars/create
Body { name, value }; обидва мають бути унікальні. Дублікати: setting.error.duplicate_name / setting.error.duplicate_value.

POST /api/global-vars/update
Body { id, name?, value? }; ті самі правила унікальності.

POST /api/global-vars/delete
Body { id } / { ids:[...] } / { settingIds:[...] }.

Key catalog - каталог ключів#

key_entity зберігає імена ключів з account.settings і account_data_blob; значення лежать у /api/accounts/vars.

GET /api/keys/list
Відповідь: { message, count, keys:[{ id, key, createdAt }] }.

POST /api/keys/delete
Видаляє лише реєстр імен, не значення акаунтів. Body { ids:[...] } або { globalKeyIds:[...] }.

Scripts - RPA-скрипти#

GET /api/scripts/list
Усі non-deleted скрипти з деревом settings і form. Відповідь: { message, count, scripts:[{ id, name, hash, form, settings, isFavorite }] }. form = поля input/select/checkbox, що передаються в задачі через additionalData.

GET /api/scripts/get?id=N
Повна структура одного скрипта: { message, script:{ id, name, settings, form } }.

POST /api/scripts/create
Створює скрипт. Body { name, settings, form?, tab?, browser?, headlessMode?, noBrowser?, extraArgs? }.

Критичний формат settings:

  • elements[]: { id, start, type, left, top, label:"", hash:"", note:"", settings:{} }; left/top на елементі, не position.
  • Рівно один start:true; settings.startElement = його id.
  • connections[]: { sourceId, targetId, sourcePosition:"bottom|right|left", targetPosition:"top|left|right" }; targetPosition обов'язковий.
  • visualGroups: [] опційно.

Успіх: { status:"success", data:{ id, hash, name, settings } }.

POST /api/scripts/update
PATCH за id: name, settings (повна заміна), form, tab, browser, headlessMode, noBrowser, isFavorite, folderId, tagIds, extraArgs.

POST /api/scripts/run
Прямий запуск скрипта на профілі без task-group/tasks. Body { profileId:"<UUID>", scriptId:<numeric|hash>, closeBrowserAfter?:bool }; відповідь { status:"success", uuid:"<task-uuid>" }.

GET /api/scripts/run-logs?uuid=UUID (або ?taskUuid=UUID)
Лог прямого запуску (text/plain), аналог /api/tasks/logs для /api/scripts/run.

POST /api/scripts/stop
Зупинити running script за task uuid. Body { uuid:"<task-uuid>" } або { taskUuid }; executor переривається на найближчому await.

Modules - RPA-модулі (executeModule)#

Модуль = JS-код для блока executeModule: ряд module + папка (index.js, utils_<id>.js, package.json, settings.json).

Workflow: POST /api/modules/create → редагувати файли в moduleDirAbs (index.js без IPC-блока process.on('message',...) і process.send({status:'ready'}), settings.json, package.json) → POST /api/modules/resign. Без свіжого Ed25519-підпису executor поверне modules.error.signature_invalid.

GET /api/modules/list
Відповідь: { message, count, modules:[{ id, hash, name, sig, moduleDir, moduleDirAbs }] }.

GET /api/modules/get?id=N (або ?hash=UUID)
Повертає ряд + moduleDirAbs + top-level files.

POST /api/modules/create

Body:

ПолеТипОпис
namestringrequired
hashstringФорсувати UUID
codestringUI-мірор index.js
settingsobject{ type:"module", fields:[{name,label,type:"text"|"number"|"checkbox"|"select",default,options?,groupId?}] }
folderIdint/nullПапка
tagIds[int]Теги
allowedFunctions[string]Node API whitelist
useCustomFolderboolВикористати customFolder
customFolderstringНаявна папка

Успіх: { status:"success", data:{ id, hash, moduleDir, moduleDirAbs } }.

Після create сервер у фоні запускає npm install і підписує модуль. Після редагування файлів завжди викликайте /api/modules/resign.

POST /api/modules/update
PATCH рядка БД, не файлів: id, name, code, moduleDir, warnReason, settings, allowedFunctions, hashes, warningFindings, flags isFavorite/isDirty/isMigrated/isWarn/requiresReview/isDeleted, folderId, tagIds + selection.

POST /api/modules/resign
Перерахувати підпис папки. Body { id }; успіх { status:"success", sig:"base64-ed25519-signature..." }.

POST /api/modules/delete
Soft-delete. Body { id } або { ids:[...] }; файли залишаються.

POST /api/modules/hard-delete
Видаляє ряд module, module_tags_tag і папку. Body { id } або { ids:[...] }.

Task Groups - групи задач#

Група задач = контейнер розкладу/повторів/таймауту/паралельності. active=1 запускає scheduler.

ПолеТипОпис
scheduleboolВікно timeFrom/timeTo
timeFrom / timeTostring HH:MMПотребує schedule=true
scheduleTimeboolВікно startHour/endHour
startHour / endHourint 0-23Потребує scheduleTime=true
isRepeatableboolПовторювати групу
repeatCountintКількість повторів
timeoutint sec0 = немає
activeSessionintПаралельність; 0 = unlimited
waitForOtherTaskCompletionboolЧекати інші групи
folderIdint/nullUI-папка

GET /api/task-groups/list
Відповідь: { message, count, groups:[{ id, tag, active, schedule, timeFrom, timeTo, isRepeatable, timeout, activeSession }] }.

GET /api/task-groups/get?id=N
Група + задачі: { message, group:{...}, tasks:[{ id, uuid, scriptId, accountId, status, executeAt, additionalData }], tasksCount }.

GET /api/task-groups/tasks?groupId=N
Лише задачі групи. Статуси: waiting, working, finished, error, stop, stopWithError.

POST /api/task-groups/create
Створює порожню групу. Body { tag?:string, name?:string, active?:bool, ...scheduleFields }. Рекомендовано: active:false, потім створити задачі й викликати start.

POST /api/task-groups/update
PATCH за id: schedule-поля, tag, active, isFavorite, isDeleted, isRescheduled.

POST /api/task-groups/start
Body { id } або { groupId }. Ставить active=1; scheduler підхоплює waiting-задачі. Ідемпотентно, завершені не ресетить.

POST /api/task-groups/restart
Body { id }. Ставить active=1 і переводить finished/error/stop/stopWithError назад у waiting з executeAt=now().

POST /api/task-groups/stop
Body { id }. Ставить active=0, переводить working у stopWithError; браузери не закриває.

POST /api/task-groups/delete або DELETE /api/task-groups/delete?id=N
Soft-delete групи та її задач (isDeleted=1, UI deleteGroup). POST body { id }.

POST /api/task-groups/hard-delete
Видаляє задачі групи й ряд групи. Body { id } або { ids:[...] }; відповідь містить deletedGroups, deletedTasks, ids.

Tasks - задачі#

Задача = scriptId × accountId з executeAt, status, additionalData, sort.

POST /api/tasks/create
Створює одну/кілька задач у групі однією транзакцією. Body { groupId:int, tasks:[...] }; відповідь { message, created, requested, errors }.

Поле task:

ПолеТипОпис
accountIdintrequired, account.id
scriptIdint/stringrequired
additionalDataobjectform-поля скрипта; інакше defaultValue
executeAtISO stringdefault now
tagstringМітка
sortintПорядок

additionalData критично важливий, якщо скрипт має form-поля.

POST /api/tasks/update
PATCH за id або taskId: status, tag, description, executeAt, sort, additionalData.

GET /api/tasks/list
Плоский список задач; фільтри AND.

Query params:

ПараметрТипОпис
statusstringCSV: working,waiting,finished,error,stop,stopWithError
groupIdintГрупа
accountIdintaccount.id
scriptIdint/stringСкрипт
limitintDefault 500, max 5000

Відповідь: { message, count, tasks:[{ id, uuid, status, groupId, accountId, scriptId, executeAt }] }.

GET /api/tasks/active
Усі working задачі з account:{id,name,accountId} і script:{id,name}.

POST /api/tasks/delete
Безповоротно видаляє задачі. Body { id } або { ids:[...] }.

POST /api/tasks/stop
Зупиняє working/waiting: status → stop + abort executor за uuid; closeBrowser:true додатково закриває браузер акаунта. Body { id } / { ids:[...] } / [{ id, uuid? }, ...], плюс closeBrowser?:bool.

Logs#

GET /api/tasks/logs?taskUuid=UUID (або ?taskId=UUID)
Текстовий лог за task.uuid (не numeric task.id), Content-Type: text/plain; charset=utf-8; можна читати під час виконання. 404, якщо файл ще не створено або видалено.

GET /api/scripts/run-logs?uuid=UUID
Те саме для прямих запусків через /api/scripts/run.

Emails - IMAP-облікові дані#

GET /api/emails/list
Список IMAP-облікових даних; паролі не повертаються. Відповідь: { message, count, emails:[{ id, email, imapServer, port, isActive, mailboxes }] }.

POST /api/emails/toggle
Вмикає/вимикає IMAP-моніторинг: оновлює isActive і синхронно відкриває/закриває з'єднання. Body { email:"user@gmail.com", isActive:true }.

Error format#

Помилка: HTTP 4xx/5xx + { "error": "Описание ошибки" }.

  • 400 - required-поля / JSON.
  • 401 - неправильний/відсутній x-api-key (з'єднання закривається).
  • 404 - ресурс не знайдено.
  • 500 - DB/внутрішня помилка.
  • Деякі бізнес-помилки йдуть HTTP 200 з { status:"error", code:"<i18n-key>", message:"..." }, наприклад duplicate global vars.

Повний приклад: запустити скрипт на акаунтах за розкладом#

Сценарій: MintNFT (scriptId=12) з walletPassword, mintCount на акаунтах 42/43/44, вікно 08:00-20:00, repeat 2, паралельність 5, старт 2026-05-11T09:00:00.000Z.

  1. Знайти IDs: GET /api/scripts/list; GET /api/profiles/list?groupId=5.
  2. Перевірити/прогріти проксі: POST /api/proxies/check, body { accountIds:[42,43,44] }.
  3. Створити групу: POST /api/task-groups/create, body { tag:"MintNFT run", active:false, schedule:true, timeFrom:"08:00", timeTo:"20:00", isRepeatable:true, repeatCount:2, activeSession:5 }; зберегти group.id.
  4. Створити задачі: POST /api/tasks/create, body { groupId:7, tasks:[{accountId:42,scriptId:12,additionalData:{walletPassword:"pwd1",mintCount:"1"},executeAt:"2026-05-11T09:00:00.000Z"}, ...] }.
  5. Активувати: POST /api/task-groups/start, body { id:7 }.
  6. Моніторити: GET /api/task-groups/get?id=7, потім GET /api/tasks/logs?taskUuid=<uuid>.
  7. Керування: GET /api/tasks/active; POST /api/tasks/stop with { ids:[100], closeBrowser:true }; POST /api/task-groups/restart; POST /api/task-groups/hard-delete.

Пряме підключення до браузера через CDP#

Після POST /api/profiles/start використовуйте wsEndpoint.

js

Python pyppeteer: browser = await connect(browserWSEndpoint=ws_endpoint). Без CDP-клієнта використовуйте POST /api/profiles/eval і POST /api/profiles/screenshot.

OAuth callback#

GET / або GET /oauth/callback?code=AUTH_CODE не потребує api-key; використовується для Google Drive OAuth flow, передає code у внутрішній канал і закриває вікно.

Source of truth#

Реалізація: src-tauri/src/browser/server.rs. Якщо поведінка endpoint-а відрізняється від документації, source code is source of truth.

Пов'язані терміни глосарія