# API Как встраивать API в свой клиент (UA, прокси, сессии, повторы) — [Usage.md](Usage.md). Задача открывает страницу в Chrome (через прокси клиента, если он задан), при необходимости проходит антибот (Cloudflare Turnstile, reCAPTCHA), выполняет список действий и возвращает cookies, результаты действий и, по запросу, HTML и скриншот. Задачи выполняет сервис worker из очереди; API работает в двух режимах: * `POST /v1/solve` — синхронно: ответ приходит, когда задача выполнена; * `POST /v2/tasks` + `GET /v2/tasks/{id}` — асинхронно, с возможностью держать окно браузера открытым между задачами (см. [API v2](#api-v2)). `GET /health` (и `GET /`) — `{"status": "ok", "browser_slots": N, "free_slots": M, "workers": [...]}`: слоты всех живых воркеров; `workers` — по каждому `worker_id`, `browser_slots`, `free_slots`, `windows` (открытые окна), `running` (выполняемые задачи). ## Авторизация Все эндпоинты, кроме `/health`, требуют ключ клиента в заголовке `X-API-Key`. Без ключа, с неверным или отозванным ключом — `401`. Ключи выпускает администратор (`make key-create name=<клиент>`, см. [Deployment.md](Deployment.md#клиенты-и-ключи)). Задача и окно сессии видны только клиенту, который их создал: с другим ключом `GET /v2/tasks/{id}` даёт `404`, а чужое открытое окно не используется. ## Запрос ```http POST /v1/solve Content-Type: application/json X-API-Key: Cookie: session_id= # необязательно, см. «Сессии» { "url": "https://freelancehunt.com/projects", "proxy": "http://user:pass@host:port", "captcha_type": "cloudflare", "actions": [ {"type": "wait_for", "selector": "a[href*=\"/project/\"]", "timeout": 60} ], "html": true, "timeout": 120 } ``` | Поле | Тип | По умолчанию | Описание | |---|---|---|---| | `url` | string | — | **обязательно**. Страница, которую открываем | | `proxy` | string | `null` | `scheme://[user:pass@]host:port`, схема `http`, `https` или `socks5` (Chrome не поддерживает socks5 с логином/паролем). По exit-IP прокси выставляются locale и timezone браузера; без прокси — настройки браузера по умолчанию | | `actions` | array | — | **обязательно**, может быть пустым. Действия, см. ниже | | `captcha_type` | string | `null` | провайдер антибота: `cloudflare` или `recaptcha`. Без него шаг антибота пропускается — для сайтов за Cloudflare обязательно | | `captcha` | string | `null` | ключ сервиса распознавания (2captcha), если антибот не проходится без решения капчи | | `captcha_detect_locator` | string | `null` | локатор, по которому определяется, что на странице антибот | | `captcha_success_locator` | string | `null` | локатор, по которому определяется, что антибот пройден | | `html` | bool | `false` | вернуть HTML страницы после выполнения действий | | `screenshot` | bool | `false` | вернуть скриншот видимой области (PNG, base64) после действий — в том числе при ошибке действия или антибота | | `timeout` | int, с | `60` | таймаут навигации | | `cookies` | array | `null` | cookies для установки до навигации, формат Playwright: `{name, value, domain, path, expires, httpOnly, secure, sameSite}` или `{name, value, url}`. **Заменяют** cookies профиля: перед установкой все cookies профиля удаляются (`[]` — просто очистить). Без поля cookies профиля сохраняются | | `disable_resources` | bool | `false` | не грузить картинки, шрифты, медиа | | `blocked_domains` | array | `null` | домены, запросы к которым блокируются | | `retries` | int | `1` | попыток навигации | | `retry_delay` | float, с | `1.0` | пауза между попытками | | `wait` | int, мс | `0` | пауза после загрузки страницы | | `google_search` | bool | `false` | выставить Referer как переход из поиска Google | ### Действия Выполняются по порядку после прохождения антибота. Если антибот не пройден, действия не выполняются (`code: challenge_failed`). На первой ошибке действия выполнение останавливается. Обязательные поля проверяются до запуска браузера: неизвестный `type`, нет `selector`/`value`, `captcha` без ключа в запросе, неверный `proxy` — ответ `422` с описанием в `detail` (стандартный формат FastAPI). | `type` | Поля | Что делает | |---|---|---| | `wait_for` | `selector`, `timeout` (1–7200 с, по умолчанию 30) | ждёт появления элемента; при таймауте — ошибка действия | | `click` | `selector` | клик по элементу | | `submit` | `selector` | клик и ожидание навигации | | `fill` | `selector`, `value` | мгновенная вставка значения в поле | | `typing` | `selector`, `value` | ввод как у человека: подвод курсора, клик, очистка поля, посимвольный набор с паузами 50–200 мс; медленнее `fill`, но не выдаёт автоматизацию | | `inner_text` | `selector` | возвращает текст элемента (HTML без тегов) в `result` | | `mouse_move` | `selector` | наведение курсора на элемент; нет элемента — ошибка `selector not found` | | `delay` | `value` (секунды строкой, 0–600) | пауза | | `inner_html` | `selector` | возвращает HTML элемента в `result`; нет элемента — ошибка `selector not found` | | `captcha` | `selector`, `value` (тип: `cloudflare`, `recaptcha`, `hcaptcha`) | решить капчу внутри страницы через 2captcha, нужен `captcha` в запросе | | `pass_challenge` | `selector` (локатор успеха, необязательно) | повторно пройти антибот, например после перехода на другую страницу; если не пройден — ошибка действия | ## Ответ Формат один для всех случаев: `status` — `"ok"` или `"err"`, при ошибке — `code` и `error`. Поля без значения не возвращаются. Успех (антибот пройден, действия выполнены), HTTP `200`: ```json { "status": "ok", "user_agent": "Mozilla/5.0 (X11; Linux x86_64) ... Chrome/155.0.0.0 Safari/537.36", "cookies": [{"name": "cf_clearance", "value": "...", "domain": ".freelancehunt.com", "...": "..."}], "actions": [ {"type": "wait_for", "ok": true, "duration_ms": 14} ], "html": "......", "duration_ms": 6405 } ``` | Поле | Описание | |---|---| | `status` | `"ok"` — антибот пройден и все действия выполнены, иначе `"err"` | | `code` | при `"err"`: причина, см. таблицу ниже | | `error` | при `"err"`: текст ошибки; для упавшего действия — `: <текст>` | | `user_agent` | родной UA браузера, с которым проходили антибот. Используйте его в своём HTTP-клиенте вместе с `cf_clearance` (см. [Usage.md](Usage.md)) | | `cookies` | все cookies контекста | | `actions` | по элементу на выполненное действие: `type`, `ok`, `result` (результат или текст ошибки), `duration_ms` | | `html` | только при `"html": true` | | `screenshot` | только при `"screenshot": true`: PNG в base64 | | `duration_ms` | общее время обработки запроса | | `code` | HTTP | Когда | Что ещё есть в ответе | |---|---|---|---| | `no_slots` | `503` + `Retry-After` | только `/v1/solve`: все слоты браузеров заняты, или воркер не взял задачу за `V1_QUEUE_WAIT` секунд (задача отменяется) | — | | `request_failed` | `200` | ошибка до действий: навигация, прокси, неизвестный `captcha_type` | `actions: []` | | `challenge_failed` | `200` | антибот не пройден за отведённое время, действия не выполнялись | cookies, html, screenshot | | `action_failed` | `200` | упало действие, выполнение остановлено на нём | cookies, html, screenshot, `actions` до упавшего включительно | Примеры: ```json {"status": "err", "code": "action_failed", "error": "inner_html: selector not found: #missing", "actions": [{"type": "inner_html", "ok": false, "result": "selector not found: #missing", "duration_ms": 26}], "...": "..."} ``` ```json {"status": "err", "code": "challenge_failed", "error": "cloudflare not passed: timeout (stage: checkbox)", "actions": [], "...": "..."} ``` ```json {"status": "err", "code": "no_slots", "error": "No slots available"} ``` ## Сессии Ответ ставит cookie `session_id` (UUID). Если передать её в следующем запросе, браузер откроется с тем же профилем (`extra/user_data_dir/`): cookies, localStorage и отпечаток сохраняются, повторно проходить Cloudflare обычно не нужно. Без cookie или с некорректным значением (не UUID) запрос получает новый профиль. Сессии изолированы друг от друга: у каждой свой профиль Chrome — cookies, localStorage, кэш. Отпечаток профиля генерируется при первом запросе сессии и дальше не меняется (`robotex_session.json` в каталоге профиля): * размер экрана и `devicePixelRatio` десктопного Chrome на Linux из реального распределения (browserforge) — нативной эмуляцией Chrome; * шум canvas: свой seed у каждого профиля, поэтому хеш canvas у профилей разный, а внутри профиля стабильный. Меняется младший бит у редких непрозрачных пикселей при чтении canvas (`toDataURL`, `toBlob`, `getImageData`); пустой canvas не меняется. Остальное — WebGL, `hardwareConcurrency`, шрифты, UA — не подменяется: это родные значения Chrome, за маскировку автоматизации отвечает Patchright. Полная JS-подмена отпечатка ломала прохождение Cloudflare. User-Agent и размер окна запросом не задаются — это родные значения браузера (почему — [Usage.md](Usage.md#что-задаёт-сервис-а-не-клиент)). Поле `user_agent` в запросе даёт `422`. Задачи одной сессии выполняются по очереди: профиль Chrome нельзя открыть дважды. Вторая задача ждёт, пока закончится первая. ## API v2 Асинхронный режим: задача ставится в очередь, клиент опрашивает её по ссылке. Тело задачи — как у `/v1/solve`, плюс: | Поле | Тип | По умолчанию | Описание | |---|---|---|---| | `browser_idle_timeout` | int, мин | `0` | держать окно браузера открытым после задачи столько минут простоя, `0`–`120`. `0` — закрыть сразу, как в v1 | | `url` | string | `null` | необязателен, если окно сессии открыто: тогда действия выполняются на текущей странице, без навигации | ### POST /v2/tasks ```http POST /v2/tasks Content-Type: application/json Cookie: session_id= # необязательно {"url": "https://freelancehunt.com/projects", "captcha_type": "cloudflare", "actions": [], "browser_idle_timeout": 10} ``` Ответ `202` и cookie `session_id`: ```json {"task_id": "0b9c…", "status": "queued", "url": "/v2/tasks/0b9c…"} ``` Без `url` и без открытого окна сессии — `422`. ### GET /v2/tasks/{id} Только с cookie `session_id` той сессии, в которой задача создана; иначе, а также для несуществующей или истёкшей задачи — `404`. ```json {"task_id": "0b9c…", "status": "done", "result": {"status": "ok", "user_agent": "…", "cookies": […], "actions": […], "duration_ms": 7120}} ``` | `status` | Значение | |---|---| | `queued` | ждёт свободного слота браузера | | `running` | выполняется | | `done` | готово, ответ в `result` — в том же формате, что ответ `/v1/solve` (со своим `status` `ok`/`err`) | | `failed` | задачу не удалось выполнить (например, воркер упал дважды), причина в `error` | Результат хранится `RESULT_TTL_HOURS` часов (по умолчанию 24), затем `404`. Опрашивать раз в 0,5–2 секунды. ### Окно браузера между задачами С `browser_idle_timeout > 0` Chrome сессии не закрывается после задачи. Следующая задача с той же cookie `session_id` выполняется в этом окне: без запуска Chrome и обычно без повторного антибота, а без `url` — прямо на текущей странице (например, продолжить заполнение формы). Каждая задача в окно заново отсчитывает простой — по своему `browser_idle_timeout`; задача с `0` закрывает окно после себя. * Открытое окно занимает слот браузера, пока не закроется. Не держите окна дольше, чем нужно. * Задача с другим `proxy`, чем у открытого окна, перезапускает Chrome (прокси задаётся при запуске). * `cookies` в задаче заменяют cookies открытого окна, как и при запуске. * Если задача в окне упала с `request_failed`, окно закрывается; следующая задача откроет его заново из профиля. ### DELETE /v2/sessions/current Закрыть окно сессии (по cookie `session_id`) досрочно и освободить слот. Если в окне идёт задача — сразу после неё. Ответ `{"closed": true}`, или `{"closed": false}`, если окна нет. ## Производительность Каждое окно — отдельный Chrome: около 0,6 ГБ памяти и до 3–4 ядер на пике при прохождении Cloudflare. Замеры и рекомендации по лимитам — в [Deployment.md](Deployment.md#нагрузка).