API — Лицензия, доступ и сертификат

Проверяйте статус лицензии, управляйте аутентификацией, читайте общий статус системы и скачивайте корневой сертификат (CA) через API.

Лицензия

GET/api/v1/license/statusТребуется авторизация

Возвращает информацию о том, активна ли лицензия, вместе с вашим тарифом, правом на пул портов (pool) и лимитами процессов Challenge Breaker. Лимита портов здесь нет: действующий потолок отдаёт поле max_ports у GET /api/v1/status.

Пример (curl)
curl -H "X-API-Key: YOUR_API_KEY" http://127.0.0.1:8891/api/v1/license/status
Ответ
{
  "activated": true,
  "email": "demo@blanktrail.pro",
  "plan": "pro",
  "period_end": "2027-02-15T00:00:00Z",
  "pool": true,
  "js_solver_max_procs": 4,
  "js_solver_procs": 4,
  "js_solver_live_procs": 1
}
  • Поля label и allowed_domains приходят ТОЛЬКО у сервисного (промо) тарифа, поэтому в примере выше их нет: label — приставка к названию тарифа, дашборд показывает её как «Pro (WB)»; allowed_domains — список доменов, которыми этот тариф ограничен, и панель выводит под названием тарифа строку «Ограничено доменами: …».
  • 🔴 allowed_domains — не справка, а запрет: пока список непустой, порт пускает ТОЛЬКО на эти домены. Запись без префикса покрывает сам домен и все его поддомены, запись с «=» в начале — только точное имя, голый IP-адрес не совпадает НИКОГДА. Пустой список (поля нет) — ограничения нет.
  • 🔴 Отказ приходит с САМОГО прокси-порта, а не из этого API: HTTP CONNECT и обычный HTTP получают 403 Forbidden с пустым телом — без JSON и без поля error; SOCKS5 — код ответа 0x02 «connection not allowed by ruleset»; UDP ASSOCIATE под таким тарифом не выдаётся вовсе. Вышестоящий прокси тут ни при чём — менять выход бесполезно, сверьте адрес со списком.
  • Challenge Breaker на домен вне списка не запускается. Ручки API, проверка порта и остальной дашборд работают как обычно.

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

Пароль здесь — от дашборда, а не от API: запросы к API удостоверяются ключом в заголовке X-API-Key, и cookie сессии им не нужна. Эти ручки нужны интерфейсу и сценариям первоначальной настройки.

POST/api/v1/auth/loginБез авторизации

Вход в дашборд паролем; ставит cookie сессии.

ПараметрТипОбязательныйОписание
passwordstringДаПароль дашборда.
Тело запроса
{ "password": "your-dashboard-password" }
Пример (curl)
curl -X POST -H "Content-Type: application/json" -c cookies.txt \
  -d '{"password":"your-dashboard-password"}' \
  http://127.0.0.1:8891/api/v1/auth/login
Ответ
{
  "status": "ok"
}
  • 401 «invalid password»; 429 «too many attempts — try again later» после череды неудач с одного адреса.
  • 501 «auth not configured», если вход в дашборд в этой сборке не настроен.
POST/api/v1/auth/logoutТребуется авторизация

Очищает cookie сессии дашборда.

Пример (curl)
curl -X POST -H "X-API-Key: YOUR_API_KEY" \
  http://127.0.0.1:8891/api/v1/auth/logout
Ответ
{
  "status": "ok"
}
GET/api/v1/auth/statusТребуется авторизация

Состояние входа и пароля: вошли ли, задан ли пароль и остался ли он первоначальным.

Пример (curl)
curl -H "X-API-Key: YOUR_API_KEY" \
  http://127.0.0.1:8891/api/v1/auth/status
Ответ
{
  "authenticated": true,
  "password_is_initial": false,
  "password_set": true
}
  • password_is_initial=true — пароль ещё тот, что выдан при установке; это повод показать требование его сменить.
POST/api/v1/auth/passwordТребуется авторизация

Меняет пароль дашборда. Нужен текущий пароль.

ПараметрТипОбязательныйОписание
current_passwordstringДаТекущий пароль. 🔴 Поле называется current_password, а не current.
new_passwordstringДаНовый пароль, не короче 8 символов. 🔴 Поле называется new_password, а не new.
Тело запроса
{ "current_password": "old-password", "new_password": "new-password" }
Пример (curl)
curl -X POST -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \
  -d '{"current_password":"old-password","new_password":"new-password"}' \
  http://127.0.0.1:8891/api/v1/auth/password
Ответ
{
  "status": "ok"
}
  • 🔴 Тело с именами current и new декодируется в две пустые строки, и ручка отвечает 401 «current password is incorrect» — как будто ошиблись паролем, а не именем поля.
  • 400 «password must be at least 8 characters» на слишком коротком новом пароле.
  • 501 «auth not configured», если вход в дашборд в этой сборке не настроен.
POST/api/v1/auth/apikey/rotateТребуется авторизация

Выдаёт новый ключ API и возвращает его. Прежний перестаёт работать немедленно.

Пример (curl)
curl -X POST -H "X-API-Key: YOUR_API_KEY" \
  http://127.0.0.1:8891/api/v1/auth/apikey/rotate
Ответ
{
  "api_key": "a1b2c3d4e5f6a7b8c9d0"
}
  • Запрос идёт со СТАРЫМ ключом, а ответ несёт новый — другого случая узнать его нет.
  • 500 «could not rotate key», если новый ключ не удалось сохранить; 501, если вход не настроен.

Статус системы

GET/api/v1/statusТребуется авторизация

Возвращает общий статус: версию, число открытых и максимальное число портов (max_ports — действующий потолок, меньшее из настроенного максимума и лимита вашей лицензии), количество идентичностей, время работы и прочее.

Пример (curl)
curl -H "X-API-Key: YOUR_API_KEY" http://127.0.0.1:8891/api/v1/status
Ответ
{
  "version": "1.3.2",
  "commit": "a1b2c3d",
  "commit_time": "2026-09-01T10:00:00Z",
  "open_ports": 2,
  "max_ports": 1000,
  "idle_timeout_seconds": 1800,
  "ports": [
    "…"
  ],
  "profile_count": 143900,
  "profile_counts": {
    "chrome_152+windows": 41000,
    "chrome_152+macos": 22000,
    "firefox_152+windows": 17400,
    "safari_18+ios": 9800
  },
  "uptime": "3d14h22m",
  "domain_routing": {
    "domains": [],
    "proxy": ""
  },
  "domain_rules_count": 3,
  "pack_loaded": true,
  "solver_bundle_state": "ready",
  "solver_bundle_total": 1,
  "vision_bundle_state": "ready",
  "vision_bundle_total": 1,
  "font_bundle_state": "ready",
  "font_bundle_total": 1,
  "build_state": "ok",
  "device_bound": true
}
Ответ (свежая установка: бандл ещё качается, файл не сошёлся)
{
  "pack_loaded": true,
  "solver_bundle_state": "downloading",
  "vision_bundle_state": "dormant",
  "font_bundle_state": "failed",
  "font_bundle_reason": "macos: connection refused",
  "build_state": "mismatch",
  "build_reason": "the program file does not match the published build — please reinstall",
  "device_bound": true,
  "device_rebound_at": "2026-09-01T10:00:00Z"
}
  • Первый пример — УСПЕШНЫЙ исход. Поля solver_bundle_state, vision_bundle_state и font_bundle_state принимают одно из четырёх значений: dormant — загрузка ещё ни разу не начиналась (для vision это норма: модели качаются в момент первой встречи с капчей), downloading — идёт скачивание, ready — установлено, failed — сорвалось.
  • 🔴 Проверку готовности нельзя писать как «равно ready, всё остальное — поломка»: свежая установка сначала показывает downloading, и такой скрипт не отличит «ещё качается» от «упало».
  • У каждого состояния есть парное поле причины — solver_bundle_reason, vision_bundle_reason, font_bundle_reason: короткая строка без секретов. При ready и dormant его в ответе нет. Поле font_bundle_reason перечисляет ВСЕ сбойные ОС (macos: connection refused; windows: 404), потому что само состояние — худшее из пулов по операционным системам.
  • 🔴 vision_bundle_state ready вместе с НЕПУСТЫМ vision_bundle_reason — это не «всё хорошо»: бандл скачан, но получить ключ содержимого не удалось (лицензия без права на решатель, устаревший токен, Кабинет недоступен), и reCAPTCHA молча не решается.
  • Значение pack_loaded false означает, что боевые рецепты отпечатков не загружены; pack_reason приходит рядом и объясняет почему (not yet attempted, недоступность Кабинета). Пустая причина при false — у лицензии нет права на пак.
  • Поле build_state — сверка ФАЙЛА программы с опубликованной сборкой. Значения: ok, mismatch, unregistered. 🔴 Поля может не быть в ответе вовсе — это «сигнала ещё не было», и считать его за ok нельзя. При mismatch и unregistered рядом приходит build_reason с готовым текстом для показа: mismatch — файл испорчен или подменён, нужна переустановка; unregistered — эта сборка не публиковалась сервером лицензий, и переустановка того же выпуска не поможет.
  • Значение device_bound false — установка не закреплена за ключом устройства. Поле device_rebound_at (время последней перепривязки) приходит, только если перепривязка была.
GET/api/v1/healthБез авторизации

Простая проверка работоспособности, которая всегда возвращает OK.

Пример (curl)
curl http://127.0.0.1:8891/api/v1/health
Ответ
{
  "status": "ok"
}

Без ключа доступны ровно четыре пути: эта проверка, POST /api/v1/auth/login, GET /crl (за списком отзыва идёт стек проверки сертификатов ОС, а не человек) и GET /export/{token}/… (доступ там даёт токен в самом адресе). Всё остальное отвечает 401. Проверкой живости пользуются из мониторинга и из оболочки контейнера; о состоянии портов или лицензии она не сообщает ничего.

Корневой сертификат (CA)

Приложение вскрывает TLS собственным корневым сертификатом, и система должна ему доверять — иначе браузер покажет ошибку сертификата на каждом сайте. Три ручки отдают сертификат в трёх видах, четвёртая ставит его сама.

GET/api/v1/caТребуется авторизация

Отдаёт корневой сертификат текстом PEM.

Пример (curl)
curl -H "X-API-Key: YOUR_API_KEY" -o blanktrail-ca.pem \
  http://127.0.0.1:8891/api/v1/ca
  • 404 «CA certificate path not configured», если путь к сертификату не задан; 500 «failed to read CA certificate», если файл не читается. Разбор PEM здесь не делается — это отдаёт файл как есть; проверку «не PEM» делают ручки, которым нужен разобранный сертификат, /ca.crt и /ca.mobileconfig.
GET/api/v1/ca.crtТребуется авторизация

Тот же сертификат в двоичном формате DER — его понимает системный установщик macOS и iOS.

Пример (curl)
curl -H "X-API-Key: YOUR_API_KEY" -o blanktrail-ca.crt \
  http://127.0.0.1:8891/api/v1/ca.crt
GET/api/v1/ca.mobileconfigТребуется авторизация

Профиль Apple .mobileconfig: открытый на устройстве, он ставит сертификат сам.

Пример (curl)
curl -H "X-API-Key: YOUR_API_KEY" -o blanktrail.mobileconfig \
  http://127.0.0.1:8891/api/v1/ca.mobileconfig
  • На iPhone мало установить профиль: сертификат нужно ещё ОТМЕТИТЬ доверенным в настройках «Об этом устройстве → Доверие сертификатам».
POST/api/v1/ca/installТребуется авторизация

Ставит корневой сертификат в пользовательское хранилище доверенных корней.

Пример (curl)
curl -X POST -H "X-API-Key: YOUR_API_KEY" \
  http://127.0.0.1:8891/api/v1/ca/install
Ответ
{
  "status": "installed"
}
  • Только для Windows: на остальных системах отвечает 501, и сертификат нужно скачать и добавить вручную.
  • После установки браузер нужно ПОЛНОСТЬЮ перезапустить — закрыть все окна, а не открыть новое: хранилище доверия он читает при старте.
  • 500 «install failed: …», если системное хранилище отвергло сертификат.

Challenge Breaker

Эти ручки показывают, чем разбор защит занят прямо сейчас и сколько машины ему разрешено занимать. Доступны они на любом тарифе: без Challenge Breaker счётчики солвов остаются нулевыми, но встреченные защиты всё равно видны — так вы узнаете, с чем столкнулся ваш трафик.

GET/api/v1/solver/statsТребуется авторизация

Счётчики с момента запуска: попытки и успехи по семействам защит, встреченные защиты и занятые ресурсы.

Пример (curl)
curl -H "X-API-Key: YOUR_API_KEY" \
  http://127.0.0.1:8891/api/v1/solver/stats
Ответ
{
  "families": [ { "family": "recaptcha", "attempts": 41, "solved": 38 } ],
  "total_attempts": 41,
  "total_solved": 38,
  "since_start": true,
  "resources": { "live_procs": 2, "busy_procs": 1, "cap": 4,
                 "constrained": false, "reason": "", "last_error": "",
                 "launch_failures": 0, "proc_deaths": 0, "mem_recycles": 0 },
  "seen": { "total": 47, "since_unix_ms": 1757116800000,
            "vendors": [ { "vendor": "recaptcha", "count": 41 },
                         { "vendor": "turnstile", "count": 6 } ] }
}
  • 🔴 На тарифе БЕЗ Challenge Breaker непустым остаётся только seen: солвов не было, а защиты были. Ради этого поле и заведено.
GET/api/v1/solver/queueТребуется авторизация

Живая очередь: сколько ждёт и сколько решается прямо сейчас, предел очереди и по строке на запрос.

Пример (curl)
curl -H "X-API-Key: YOUR_API_KEY" \
  http://127.0.0.1:8891/api/v1/solver/queue
Ответ
{
  "queued": 2,
  "running": 1,
  "max_len": 16,
  "items": [ { "port": 20134, "host": "example.com", "vendor": "recaptcha",
               "class": "chrome-152-win", "state": "solving",
               "waiters": 1, "age_ms": 4120 } ]
}
  • Отдельная ручка, а не поле в /solver/stats: очередь меняется на порядки быстрее счётчиков, и панель опрашивает её своим темпом. Управления очередью здесь нет — только чтение.
PUT/api/v1/solver/procsТребуется авторизация

Задаёт, сколько процессов разбора работает одновременно. Применяется без перезапуска и переживает его.

ПараметрТипОбязательныйОписание
procsintДаЧисло процессов; 0 выключает разбор.
Тело запроса
{ "procs": 4 }
Пример (curl)
curl -X PUT -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \
  -d '{"procs":4}' \
  http://127.0.0.1:8891/api/v1/solver/procs
Ответ
{
  "js_solver_procs": 4
}
  • 400 «procs must be >= 0»; 409, если запрошено больше, чем разрешает лицензия — предел виден как js_solver_max_procs в ответе GET /api/v1/license/status.
  • Когда значение не задано, действует потолок лицензии: выделять процессы вручную не обязательно.

Настройки и ключи

GET/api/v1/settings/networkТребуется авторизация

Сетевые настройки: принимают ли прокси-порты соединения из локальной сети, требуют ли они логин с паролем и по какому адресу видно список отзыва сертификатов.

Пример (curl)
curl -H "X-API-Key: YOUR_API_KEY" \
  http://127.0.0.1:8891/api/v1/settings/network
Ответ
{
  "allow_lan": false,
  "crl_public_host": "",
  "proxy_auth_enabled": true,
  "proxy_auth_user": "proxy"
}
  • Пароль прокси в ответе не возвращается никогда — его можно только задать.
PUT/api/v1/settings/networkТребуется авторизация

Меняет сетевые настройки. Присылать нужно только то, что меняется: поле, которого в теле нет, остаётся как было.

ПараметрТипОбязательныйОписание
allow_lanboolНетПринимать соединения на прокси-порты из локальной сети, а не только с этой машины.
crl_public_hoststringНетАдрес, по которому с ДРУГОЙ машины виден список отзыва сертификатов: host или host:port, без схемы и пути. Loopback отвергается — нужен адрес, по которому эта машина видна снаружи.
proxy_auth_enabledboolНетТребовать логин и пароль на прокси-портах.
proxy_auth_userstringНетЛогин прокси.
proxy_auth_passstringНетПароль прокси.
Тело запроса
{ "allow_lan": true, "proxy_auth_enabled": true,
  "proxy_auth_user": "proxy", "proxy_auth_pass": "…" }
Пример (curl)
curl -X PUT -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \
  -d '{"allow_lan":true}' \
  http://127.0.0.1:8891/api/v1/settings/network
Ответ
{
  "allow_lan": true,
  "crl_public_host": "",
  "proxy_auth_enabled": true,
  "proxy_auth_user": "proxy"
}
  • 400 «proxy_auth_user and proxy_auth_pass are required to enable proxy auth»: включить проверку без пары логин-пароль нельзя.
  • 400 с объяснением на неверном crl_public_host; 503 «auth store not configured». Тело ограничено 8 КиБ.
GET/api/v1/auth/apikeyТребуется авторизация

Возвращает ключ API — тот самый, который идёт в заголовке X-API-Key.

Пример (curl)
curl -H "X-API-Key: YOUR_API_KEY" \
  http://127.0.0.1:8891/api/v1/auth/apikey
Ответ
{
  "api_key": "a1b2c3d4e5f6a7b8c9d0"
}
  • Ответ пуст, если сохранённой копии ключа нет: ключ, заданный только хешем, показать невозможно.
PUT/api/v1/auth/apikeyТребуется авторизация

Ставит собственный ключ API вместо сгенерированного. Прежний перестаёт работать немедленно.

ПараметрТипОбязательныйОписание
api_keystringДа12–128 печатных символов ASCII без пробелов: ключ едет в заголовке дословно.
Тело запроса
{ "api_key": "my-own-api-key-2026" }
Пример (curl)
curl -X PUT -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \
  -d '{"api_key":"my-own-api-key-2026"}' \
  http://127.0.0.1:8891/api/v1/auth/apikey
Ответ
{
  "api_key": "my-own-api-key-2026"
}
  • 400 «api key must be 12-128 characters» или «api key must be printable ASCII without spaces». Тело ограничено 8 КиБ.
GET/api/v1/settings/integration-keyТребуется авторизация

Сообщает, сохранён ли интеграционный ключ — он нужен, чтобы активировать лицензию от имени интегратора.

Пример (curl)
curl -H "X-API-Key: YOUR_API_KEY" \
  http://127.0.0.1:8891/api/v1/settings/integration-key
Ответ
{
  "integration_key": "",
  "set": false
}
PUT/api/v1/settings/integration-keyТребуется авторизация

Сохраняет или очищает интеграционный ключ. Действует без перезапуска.

ПараметрТипОбязательныйОписание
integration_keystringДаКлюч, до 512 символов. Пустое значение очищает сохранённый.
Тело запроса
{ "integration_key": "…" }
Пример (curl)
curl -X PUT -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \
  -d '{"integration_key":""}' \
  http://127.0.0.1:8891/api/v1/settings/integration-key
Ответ
{
  "ok": true
}
  • 400 «integration_key must be at most 512 characters»; 503 «auth store not configured».

Активация из кода

Активацию обычно проходят в дашборде, но её можно выполнить и запросом — например, разворачивая машину скриптом. Учётные данные уходят прямо в Кабинет; приложение их не хранит, а сохраняет только подписанную лицензию.

POST/api/v1/license/enrollТребуется авторизация

Начинает активацию учётной записью blanktrail.com.

ПараметрТипОбязательныйОписание
emailstringДаПочта учётной записи.
passwordstringДаПароль учётной записи.
Тело запроса
{ "email": "you@example.com", "password": "…" }
Пример (curl)
curl -X POST -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \
  -d '{"email":"you@example.com","password":"…"}' \
  http://127.0.0.1:8891/api/v1/license/enroll
Ответ
{
  "choose": false,
  "activated": true,
  "plan": "pro"
}
  • Если на учётной записи несколько лицензий, ответ приходит с choose=true, списком и разовым билетом — выбирают следующей ручкой.
  • 400 «email and password are required»; 502 с состоянием внутри тела, если Кабинет недоступен; 503 «license manager not configured».
POST/api/v1/license/enroll/confirmТребуется авторизация

Выбирает лицензию, когда на учётной записи их несколько.

ПараметрТипОбязательныйОписание
ticketstringДаРазовый билет из ответа предыдущего запроса.
license_idintДаИдентификатор выбранной лицензии из того же ответа.
Тело запроса
{ "ticket": "…", "license_id": 42 }
Пример (curl)
curl -X POST -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \
  -d '{"ticket":"…","license_id":42}' \
  http://127.0.0.1:8891/api/v1/license/enroll/confirm
Ответ
{
  "activated": true,
  "plan": "pro"
}
  • 400 «ticket and license_id are required»; 502, если Кабинет недоступен.
POST/api/v1/auth/onboard-passwordТребуется авторизация

Задаёт пароль дашборда на первом экране — по разовому токену bootstrap_token из ответа POST /api/v1/auth/onboard-activate, а не по прежнему паролю.

ПараметрТипОбязательныйОписание
bootstrap_tokenstringДаРазовый токен первого запуска — из ответа POST /api/v1/auth/onboard-activate. Одноразовый, живёт 15 минут.
new_passwordstringДаНовый пароль, не короче 8 символов.
Тело запроса
{ "bootstrap_token": "…", "new_password": "new-password" }
Пример (curl)
curl -X POST -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \
  -d '{"bootstrap_token":"…","new_password":"new-password"}' \
  http://127.0.0.1:8891/api/v1/auth/onboard-password
Ответ
{
  "status": "ok"
}
  • 401 «invalid or expired activation token»; 400 с правилом пароля, если он короче восьми символов; 501, если вход не настроен.
POST/api/v1/auth/onboard-activateТребуется авторизация

Активирует лицензию на том же первом экране, не заходя в дашборд отдельно.

ПараметрТипОбязательныйОписание
emailstringДаПочта учётной записи.
passwordstringДаПароль учётной записи.
ticketstringНетБилет выбора лицензии, если их несколько.
license_idintНетИдентификатор выбранной лицензии.
Тело запроса
{ "email": "you@example.com", "password": "…" }
Пример (curl)
curl -X POST -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \
  -d '{"email":"you@example.com","password":"…"}' \
  http://127.0.0.1:8891/api/v1/auth/onboard-activate
Ответ
{
  "activated": true,
  "plan": "pro",
  "bootstrap_token": "…"
}
  • Поле bootstrap_token приходит ТОЛЬКО в этом ответе и только при activated: true. Оно одноразовое и живёт 15 минут; сохраните его и передайте в POST /api/v1/auth/onboard-password. Другого источника нет — установщик этот токен не выдаёт.
  • 400 «email and password are required»; 429 после череды попыток с одного адреса; 502, если Кабинет недоступен; 503 «license manager not configured».