API — Профили, пресеты и роутинг
Просматривайте и импортируйте идентичности, управляйте пресетами и правилами доменного роутинга, а также регистрируйте шлюзы, через которые проходит трафик ваших портов.
Профили
/api/v1/profilesТребуется авторизацияВозвращает список сохранённых идентичностей с опциональной постраничной навигацией и фильтром по браузеру.
| Параметр | Тип | Обязательный | Описание |
|---|---|---|---|
limit | int (query) | Нет | Размер страницы: по умолчанию 100, максимум 1000. Большее значение НЕ отклоняется — оно молча урезается до 1000; фактический размер страницы возвращается в поле limit ответа. Чтобы выкачать базу целиком, листайте offset'ом, сверяясь с total. |
offset | int (query) | Нет | Смещение страницы (по умолчанию 0). |
browser | string (query) | Нет | Фильтр по браузеру, например "chrome". |
curl -H "X-API-Key: YOUR_API_KEY" "http://127.0.0.1:8891/api/v1/profiles?browser=chrome&limit=50"{
"profiles": [
{ "id": 8412, "name": "chrome_152_windows", "browser": "chrome",
"version": "152", "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) …",
"created_at": "2026-08-20T12:00:00Z" }
],
"total": 143900,
"limit": 50,
"offset": 0
}
total — сколько профилей отвечает фильтру ЦЕЛИКОМ, а не сколько вернулось на этой странице. 400 «browser must be one of: chrome, firefox, safari, edge» на неизвестном браузере.
/api/v1/countsТребуется авторизацияВозвращает разбивку идентичностей по сочетанию браузер+версия+ОС. Группировки по семейству браузера ручка не делает.
curl -H "X-API-Key: YOUR_API_KEY" http://127.0.0.1:8891/api/v1/counts{
"chrome_152+windows": 41000,
"chrome_152+macos": 22000,
"firefox_152+windows": 17400,
"safari_18+ios": 9800
}Ключ имеет вид "браузер_версия+ос": браузер — в НИЖНЕМ регистре (chrome, firefox, safari, edge), затем подчёркивание, версия, знак плюса и ОС (windows, macos, linux, android, ios). Если у профиля не записана ОС, ключ выглядит как "chrome_152+" — с пустым хвостом после плюса. Ключей вида "Chrome" в ответе не бывает: чтобы получить итог по семейству, сложите значения сами. Это те же счётчики, что приходят в поле profile_counts у GET /api/v1/status, и их сумма равна profile_count оттуда же.
Идентичность порта
/api/v1/port/{port}/rotateТребуется авторизацияМеняет идентичность порта на другую в пределах его текущих фильтров.
curl -H "X-API-Key: YOUR_API_KEY" -X POST http://127.0.0.1:8891/api/v1/port/20134/rotatecurl -X POST -H "X-API-Key: YOUR_API_KEY" \
http://127.0.0.1:8891/api/v1/port/20134/rotate
{
"name": "firefox_152_macos",
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:152.0) …",
"browser": "firefox",
"os": "macos"
}
500 «no profile available», если движку нечего выдать: база профилей пуста или отфильтрована в ноль. Новая личность действует со следующего соединения — уже открытые keep-alive не рвутся.
/api/v1/port/{port}/profile/viewТребуется авторизацияВозвращает полную идентичность, которую порт в данный момент представляет.
curl -H "X-API-Key: YOUR_API_KEY" http://127.0.0.1:8891/api/v1/port/20134/profile/viewcurl -H "X-API-Key: YOUR_API_KEY" \
http://127.0.0.1:8891/api/v1/port/20134/profile/view
{
"name": "chrome_152_windows",
"browser": "chrome",
"os": "windows",
"version": 152,
"user_agent": "Mozilla/5.0 …",
"tls": {
"available": true,
"cipher_suites": ["TLS_AES_128_GCM_SHA256", "…"],
"extensions": ["server_name", "…"],
"curves": ["X25519MLKEM768", "…"],
"alpn": ["h2", "http/1.1"],
"ja3": "771,4865-4866-…",
"ja3_hash": "cd08e31494f9531f560d64c695473da9",
"ja3_note": "…"
},
"h2": { "settings": ["HEADER_TABLE_SIZE=65536", "…"] },
"spec_source": "spec"
}
spec_source говорит, ОТКУДА взята форма: preset — из подготовленного набора, spec — собрана по спецификации, factory — запасная сборка, unavailable — форму собрать не удалось, и в этом случае tls.available=false. Это и есть ответ на вопрос «почему порт выглядит не так, как я просил».
Пресеты
Пресет — это сохранённый НАБОР портов вместе с их настройками, разложенный по папкам. Им поднимают готовую рабочую расстановку одним запросом.
/api/v1/presetsТребуется авторизацияДерево сохранённых пресетов и папок.
curl -H "X-API-Key: YOUR_API_KEY" \
http://127.0.0.1:8891/api/v1/presets{
"name": "",
"path": "",
"is_folder": true,
"children": [
{ "name": "parsing", "path": "parsing", "is_folder": true,
"children": [ { "name": "marketplaces", "path": "parsing/marketplaces",
"is_folder": false } ] },
{ "name": "daily-crawl", "path": "daily-crawl", "is_folder": false }
]
}/api/v1/presetsТребуется авторизацияСохраняет конфигурации открытых портов пресетом по указанному пути.
| Параметр | Тип | Обязательный | Описание |
|---|---|---|---|
path | string | Да | Путь пресета в дереве, например parsing/marketplaces. |
ports | array | Нет | Номера портов, которые сохранять. Пусто — сохранить все открытые. |
{ "path": "parsing/marketplaces", "ports": [20134, 20135] }curl -X POST -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \
-d '{"path":"parsing/marketplaces","ports":[20134,20135]}' \
http://127.0.0.1:8891/api/v1/presets{
"status": "saved",
"path": "parsing/marketplaces"
}- 400 «path is required»; 400 «port N is not open», если в списке закрытый порт; 400 «invalid path …», «invalid path segment …» или «path escapes root», если путь ведёт за пределы хранилища пресетов. 403 приходит только при реальном отказе файловой системы в правах.
/api/v1/presets/loadТребуется авторизацияПоднимает порты из пресета.
| Параметр | Тип | Обязательный | Описание |
|---|---|---|---|
path | string | Да | Путь пресета. |
mode | string | Да | replace или merge. Другое значение — 400. |
{ "path": "parsing/marketplaces", "mode": "merge" }curl -X POST -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \
-d '{"path":"parsing/marketplaces","mode":"merge"}' \
http://127.0.0.1:8891/api/v1/presets/load{
"results": [
{ "port": 20134, "status": "opened" },
{ "port": 20135, "status": "failed", "error": "port 20135 is already in use" }
]
}- 🔴 mode=replace ЗАКРЫВАЕТ ВСЕ открытые порты перед тем, как поднять порты пресета — идущий через них трафик обрывается. mode=merge закрывает только те порты, чьи номера есть в пресете.
- Ответ — построчный итог по каждому порту: пресет мог быть записан давно, и часть портов может не подняться. Порт с несовместимым сочетанием mode=random и фильтров отклоняется здесь так же, как при обычном открытии.
- 400 «path is required», 400 «mode must be replace or merge», 404 «preset not found».
/api/v1/presetsТребуется авторизацияУдаляет пресет.
| Параметр | Тип | Обязательный | Описание |
|---|---|---|---|
path | string | Да | Путь пресета. |
{ "path": "parsing/marketplaces" }curl -X DELETE -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \
-d '{"path":"parsing/marketplaces"}' \
http://127.0.0.1:8891/api/v1/presets{
"status": "deleted"
}- 400 «path is required»; 404 «preset not found». Тело обязательно.
Правила доменного роутинга
Правила разбирают трафик ОДНОГО порта по доменам: какой домен куда идёт и под какой личностью. Правила упорядочены, и выигрывает первое включённое, чьи образцы совпали с именем хоста.
/api/v1/domain_rulesТребуется авторизацияВозвращает упорядоченный список правил.
curl -H "X-API-Key: YOUR_API_KEY" \
http://127.0.0.1:8891/api/v1/domain_rules{
"rules": [
{
"id": "r1",
"name": "catalogue",
"enabled": true,
"matchers": ["example.com", "=shop.example.net"],
"upstream": "socks5://203.0.113.10:1080",
"chain_proxy": "",
"upstream_gateway": "",
"chain_gateway": "",
"spoof": "inherit",
"spoof_cfg": { "mode": "", "browser": "", "os": "",
"spoof_headers": null, "h2_spoofing": null }
}
]
}- Образец без префикса совпадает с доменом И всеми его поддоменами; образец с «=» в начале — только с самим доменом.
/api/v1/domain_rulesТребуется авторизацияЗаменяет весь список правил целиком — присылать нужно все правила, а не одно.
| Параметр | Тип | Обязательный | Описание |
|---|---|---|---|
rules | array | Да | Правила в порядке применения. |
rules[].name | string | Да | Имя правила — по нему его узнаёт человек. |
rules[].enabled | bool | Да | Выключенное правило пропускается при разборе. |
rules[].matchers | array | Да | Домены; «=» в начале делает совпадение точным. |
rules[].upstream | string | Нет | Выход для этих доменов. |
rules[].chain_proxy | string | Нет | Первое звено цепочки. |
rules[].upstream_gateway | string | Нет | Имя сохранённого шлюза вместо адреса выхода. |
rules[].chain_gateway | string | Нет | Имя шлюза для первого звена. |
rules[].spoof | string | Нет | inherit — брать личность у порта; custom — своя, из spoof_cfg; off — не подменять. |
rules[].spoof_cfg | object | Нет | Своя личность для spoof=custom: mode, browser, os, profile, spoof_headers, h2_spoofing. |
{ "rules": [ { "name": "catalogue", "enabled": true,
"matchers": ["example.com"],
"upstream": "socks5://203.0.113.10:1080",
"spoof": "inherit" } ] }curl -X PUT -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \
-d '{"rules":[{"name":"catalogue","enabled":true,"matchers":["example.com"],"upstream":"socks5://203.0.113.10:1080","spoof":"inherit"}]}' \
http://127.0.0.1:8891/api/v1/domain_rules{
"rules": [ { "id": "r1", "name": "catalogue", "enabled": true,
"matchers": ["example.com"],
"upstream": "socks5://203.0.113.10:1080", "spoof": "inherit" } ]
}- 400 с объяснением, если правило не той формы — например, назван шлюз, которого нет. Ответ отдаёт список так, как он сохранён, вместе с назначенными идентификаторами.
Шлюзы (OpenVPN, VLESS, VMess, Trojan, Shadowsocks, Hysteria2, WireGuard)
Шлюз — сохранённая конфигурация туннеля, который поднимается на этой машине и отдаёт локальный SOCKS5. Дальше его назначают порту выходом (upstream_gateway) или первым звеном цепочки (chain_gateway) — по имени, а не по адресу.
/api/v1/ovpnТребуется авторизацияСписок сохранённых шлюзов, состояние их туннелей и то, работает ли бэкенд вообще.
curl -H "X-API-Key: YOUR_API_KEY" \
http://127.0.0.1:8891/api/v1/ovpn{
"configs": [
{
"name": "eu-demo",
"kind": "openvpn",
"size": 4821,
"remote": "203.0.113.77:1194",
"via": "",
"uploaded_at": "2026-08-20T12:00:00Z",
"tunnel": { "config": "eu-demo", "status": "up", "ports": 2, "socks_addr": "127.0.0.1:41675" },
"ping": { "ms": 38, "at": "2026-09-04T09:41:12Z" }
}
],
"available": true
}- available=false означает, что на хосте нет ни OpenVPN, ни Xray; тогда рядом приходит reason. Поле via называет шлюз, через который этот выходит наружу — цепочку строят и здесь.
- Объект tunnel приходит только у шлюза, который уже поднимали, и состояние лежит в поле status — up, connecting или error. Поля state нет, времени подъёма туннеля ручка не отдаёт вовсе. Рядом приходят ports (сколько портов сейчас держат туннель), socks_addr (локальный SOCKS5 этого туннеля) и config (имя шлюза), а при status=error — поле error с причиной, по которой туннель не поднялся.
/api/v1/ovpnТребуется авторизацияДобавляет шлюз. Конфигурация передаётся ТЕКСТОМ в поле content — файл .ovpn или конфигурация WireGuard целиком, либо ссылка vless://, trojan://, ss://, vmess:// или hysteria2:// (равнозначно hy2://); отдельной загрузки файла у ручки нет.
| Параметр | Тип | Обязательный | Описание |
|---|---|---|---|
name | string | Да | Имя, под которым шлюз будет виден портам. |
content | string | Да | Текст файла .ovpn или конфигурации WireGuard целиком, либо ссылка vless://, trojan://, ss://, vmess:// или hysteria2:// (hy2://). |
kind | string | Нет | Одно из: openvpn, vless, trojan, shadowsocks, vmess, hysteria2, wireguard. Пусто — определить по содержимому: по схеме ссылки (hy2:// приравнивается к hysteria2), по разделам [Interface] и [Peer] — wireguard, иначе openvpn. |
via | string | Нет | Имя другого шлюза, через который этот будет выходить наружу. |
{ "name": "eu-demo", "kind": "vless",
"content": "vless://…@203.0.113.77:443?…" }curl -X POST -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \
-d "{\"name\":\"eu-demo\",\"content\":\"$(cat eu-demo.ovpn | sed 's/\"/\\\\"/g')\"}" \
http://127.0.0.1:8891/api/v1/ovpn{
"status": "saved",
"name": "eu-demo",
"kind": "vless"
}- 400 «name is required»; 400 «content is required (the .ovpn text or a vless:// link)»; 400 «unknown gateway kind …» на неизвестном значении kind.
- 400 с кодом via_cycle, если цепочка шлюзов замкнётся сама на себя — код приходит рядом с текстом, чтобы интерфейс мог отличить эту причину от прочих.
- 501 «gateway config manager is not enabled» в сборке, где шлюзы не собраны.
/api/v1/ovpn/{name}Требуется авторизацияПравит существующий шлюз: содержимое, звено via или и то и другое.
| Параметр | Тип | Обязательный | Описание |
|---|---|---|---|
content | string | Нет | Новый текст конфигурации. Пустая строка оставляет прежний. |
via | string | null | Нет | null — оставить как есть; пустая строка — снять звено; иначе имя шлюза. |
{ "via": "us-demo" }curl -X PUT -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \
-d '{"via":"us-demo"}' \
http://127.0.0.1:8891/api/v1/ovpn/eu-demo{
"status": "updated",
"name": "eu-demo",
"kind": "vless"
}- 404 «config "…" not found»; 400 с кодом via_cycle при замкнутой цепочке.
/api/v1/ovpn/{name}Требуется авторизацияУдаляет шлюз.
curl -X DELETE -H "X-API-Key: YOUR_API_KEY" \
http://127.0.0.1:8891/api/v1/ovpn/eu-demo{
"status": "deleted",
"name": "eu-demo"
}- 409 с кодом in_use, если шлюз назначен живому порту; 400 с кодом via_referenced, если через него выходит другой шлюз; 404, если такого шлюза нет.
/api/v1/gateway/openvpn-statusТребуется авторизацияСтоит ли на этой машине OpenVPN, и если нет — как его поставить именно на этой системе.
curl -H "X-API-Key: YOUR_API_KEY" \
http://127.0.0.1:8891/api/v1/gateway/openvpn-status{
"os": "windows",
"available": false,
"reason": "openvpn executable not found in PATH",
"install": {
"title": "Install OpenVPN Community for Windows",
"download_url": "https://openvpn.net/community-downloads/",
"steps": ["…"],
"note": "…"
}
}- Поле install приходит только при available=false. Проверка ограничена тремя секундами.
- Внутри install: title — заголовок, steps — шаги, download_url — ссылка на загрузку (именно download_url, не url), note — необязательная приписка. На linux и macOS дополнительно приходит command — готовая однострочная команда установки, например brew install openvpn. Поля download_url, command и note необязательные: при пустом значении их в ответе нет.
/api/v1/ovpn/pingТребуется авторизацияЗамеряет время отклика каждого сохранённого шлюза и возвращает снимок результатов.
curl -X POST -H "X-API-Key: YOUR_API_KEY" \
http://127.0.0.1:8891/api/v1/ovpn/ping{
"results": {
"eu-demo": { "ms": 38, "at": "2026-09-04T09:41:12Z" },
"us-demo": { "ms": 0, "at": "2026-09-04T09:41:12Z", "error": "timeout" }
}
}- Замер идёт и по расписанию, поэтому в ответе могут оказаться значения, снятые раньше: у каждого результата есть время снятия.
- 501 «gateway ping is not enabled»; 403 при неактивной лицензии — ручка за лицензией.
/api/v1/gateway/subsТребуется авторизацияПеречисляет подписки: сколько серверов дала каждая, какие шлюзы ей принадлежат, когда обновлялась и почему обновление не удалось в последний раз.
curl -H "X-API-Key: YOUR_API_KEY" \
http://127.0.0.1:8891/api/v1/gateway/subs{
"subscriptions": [
{
"name": "demo-sub",
"source": "https://example.com/…",
"enabled": true,
"interval_min": 60,
"last_ok": "2026-09-04T08:00:00Z",
"servers": 3,
"gateways": ["demo-sub.eu-demo", "demo-sub.us-demo", "demo-sub.asia-demo"],
"skipped": 0
}
]
}- Поле source приходит ОТРЕДАКТИРОВАННЫМ: остаются схема и хост, путь заменяется на «/…», запрос — на «?…». Секрет подписки лежит именно в пути, поэтому рабочей ссылкой это значение не является — сохранять его как адрес подписки нельзя.
- Имена шлюзов подписки имеют вид «имя-подписки.имя-сервера» (например demo-sub.DE-Germaniya): основа берётся из ремарки сервера. Двоеточие в имени недопустимо; префикс «gw:» в панели — только оформление строки маршрута, в API его не подставляют.
- skipped — сколько серверов подписки пропущено как неподдерживаемые.
/api/v1/gateway/subsТребуется авторизацияСоздаёт подписку или правит существующую по имени. Серверы подписки появляются как обычные шлюзы, и их можно назначить выходом или первым звеном.
| Параметр | Тип | Обязательный | Описание |
|---|---|---|---|
name | string | Да | Имя подписки: латиница, цифры, точка, дефис и подчёркивание, до 64 символов. |
source | string | Нет | Ссылка на подписку. Обязательна при создании; при правке опустите её, чтобы сохранить прежнюю. |
enabled | bool | Нет | Обновлять ли подписку по расписанию. |
interval_min | int | Нет | Период обновления в минутах. |
{ "name": "demo-sub", "source": "https://example.com/sub/demo",
"enabled": true, "interval_min": 60 }curl -X POST -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \
-d '{"name":"demo-sub","source":"https://example.com/sub/demo","interval_min":60}' \
http://127.0.0.1:8891/api/v1/gateway/subs{
"subscription": {
"name": "demo-sub",
"source": "https://example.com/…",
"enabled": true,
"interval_min": 60,
"last_ok": "2026-09-04T08:00:00Z",
"servers": 3,
"gateways": ["demo-sub.eu-demo", "demo-sub.us-demo", "demo-sub.asia-demo"],
"skipped": 0
},
"result": {
"added": 3, "updated": 0, "kept": 0,
"removed": 0, "retained": 0,
"skipped": 0, "unsupported": []
}
}- Полей status и name на верхнем уровне НЕТ: запись приходит целиком в subscription — той же формы, что элементы списка в GET /api/v1/gateway/subs.
- Подписка обновляется ПРЯМО в этом запросе, до 60 секунд: поэтому её серверы появляются сразу, а не после первого тика расписания. Ответ может прийти не мгновенно.
- 🔴 Провал обновления — НЕ провал сохранения: код ответа всё равно 200, но вместо result приходит refresh_error с текстом ошибки, а запись уже на диске и будет пытаться снова. Клиент обязан проверять refresh_error: иначе нерабочая подписка засчитывается как успешно заведённая.
- result — итог этого обновления: added, updated, kept, removed, retained, skipped и unsupported (список вида «схема×количество»).
- Ссылка на подписку обратно в открытом виде не отдаётся: в source приходит сокращённая форма — схема и хост, а путь и параметры заменены многоточием.
/api/v1/gateway/subs/{name}/refreshТребуется авторизацияОбновляет одну подписку немедленно, не дожидаясь расписания.
curl -X POST -H "X-API-Key: YOUR_API_KEY" \
http://127.0.0.1:8891/api/v1/gateway/subs/demo-sub/refresh{
"subscription": {
"name": "demo-sub",
"source": "https://example.com/…",
"enabled": true,
"interval_min": 60,
"last_ok": "2026-09-04T08:00:00Z",
"servers": 3,
"gateways": ["demo-sub.eu-demo", "demo-sub.us-demo", "demo-sub.asia-demo"],
"skipped": 0
},
"result": {
"added": 1, "updated": 0, "kept": 2,
"removed": 0, "retained": 0,
"skipped": 0, "unsupported": []
}
}{
"subscription": { "name": "demo-sub", "servers": 3, "…": "…" },
"refresh_error": "текст ошибки обновления"
}- Полей status и name на верхнем уровне нет. Имя, число серверов и список шлюзов лежат в subscription — это тот же объект, что в GET /api/v1/gateway/subs, и source в нём отредактирован: остаются только схема и хост.
- result — итог одного обхода: added, updated, kept, removed, retained, skipped, unsupported. Поле skipped внутри result — про этот обход, а skipped внутри subscription — про состояние подписки.
- Неудачное обновление возвращает 200 с полем refresh_error, а не ошибку HTTP: запись подписки остаётся на диске и попытки продолжатся по расписанию. Успех отличайте по наличию result и отсутствию refresh_error, а не по коду ответа.
- 403 при неактивной лицензии; 404, если подписки с таким именем нет; 501 «gateway subscriptions are not enabled», если планировщик подписок выключен.
/api/v1/gateway/subs/{name}Требуется авторизацияУдаляет подписку вместе со шлюзами, которые она создала, — кроме тех, что удерживаются ссылкой.
curl -X DELETE -H "X-API-Key: YOUR_API_KEY" \
http://127.0.0.1:8891/api/v1/gateway/subs/demo-sub{
"status": "deleted",
"removed": ["demo-sub.eu-demo", "demo-sub.us-demo"],
"retained": ["demo-sub.asia-demo"]
}- В ответе НЕТ имени подписки: приходят два списка шлюзов — removed (удалены) и retained (оставлены). Это единственный способ узнать, что уцелело.
- 🔴 Сама подписка удаляется ВСЕГДА; отказа «порт занят» у этой ручки не бывает. Шлюз, на который ссылается открытый порт или включённое правило доменного роутинга, попадает в retained: он остаётся обычным шлюзом, но подписки-владельца у него больше нет и обновляться он не будет. Хотите, чтобы шлюз ушёл вместе с подпиской, — освободите порт ДО удаления.
- 404, если подписки с таким именем нет; 501, если хранилище шлюзов выключено.
Папки пресетов
Пресеты складываются в папки, и папка — не украшение: пресет загружают целым набором портов, а набор удобнее держать вместе.
/api/v1/presets/folderТребуется авторизацияСоздаёт папку.
| Параметр | Тип | Обязательный | Описание |
|---|---|---|---|
path | string | Да | Путь папки в дереве. |
{ "path": "parsing" }curl -X POST -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \
-d '{"path":"parsing"}' \
http://127.0.0.1:8891/api/v1/presets/folder{
"status": "created",
"path": "parsing"
}- 400 «path is required»; 400 с объяснением, если путь не той формы.
/api/v1/presets/folderТребуется авторизацияУдаляет папку вместе со всем, что в ней лежит.
| Параметр | Тип | Обязательный | Описание |
|---|---|---|---|
path | string | Да | Путь папки. |
{ "path": "parsing" }curl -X DELETE -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \
-d '{"path":"parsing"}' \
http://127.0.0.1:8891/api/v1/presets/folder{
"status": "deleted",
"path": "parsing"
}- 400 «path is required»; 404 «folder not found»; 400 «invalid path …», «invalid path segment …» или «path escapes root», если путь ведёт за пределы хранилища пресетов. 403 приходит только при реальном отказе файловой системы в правах.
/api/v1/presets/moveТребуется авторизацияПереносит пресет или папку в другое место дерева.
| Параметр | Тип | Обязательный | Описание |
|---|---|---|---|
from | string | Да | Что переносить. |
to | string | Да | Куда. |
{ "from": "daily-crawl", "to": "parsing/daily-crawl" }curl -X POST -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \
-d '{"from":"daily-crawl","to":"parsing/daily-crawl"}' \
http://127.0.0.1:8891/api/v1/presets/move{
"status": "moved",
"from": "daily-crawl",
"to": "parsing/daily-crawl"
}- 400 «from and to are required»; 404 «source not found»; 409 «destination already exists».
Профили и отпечатки
Отпечаток, снятый с настоящего браузера, можно разобрать, посмотреть и сохранить под именем — чтобы потом выбирать его как обычный профиль.
/api/v1/fingerprint/parseТребуется авторизацияРазбирает снятый отпечаток и показывает, из чего он состоит, ничего не сохраняя. Тело запроса — СЫРОЙ JSON снятого отпечатка целиком, как его отдал сервис снятия.
{ "tls": { "ja3": "771,4865-4866-…", "ja4": "t13d1516h2_…" },
"http2": { "akamai_fingerprint": "1:65536;…" },
"user_agent": "Mozilla/5.0 …" }curl -X POST -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \
--data-binary @captured.json \
http://127.0.0.1:8891/api/v1/fingerprint/parse{
"custom_tls": { "ja3": "…", "ja4": "…", "ciphers": ["…"], "extensions": [{"name": "server_name (0)"},
{"name": "application_layer_protocol_negotiation (16)", "protocols": ["h2", "http/1.1"]}],
"alpn": ["h2", "http/1.1"], "userAgent": "Mozilla/5.0 …" },
"summary": { "ja3_hash": "…", "ja4": "…", "user_agent": "Mozilla/5.0 …",
"browser": "chrome", "ciphers": 16, "extensions": 15 }
}- custom_tls — готовое тело для PUT /api/v1/port/{port}/custom_tls: разобрали, посмотрели, применили.
- Принимаются форматы tls.peet.ws /api/all и ChromeApi /tls. Тело ограничено 1 МиБ. 400 с объяснением, если разобрать не удалось.
- Поле custom_tls.extensions — список ОБЪЕКТОВ: у каждого обязательное name, плюс необязательные supported_groups, signature_algorithms, versions, protocols. Это НЕ то же поле, что extensions в ответе /profile/view: там элементы — строки. Поле summary — тоже объект, а не строка.
/api/v1/profiles/importТребуется авторизацияСохраняет ОДИН отпечаток в базу профилей под именем — после этого его можно выбрать как mode=specific с этим именем.
| Параметр | Тип | Обязательный | Описание |
|---|---|---|---|
name | string | Да | Имя, под которым сохранить. |
source_json | string | Нет | Снятый отпечаток СТРОКОЙ, как он пришёл от сервиса снятия. |
custom_tls | object | Нет | Уже разобранный отпечаток — то, что вернул /fingerprint/parse. |
{ "name": "chrome_152_captured",
"source_json": "{\"tls\": … }" }curl -X POST -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \
-d '{"name":"chrome_152_captured","custom_tls":{ … }}' \
http://127.0.0.1:8891/api/v1/profiles/import{
"name": "chrome_152_captured",
"summary": { "ja3_hash": "…", "ja4": "…", "user_agent": "Mozilla/5.0 …",
"browser": "chrome", "ciphers": 16, "extensions": 15 }
}- Одно из полей source_json или custom_tls обязательно: без обоих ответ 400 «custom_tls or source_json required». Без имени — 400 «name is required».
- 409, если профиль с таким именем уже есть. Тело ограничено 1 МиБ.
/api/v1/scraper/tasks/{id}/profile/viewТребуется авторизацияПоказывает профиль, под которым идёт задача пула портов: тот же состав отпечатка, что уходит в сеть.
curl -H "X-API-Key: YOUR_API_KEY" \
http://127.0.0.1:8891/api/v1/scraper/tasks/TASK_ID/profile/view{
"port": 20134,
"profile": { "name": "chrome_152_windows", "browser": "chrome",
"os": "windows", "tls": { "ja3_hash": "…" }, "spec_source": "spec" }
}
port называет, какой именно порт пула проверялся: пул держит их много, и профиль у каждого свой. 409 «scraper task has no open ports yet» и «scraper task ports are not live»; 404, если пула нет.