Afina HTTP API#
Afina HTTP API - это мост между антидетект-браузером Afina и вашим собственным кодом. Все, что вы обычно делаете вручную в интерфейсе - создаете профили, запускаете браузеры, гоняете RPA-скрипты, переключаете прокси, - можно делать обычными HTTP-запросами. Удобнее всего это тогда, когда Afina нужно подключить к CRM, AI-агенту или CI-пайплайну и больше не возвращаться к ручной работе.
- Base URL:
http://127.0.0.1:50778(default port50778; если он занят, сервер берет следующий свободный). - Формат: все ответы
application/json, кроме/api/tasks/logsи/api/scripts/run-logs(text/plain). - Покрытие: задокументированы все ~67 HTTP-эндпоинтов, разбитые на 11 логических модулей.
Любой язык программирования#
API - это обычные HTTP-запросы, поэтому обращаться к нему можно из любого языка программирования. Вам не нужны специальные SDK или библиотеки Afina: достаточно открыть любимую IDE и написать короткий скрипт на Python, JavaScript, Go, PHP или чем угодно, что умеет делать HTTP-вызовы. Ниже - максимально лаконичный пример на Python (библиотека requests), который получает список профилей:
Этого достаточно, чтобы убедиться, что сервер живой и отвечает. Дальше вы просто меняете URL эндпоинта и метод (GET/POST/DELETE), а остальная часть справочника описывает, какие именно запросы доступны.
Как работает API#
Внутри Afina живут две части, которые работают бок о бок. Одна - привычное десктопное приложение с кнопками, по которым вы кликаете мышью. Вторая - небольшой локальный веб-сервер, который тихо стартует вместе с приложением и слушает порт 50778 на вашей же машине (127.0.0.1). Именно к нему и ходит HTTP API.
Несколько вещей, которые стоит держать в голове:
- API живет локально. Сервер привязан к
127.0.0.1, поэтому принимает запросы только с вашего компьютера. Никакого облака: наружу он не светится, а пока приложение Afina закрыто, API просто недоступно. Если нужно ходить с другой машины в локальной сети, это настраивается отдельно, но по умолчанию все сидит на localhost ради безопасности. - Приложение - это "мозг", API - это "руки". Отправляете
POST /api/profiles/start- и сервер не действует сам по себе: он просит десктопный движок Afina поднять настоящий браузер с нужным отпечатком и прокси, а вам отдаетwsEndpoint, прямой адрес для подключения через CDP (Chrome DevTools Protocol). По сути API - это тонкий слой управления над тем же движком, который крутит и GUI. - Одно состояние на двоих. API и интерфейс смотрят в одну базу данных (SQLite). Создали профиль через API - он в тот же момент появится в списке в приложении, и наоборот. После каждой записи, которая меняет данные, сервер шлет событие
reload-event, чтобы GUI сразу подхватил изменения. - Предсказуемые ответы. Почти все возвращается в JSON с понятной структурой (
status,data,message,error). Отдельный случай - логи: их отдают простым текстом, чтобы удобно было стримить прямо в консоль.
Как пользоваться API#
Вся работа с API держится на трех шагах: взять ключ -> отправить с ним запрос -> прочитать JSON-ответ. Разберем каждый.
1. Авторизация по ключу. Поскольку сервер имеет доступ к вашим профилям, прокси и cookies, каждый запрос к /api/* должен содержать заголовок x-api-key. Ключ вы берете один раз в приложении: Настройки -> Основные настройки Afina -> API ключ. Думайте о нем как о пароле: не зашивайте его в публичные репозитории, держите в переменных окружения или секрет-менеджере. Если ключ неверный или отсутствует, сервер ответит 401 и просто закроет соединение. Исключения, которым ключ не нужен: GET /api/health, GET / и GET /oauth/callback.
2. Типовые сценарии использования. API создано для того, чтобы встроить Afina в ваши собственные процессы. Чаще всего его используют, чтобы:
- Интегрировать Afina с CRM или внутренними системами - например, автоматически заводить новый браузерный профиль под каждого клиента или аккаунт.
- Строить AI-агентов, которые самостоятельно открывают страницы, читают их (
/api/profiles/eval), делают скриншоты и принимают решения. - Массово создавать профили с уникальными, реалистичными отпечатками - десятками и сотнями, не кликая мышью.
- Запускать RPA-скрипты по расписанию на группах аккаунтов (минты, прогревы, рутинные действия) и мониторить их выполнение через логи.
- Управлять прокси и cookies программно - проверять, добавлять, экспортировать/импортировать сессии между профилями.
3. Первый запрос. Самый быстрый способ убедиться, что все живет, - попросить у сервера список профилей:
Пришел JSON со списком аккаунтов - все, вы на связи. Дальше логика почти всегда та же: сначала узнаете нужные id (через */list), потом выполняете действие (create, start, run...), а за долгими операциями вроде задач или скриптов следите через их логи. Ниже - полный справочник, разложенный по 11 тематическим модулям.
Аутентификация#
Все /api/* требуют заголовок x-api-key (Настройки -> Основные -> API ключ). Без ключа или с неверным ключом - 401 Unauthorized.
Без ключа доступны только GET /api/health, GET /, GET /oauth/callback. CORS - только localhost-origin (http://localhost, http://127.0.0.1, tauri://); методы GET, POST, DELETE, OPTIONS; headers Content-Type, X-API-Key.
Конвенции#
Несколько сквозных правил работают одинаково во всех модулях. Прочитайте один раз, и дальше будет проще:
id= числовой PK (SQLite);accountId= UUID профиля. Большинство write-эндпоинтов принимают оба варианта - используйте тот, который удобнее в вашем коде.- Boolean принимается как
true/falseили0/1; datetime - в формате ISO-8601 (defaultdatetime('now')). */delete= soft-delete (isDeleted=1, объект в Корзине, можно восстановить);*/hard-delete= физическое удаление из БД/диска без возврата;*/update= PATCH только переданных полей (остальное остается как было).- Обозначения обязательности в таблицах: ✅ - обязательный, ⬜ - опциональный.
Health#
GET /api/health - самый простой "пинг" сервера, не требует API key. Удобно использовать в скриптах как проверку "живое ли приложение" перед серией запросов. Ответ: { "status": "ok", "running": 2 }, где running = количество сейчас запущенных браузеров.
Модуль 1. Профили / Аккаунты#
Профили (аккаунты) - это фундамент всей работы в Afina. Каждый профиль - отдельный изолированный браузер со своим уникальным отпечатком (fingerprint), собственными cookies, прокси и настройками. Благодаря этому каждый сайт видит профиль как отдельного живого пользователя с другого устройства.
Это ваш главный инструмент масштабирования. Здесь вы программно собираете десятки уникальных отпечатков, обновляете и удаляете их, а заодно управляете жизнью самого браузера: запускаете, останавливаете, создаете одноразовые (disposable) профили под разовые задачи. Только начинаете знакомство с API? Начинайте отсюда.
GET /api/profiles/list - список аккаунтов; к каждому добавляются isRunning, tags, groups. Query-фильтры (AND, опциональные):
| Параметр | Тип | Обяз. | Описание |
|---|---|---|---|
isRunning | bool | ⬜ | Запущенные/остановленные |
groupId | int | ⬜ | Фильтр по группе |
tagId | int | ⬜ | Фильтр по тегу |
GET|POST /api/profiles/get - один аккаунт. Query/body { accountId }. -> { message, profile:{...} }.
POST /api/profiles/create - создает профиль. Fingerprint: не передан -> генерируется; полный -> нормализуется; частичный -> база + merge (incoming-wins).
| Поле | Тип | Обяз. | Описание |
|---|---|---|---|
name | string | ⬜ | Название |
accountId | string | ⬜ | Форсировать UUID |
os | macos/windows10/windows11 | ⬜ | Генерация FP (default macos) |
osArch | arm64/x86 | ⬜ | Архитектура macOS (default arm64) |
chip | m1-m4/intel | ⬜ | Mac-чип |
browserType | string | ⬜ | afina(деф.)/mimic/octo/vision/ads/dolphin |
proxyId | int | ⬜ | Saved proxy (взаимоискл. с proxyData) |
proxyType | saved/set/without_proxy | ⬜ | Тип прокси |
proxyData | object | ⬜ | Inline: host,port,type обязательны; опц. username,password,changeIpUrl,country... |
tagIds/tagNames | array | ⬜ | Теги по id/name (новые создаются) |
accountGroupIds/accountGroupNames | array | ⬜ | Группы по id/name |
language/timezone | string | ⬜ | Если *_from_ip=false |
languages | array | ⬜ | Override языков |
timezone_from_ip/language_from_ip/languages_from_ip | bool | ⬜ | Авто по IP (деф. true) |
screenSize | string | ⬜ | 1920x1080; avail авто |
availWidth/availHeight/colorDepth/pixelDepth | int | ⬜ | Экран |
blockedPorts | [int] | ⬜ | Защита портов |
blockOnProxyCountryChange | bool/null | ⬜ | null=global, true=block, false=allow |
localCacheMode | default/no_cache | ⬜ | no_cache -> --disk-cache-size=0 |
startupUrls | [string] | ⬜ | URL первого запуска |
extraArgs | string | ⬜ | Chromium CLI args |
settings | object | ⬜ | Per-account KV (${key}) |
isNoiseCanvas/Audio/Rects/GLEnabled | bool | ⬜ | FP-шумы |
fingerprint | object | ⬜ | Полный/частичный FP |
note/teamUuid | string | ⬜ | Заметка / команда лицензии |
FP-нюансы. Всегда игнорируются (берутся из build):
userAgent,BrandFullVersion,deviceMemory(дляua<147максимум 8).uaберется из{data_dir}/browser/UA*. Алиасы нормализуются:hardwareConcurrency->CPUcores,webGLRenderer->WebGLRenderer,uaPlatformVersion->platformVersion,device_memory->deviceMemoryи т.д.macChipопределяется изWebGLRenderer(Apple M2->m2).
Успех: { "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 }. -> { "message":"Account successfully deleted" }.
POST /api/profiles/hard-delete - безвозвратно (account/profile/junction/proxy_usage/файлы + DELETE /profiles/:uuid на сервере; закрывает браузер). Body { id }/{ ids:[...] } или { accountId }/{ accountIds:[...] }. -> { "status":"success","deleted":3 }.
POST /api/profiles/start - запускает браузер. Body { "profileId":"<UUID>" }. -> содержит wsEndpoint и data.port (для Puppeteer/Playwright/CDP); если уже запущен - alreadyRunning:true.
POST /api/profiles/stop - закрывает браузер (CDP Browser.close -> graceful kill). Body { "profileId":"<UUID>" }. 404, если не запущен.
POST /api/profiles/one-time - disposable-профиль: создает + сразу запускает + hard-delete после остановки. Идеально для разовых проверок или scrape-задач, после которых не хочется оставлять "мусор". Body = поля create; name default one-time-<ts>. -> { id, accountId, wsEndpoint, port, isOneTime:true }.
isOneTimeне выставляется черезcreate/update; hard-delete в exit-handler срабатывает только по SQL-проверкеaccount.isOneTime=1. Темп: ~1 вызов/сек.
Модуль 2. Переменные аккаунтов и каталог ключей#
Вместе с профилем часто приходится хранить сопутствующие данные: пароль от кошелька, токен, номер карты, логин на сайте. Этот модуль позволяет "прикрепить" к аккаунту набор переменных, а потом доставать их прямо из RPA-скрипта через ${key} - и не зашивать секреты в сам код.
Хранилищ для этого два. plain - обычный JSON (account.settings) для несекретных значений. encrypted - защищенное хранилище (account_data_blob, sealed-box): оно требует введенного мастер-пароля и расшифровывается только в момент запуска скрипта. А каталог ключей (key_entity) - это даже не значения, а просто реестр имен всех ключей, которые когда-либо засветились, чтобы вы видели, какие переменные вообще есть в системе.
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 + регистрация в каталоге; true -> decrypt->merge->encrypt. value: string/number/bool/object/array.
POST /api/accounts/vars/delete - Body { accountId|accountUuid, key, encrypted?:bool }; ответ содержит removed.
GET /api/keys/list - каталог имен ключей. -> { message, count, keys:[{ id, key, createdAt }] }.
POST /api/keys/delete - удаляет только реестр имен (не значения аккаунтов). Body { ids:[...] } или { globalKeyIds:[...] }.
Модуль 3. Скрипты (RPA)#
RPA-скрипты - это сердце автоматизации Afina. Скрипт - визуальная последовательность блоков (открыть страницу, кликнуть, ввести текст, проверить условие), которую движок выполняет внутри профиля вместо вас. Через этот модуль вы достаете готовые скрипты, создаете новые программно и запускаете их.
Для разработчика здесь две скорости. Прямой запуск (/api/scripts/run) стартует скрипт на одном профиле немедленно, без лишней бюрократии с задачами, - то, что нужно для разработки и отладки. А когда скрипт пора прогнать на сотни аккаунтов по расписанию, в игру вступают полноценные задачи и группы из Модуля 7. И не забывайте про поле form: если скрипт ждет входные данные, вы передаете их через additionalData.
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) |
startElement | id единственного элемента с start:true |
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). Body { profileId:"<UUID>", scriptId:<numeric|hash>, closeBrowserAfter?:bool }. -> { status:"success", uuid:"<task-uuid>" }.
GET /api/scripts/run-logs?uuid=UUID (или ?taskUuid=UUID) - лог прямого запуска (text/plain).
POST /api/scripts/stop - остановить running script. Body { uuid } или { taskUuid }; executor прерывается на ближайшем await.
Модуль 4. Модули (RPA modules)#
Когда встроенных RPA-блоков уже не хватает, в игру заходят модули - ваш собственный JavaScript-код, который вы цепляете к скрипту блоком executeModule. Фактически это способ дотянуть Afina до чего угодно: хитрая логика, работа с файлами, звонки во внешние API через Node.js.
Работа с модулем - короткий цикл, который стоит запомнить: создать (create, сервер сам сгенерирует папку со скелетом и сделает npm install) -> отредактировать файлы на диске в moduleDirAbs (index.js, settings.json) -> переподписать (resign). На последнем шаге споткнуться проще всего. Из соображений безопасности Afina запускает только модули с валидной Ed25519-подписью, поэтому если забыть сделать resign после правки файлов, 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:
| Поле | Тип | Обяз. | Описание |
|---|---|---|---|
name | string | ✅ | Название |
hash | string | ⬜ | Форсировать UUID |
code | string | ⬜ | UI-миррор index.js |
settings | object | ⬜ | { type:"module", fields:[{name,label,type,default,options?,groupId?}] } |
folderId | int/null | ⬜ | Папка |
tagIds | [int] | ⬜ | Теги |
allowedFunctions | [string] | ⬜ | Node API whitelist |
useCustomFolder/customFolder | bool/string | ⬜ | Использовать существующую папку |
Успех: { status:"success", data:{ id, hash, moduleDir, moduleDirAbs } }. После create сервер в фоне делает npm install + подпись.
POST /api/modules/update - PATCH строки БД (не файлов): id, name, code, moduleDir, settings, allowedFunctions, hashes, warnReason, warningFindings, flags isFavorite/isDirty/isMigrated/isWarn/requiresReview/isDeleted, folderId, tagIds + selection.
POST /api/modules/resign - пересчитать подпись папки. Body { id }. -> { status:"success", sig:"<base64-ed25519>" }.
POST /api/modules/delete - soft-delete (файлы остаются). Body { id } или { ids:[...] }.
POST /api/modules/hard-delete - удаляет строку module, module_tags_tag и папку. Body { id } или { ids:[...] }.
Модуль 5. Базы данных#
RPA-скрипты редко живут в вакууме: им нужно откуда-то брать данные (логины, ссылки, тексты) и куда-то складывать результаты. Этот модуль подключает к Afina внешние базы данных, чтобы потом обращаться к ним прямо из скрипта через RPA-блок database.
Поддерживаются и легкие файловые БД (SQLite - Afina может даже создать .db-файл за вас), и взрослые сетевые серверы (PostgreSQL, MySQL, MSSQL, MongoDB, Redis). Здесь вы управляете только подключениями (строка в таблице 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:
| Поле | Тип | Обяз. | Описание |
|---|---|---|---|
name | string | ✅ | Название |
type | sqlite/postgres/mysql/mssql/mongodb/redis | ⬜ | Default sqlite |
filePath | string | ⬜ | .db для sqlite |
host/port/user/password/database | mixed | ⬜ | Сетевые БД |
ssl | bool | ⬜ | TLS |
uri | string | ⬜ | Connection URI override |
folderId | int/null | ⬜ | UI-папка |
isCreateFile | bool | ⬜ | 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:[...] }.
Модуль 6. Глобальные переменные#
Бывает, одно значение нужно сразу куче скриптов: базовый URL вашего API, общий ключ сервиса, название кампании. Вместо того чтобы копировать его туда-сюда, вынесите в глобальную переменную и доставайте из любого скрипта через ${name}.
В отличие от переменных аккаунта (Модуль 2), которые привязаны к конкретному профилю, глобальные переменные общие для всего рабочего пространства (таблица settings, раздел Настройки -> Переменные окружения). Один нюанс держите в голове: и name, и value должны быть уникальными, дубликат сервер создать не даст.
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/duplicate_value).
POST /api/global-vars/update - Body { id, name?, value? } (те же правила уникальности).
POST /api/global-vars/delete - Body { id } / { ids:[...] } / { settingIds:[...] }.
Модуль 7. Задачи и группы задач#
Это модуль для серьезной, масштабной автоматизации. Когда один скрипт нужно прогнать на сотни аккаунтов - по расписанию, с повторами, лимитом параллельности и контролем времени, - на сцену выходят задачи и группы задач.
Логика простая и иерархичная. Группа - это контейнер с правилами: когда запускать (окно времени), сколько раз повторить, сколько браузеров держать одновременно. Задача внутри группы - конкретная пара "этот скрипт x этот аккаунт" со своим временем старта и входными данными. Рабочий цикл рекомендую такой: создать группу с active:false -> насыпать в нее задач -> вызвать start. Так планировщик не схватит пустую группу раньше времени. За ходом выполнения следите через логи и статусы (waiting/working/finished/error/stop/stopWithError).
Группы задач#
Группа = контейнер расписания/повторов/таймаута/параллельности; active=1 запускает scheduler. Поля расписания:
| Поле | Тип | Описание |
|---|---|---|
schedule + timeFrom/timeTo | bool + HH:MM | Окно времени суток |
scheduleTime + startHour/endHour | bool + int 0-23 | Окно по часам |
isRepeatable + repeatCount | bool + int | Повторы группы |
timeout | int sec | 0 = нет |
activeSession | int | Параллельность (0 = unlimited) |
waitForOtherTaskCompletion | bool | Ждать другие группы |
folderId | int/null | UI-папка |
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?, name?, active?, ...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|DELETE /api/task-groups/delete - soft-delete группы и ее задач. POST body { id } / DELETE ?id=N.
POST /api/task-groups/hard-delete - удаляет задачи группы и строку группы. Body { id } или { ids:[...] }; -> deletedGroups, deletedTasks, ids.
Задачи#
Задача = scriptId x accountId с executeAt, status, additionalData, sort.
POST /api/tasks/create - создает задачи в группе одной транзакцией. Body { groupId:int, tasks:[...] }. -> { message, created, requested, errors }. Поле task:
| Поле | Тип | Обяз. | Описание |
|---|---|---|---|
accountId | int | ✅ | account.id |
scriptId | int/string | ✅ | Скрипт |
additionalData | object | ⬜ | Form-поля скрипта (критично, если есть форма) |
executeAt | ISO | ⬜ | Default now |
tag | string | ⬜ | Метка |
sort | int | ⬜ | Порядок |
POST /api/tasks/update - PATCH по id/taskId: status, tag, description, executeAt, sort, additionalData.
GET /api/tasks/list - плоский список (фильтры AND). Query: status (CSV), groupId, accountId, scriptId, limit (деф. 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; closeBrowser:true дополнительно закрывает браузер. Body { id } / { ids:[...] } / [{ id, uuid? }] + closeBrowser?.
Логи#
GET /api/tasks/logs?taskUuid=UUID (или ?taskId=UUID) - текстовый лог по task.uuid (text/plain); читается во время выполнения. 404, если файл еще не создан. (Для прямых запусков - GET /api/scripts/run-logs, Модуль 3.)
Модуль 8. Прокси#
Именно прокси дает каждому профилю свой IP-адрес и географию. Без нормально настроенного прокси весь смысл антидетекта просто испаряется. Этот модуль добавляет новые прокси и массово проверяет те, что уже есть, перед важным запуском.
Одна полезная деталь: Afina не просто сохраняет прокси, а "прогревает" его - реально подключается, смотрит на видимый IP, страну, часовой пояс и (для socks5) поддержку UDP. Не ответил - даже не сохранится. Это спасает от классики, когда массовый запуск падает из-за одного мертвого прокси. Поэтому возьмите за привычку гонять check/check-all перед стартом большой группы задач.
POST /api/proxies/check - проверяет прокси аккаунтов (checker ipapicom/ipinfoio; для socks5 еще UDP); обновляет proxy и proxy_usage, учитывает country-block. Body (фильтры AND): accountIds:[int] / groupId / tagId / ничего = все 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_usage всех аккаунтов. -> { message, checked, results:[...] }.
POST /api/proxies/add - добавляет proxy после прогревочной проверки (при fail не сохраняет). Body { host, port, type?, username?, password?, remark?, changeIpUrl? } (type деф. http). -> { added:true, proxyId, result } или { added:false, result:{status:"error",message} }.
Модуль 9. Cookies и CDP-миграция#
Cookies - это сохраненные сессии: залогиненные аккаунты, корзины, настройки сайтов. Этот модуль позволяет программно "заливать" cookies в профиль и выгружать их наружу - например, перенести рабочую сессию из другого браузера или сделать бекап.
Главное здесь - понять момент применения. Импортированные cookies не вставляются мгновенно: они ложатся в очередь и инжектятся при следующем запуске браузера, поэтому во время cookies/set аккаунт должен быть остановлен. А если браузер уже работает и cookies нужно подложить "на горячую", идите через прямое подключение по CDP (wsEndpoint) - об этом чуть ниже. С экспортом тоже есть нюанс: cookies лежат в зашифрованном хранилище, поэтому понадобится разблокированная сессия (введенный мастер-пароль).
POST /api/profiles/cookies/set - кладет cookies в очередь {data_dir}/cookies/{uuid}/cookies_{ts}.json; инжектятся через CDP при следующем start. Body { accountId (int/UUID, alias id), cookies:[{domain,name,value,path?,expirationDate?,secure?,httpOnly?,sameSite?,...}] }. -> { "status":"success", "data":{ "count":1 } }.
Аккаунт должен быть остановлен. Для горячего ввода - CDP
Network.setCookiesчерез/api/profiles/eval.
POST /api/profiles/cookies/export - экспорт расшифрованных cookies (папка профиля -> .zip -> .afbk; Chrome-extension формат). Body:
| Поле | Тип | Описание |
|---|---|---|
id / ids | int / [int] | Один / bulk account.id |
accountId / accountIds | UUID / [UUID] | Один / bulk UUID |
path | string | Файл (.json) или папка |
Поведение: single без path -> data.cookies; single + .json -> один файл; bulk/папка -> cookie_{accountName}_{ts}.json. Нужна разблокированная сессия (иначе 500 Cookie key not available).
CDP-миграция (прямое подключение). Отдельного эндпоинта импорта через CDP HTTP API нет; миграцию делают так: POST /api/profiles/start -> взять wsEndpoint -> подключиться Puppeteer/Playwright:
Дальше Storage.getCookies / Network.setCookies через CDP, или /api/profiles/cookies/export + /api/profiles/cookies/set между аккаунтами.
Модуль 10. Взаимодействие с браузером#
Этот модуль превращает API в "глаза и руки" внутри запущенного браузера. Не обязательно писать полноценный RPA-скрипт: можно на лету выполнить произвольный JavaScript в текущей вкладке или сделать скриншот. Для AI-агентов и отладки это очень полезно.
Именно /api/profiles/eval делает API по-настоящему гибким. Через него вы читаете со страницы что угодно, кликаете элемент, заполняете форму, даже дергаете CDP-команды. А screenshot дает визуальный контекст - например, чтобы vision-модель буквально увидела страницу. Одно "но": браузер профиля должен быть запущен (/api/profiles/start), иначе в ответ прилетит 404.
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..." }.
Для поиска элементов/текста/URL (аналоги MCP
find_clickable,get_page_text,get_current_url) в HTTP API отдельных эндпоинтов нет - выполняйте соответствующий JS через/api/profiles/eval.
Модуль 11. Email (IMAP)#
Многие сценарии автоматизации рано или поздно упираются в почту: коды регистрации, письма верификации, OTP. Этот модуль управляет тем, какие почтовые ящики (через IMAP) Afina мониторит, чтобы скрипт мог сам дождаться нужного письма и прочитать его.
Из соображений безопасности пароли ящиков через API не возвращаются никогда. Вы видите только метаданные подключения и можете включать или выключать мониторинг.
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 }.
Формат ошибок#
Ошибки сервера прозрачные и предсказуемые: HTTP-статус 4xx/5xx плюс тело { "error": "Описание ошибки" }. По коду сразу видно, что именно пошло не так:
| Код | Причина |
|---|---|
400 | Отсутствуют required-поля / невалидный JSON |
401 | Неверный/отсутствующий x-api-key (соединение закрывается) |
404 | Ресурс не найден / браузер не запущен |
500 | DB / внутренняя ошибка |
И один коварный момент, который легко пропустить: часть бизнес-ошибок прилетает с HTTP 200, но с телом { status:"error", code:"<i18n-key>", message:"..." } - например, попытка завести дубликат глобальной переменной. Поэтому в серьезном коде смотрите не только на HTTP-статус, но и на поле status внутри JSON.
Полный пример (запуск по расписанию)#
Чтобы все выше сложилось в одну картину, пройдем сквозной сценарий от первого запроса до мониторинга. Задача: запустить скрипт MintNFT (scriptId=12) с полями walletPassword/mintCount на аккаунтах 42/43/44, в окне времени 08:00-20:00, с повтором 2, параллельностью 5, начиная с 2026-05-11T09:00:00.000Z.
Мониторинг: GET /api/task-groups/get?id=7 -> GET /api/tasks/logs?taskUuid=<uuid>. Управление: GET /api/tasks/active; POST /api/tasks/stop {"ids":[100],"closeBrowser":true}; POST /api/task-groups/restart; POST /api/task-groups/hard-delete.