Перейти к содержанию
← На главную

Локальный API

Сверено с контрактом: сентябрь 2026

Выпущен и есть в сборке, которую можно скачать сегодня, — и на macOS, и на Windows. Переключатель лежит в настройках приложения, и API остаётся выключенным, пока вы его не включите. На какие вызовы эта сборка отвечает, помечено у каждой операции и у каждого примера ниже, а не написано здесь фразой, которую следующий выпуск сделает неверной.

Скрипт на той же машине превращает браузер профиля в то, чем можно управлять. Интерфейс — это один сервис, один ключ и один файл адреса, описанные первыми, потому что всё стоит на них, и несколько путей поверх: найти профиль, открыть его, подключить Puppeteer, Playwright или Selenium к адресу из ответа, закрыть обратно.

Как включить

По умолчанию выключен и доступен только с этого компьютера.

Переключатель
Откройте Settings → API & Automation и включите Local API. Пока он выключен, никто не слушает: порт не открыт, и скрипт получает отказ в соединении, а не пустой ответ.
Только этот компьютер
Сервис принимает соединения на 127.0.0.1 и больше нигде. Другая машина в вашей сети до него не доберётся, и настройки, которая это открыла бы, нет: скрипт работает рядом с Liminal, а не обращается к нему по сети.
Порт
По умолчанию 48361 — свой порт Liminal, чтобы не столкнуться с другим браузером, который уже слушает на этой машине. Впишите другой или нажмите Random free port, и приложение выберет само. Изменение действует сразу, без перезапуска Liminal.
Если порт занят
Когда порт не удалось занять, экран показывает Port in use, а файл адреса сообщает port_in_use. Никто не слушает, пока вы не выберете другой порт, — и скрипт, который читает файл, отличает «выключено» от «не удалось запустить».

Ключ

Один ключ, показан один раз, лежит там, где система хранит секреты.

Скопируйте, когда он появился
При включении API выдаёт ключ из 64 строчных шестнадцатеричных символов и показывает его один раз. Уйдёте со страницы — он скрыт: показать его снова приложение не может, только выдать новый.
Где он хранится
В Keychain на macOS и в Credential Manager на Windows — не в файле настроек, не в снимке синхронизации, не в логах и не в файле адреса ниже. Свою копию держите так же, как любой другой секрет, которым пользуется скрипт.
Как отозвать
Regenerate key выдаёт новый ключ и сразу перестаёт принимать прежний — без перезапуска приложения и без смены порта. Скрипт, у которого остался старый ключ, получает отказ с первого же запроса.
Как его передать
Каждый запрос несёт ключ в одном заголовке: Authorization: Bearer и сам ключ. Ни параметра в адресе, ни cookie для этого нет, а запрос без заголовка отклоняется так же, как запрос с неверным ключом.

Где скрипт берёт адрес

Приложение пишет свой адрес в файл, чтобы ничего не вводить дважды.

Файл
Экран настроек показывает абсолютный путь к Local API/status.json внутри каталога данных Liminal. Читайте его вместо того, чтобы зашивать порт: он следует и за портом, который вы задали, и за тем, который выбрал Random free port.
Что в нём
Четыре поля — enabled, status, address, port — и никаких секретов.
{"enabled":true,"status":"running","address":"http://127.0.0.1:48361","port":48361}
Когда API выключен
Файл остаётся на месте и сообщает об этом: off, пустой адрес и нулевой порт. Та же форма, другое состояние — скрипт читает один файл и узнаёт всё.
{"enabled":false,"status":"off","address":"","port":0}
Он заменяется целиком
Любое изменение — включили или выключили API, сменили порт, порт не удалось занять — перезаписывает файл атомарно. Скрипт, который читает его в этот момент, видит либо прежнее содержимое, либо новое, но не обрезанную строку.

Запрос статуса

Вызов, с которого стоит начать: работает ли Liminal и вошёл ли в него кто-нибудь.

Вызов
GET на /v1/status по адресу из файла, с ключом в заголовке.
curl -H 'Authorization: Bearer <key>' http://127.0.0.1:48361/v1/status
Ответ
HTTP 200, версия работающей сборки и признак того, вошёл ли кто-нибудь в приложение. Ложный signedIn — не ошибка: API отвечает, а аккаунт нужен тем операциям, которые читают каталог или открывают профиль.
{"status":"running","version":"<version>","signedIn":true}
Нет ключа или он отозван
HTTP 401 и код invalid_key — он постоянный, и ветвиться нужно по нему. Текст рядом — слова приложения для человека, который читает лог; считайте его текстом, который может измениться, а не частью контракта.
{"error":{"code":"invalid_key","message":"Неверный ключ"}}
Обращайтесь ровно так, как написано в файле
Используйте 127.0.0.1 и порт из файла. Запрос, который пришёл с другим именем хоста, ответа не получит, даже если попал на тот же порт: сервис отвечает только на свой адрес.
Что делает прежний Liminal
Ничего: порт не открыт, и скрипт получает отказ в соединении. Этот отказ и отличает сборку без локального API от сборки, где он выключен: во втором случае файл адреса существует и сообщает off.

Что эта версия делает и чего не делает

Прямо, чтобы ничего здесь не читалось как обещание, которым оно не является.

Операция попадает на эту страницу раньше, чем в вашу сборку
Интерфейс растёт от выпуска к выпуску, и написанный вызов описан здесь раньше, чем появляется сборка, которая на него отвечает, — с пометкой, а не спрятанным. Поэтому читайте пометку, а не обещание: она стоит у каждой операции и у каждого примера ниже и говорит, работает ли то, что вы собираетесь скриптовать, в вашем приложении.
На каких тарифах
На всех, включая бесплатный. Локальный API — не платная ступень: скрипт на своей же машине и есть то, ради чего антидетект-браузер часто берут, и прятать это за тариф значило бы брать деньги за сам способ работы.Тарифы
Как часто можно спрашивать
Квоты, привязанной к тарифу, нет. Сервис защищает себя от потока запросов, и написанный под него скрипт этого не заметит.
Профиль под автоматизацией — тот же профиль
У него тот же отпечаток, тот же прокси и те же cookies, и в сеть он выходит через свой прокси точно так же, как когда вы открываете его руками. API — это вход, а не второй режим браузера.Данные и безопасность

Каждая операция по отдельности

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

GET /v1/status

Есть в выпущенной сборке

Работает ли Liminal и вошёл ли в него кто-нибудь

Самый дешёвый вызов и тот, которым стоит ждать приложение. Отвечает и без аккаунта: ложный signedIn — это состояние, а не ошибка.

Параметры

Нет.

Ответ

{
  "status": "running",
  "version": "<version>",
  "signedIn": true
}

Отказы

  • 401 invalid_key — Ключ не передан, испорчен или с тех пор перевыпущен.

GET /v1/profiles

Есть в выпущенной сборке

Профили каталога, с фильтрами и по страницам

Читает тот же каталог, который показывает приложение. Фильтры складываются, и каждый из них точный, кроме name: он подходит любому профилю, в имени которого встречается введённое, без учёта регистра.

Параметры

pagequery · integer · необязательный · от 1 до 1000000 · по умолчанию 1
Какую страницу читать, считая с первой.
pageSizequery · integer · необязательный · от 1 до 100 · по умолчанию 100
Сколько профилей на странице. Потолок равен значению по умолчанию.
workspaceIdquery · string · необязательный
Только профили этого рабочего пространства.
folderIdquery · string · необязательный
Только профили этой папки.
statusquery · string · необязательный
Только профили в этом состоянии; running — состояние открытого профиля.
namequery · string · необязательный
Только профили, в имени которых это встречается, без учёта регистра.

Ответ

{
  "page": 1,
  "pageSize": 100,
  "total": 1,
  "items": [
    "<profile>"
  ]
}

Отказы

  • 401 invalid_key — Ключ не передан, испорчен или с тех пор перевыпущен.
  • 403 not_signed_in — В приложение никто не вошёл, а операциям каталога нужен аккаунт.
  • 503 catalog_unavailable — Каталог профилей ещё не готов. Спросите снова.
  • 400 invalid_pagination — page или pageSize вне допустимых границ.

GET /v1/profiles/{id}

Есть в выпущенной сборке

Один профиль, с его прокси и отпечатком

Тот же объект, что возвращает список, но для одного идентификатора. Адрес прокси показан, пароль — никогда.

Параметры

idpath · string · обязательный
Идентификатор профиля — такой, каким его возвращают операции каталога.

Ответ

{
  "id": "<profile-id>",
  "name": "Shop EU",
  "workspaceId": "<workspace-id>",
  "folderId": "<folder-id>",
  "status": "running",
  "health": "ok",
  "proxy": {
    "id": "<proxy-id>",
    "name": "Residential DE",
    "type": "socks5",
    "host": "proxy.example.net",
    "port": 1080,
    "working": true,
    "lastCheckedExternalIp": "203.0.113.10",
    "lastCheckedCountryCode": "DE"
  },
  "fingerprint": {
    "platform": "MacIntel",
    "language": "de-DE",
    "timezone": "Europe/Berlin"
  },
  "lastOpenedAt": "2026-09-30T08:15:00.000Z"
}

Отказы

  • 401 invalid_key — Ключ не передан, испорчен или с тех пор перевыпущен.
  • 403 not_signed_in — В приложение никто не вошёл, а операциям каталога нужен аккаунт.
  • 503 catalog_unavailable — Каталог профилей ещё не готов. Спросите снова.
  • 404 profile_not_found — Профиля с таким идентификатором в каталоге нет.

POST /v1/profiles/{id}/start

Есть в выпущенной сборке

Открыть профиль и получить адрес, к которому подключается драйвер

Запускает профиль так же, как это делает приложение — его отпечаток, его прокси, его cookies — и отвечает CDP-адресом, когда браузер поднялся. Вызов ждёт, а не возвращает задачу, которую надо опрашивать.

Параметры

idpath · string · обязательный
Идентификатор профиля — такой, каким его возвращают операции каталога.
debugPortbody · integer · необязательный · от 1024 до 65534 · по умолчанию 0
Порт, на котором поднимается CDP-endpoint. Ноль — приложение выберет свободный само; любое другое значение должно быть портом, который ваш пользователь может занять.
timeoutSecondsbody · integer · необязательный · от 1 до 180 · по умолчанию 60
Сколько вызов ждёт, пока профиль поднимется, прежде чем сдаться.
maskAutomationbody · boolean · необязательный · по умолчанию true
Принимается только true. Маскировку автоматизации выключить нельзя: профиль под скриптом — тот же профиль, который сайт видит, когда вы открываете его руками.

Ответ

{
  "id": "<profile-id>",
  "status": "running",
  "cdpDiscoveryUrl": "http://127.0.0.1:48361/devtools/<token>/json/version",
  "cdpBrowserUrl": "ws://127.0.0.1:9222/devtools/browser/<token>",
  "seleniumDebuggerUrl": "127.0.0.1:9222",
  "chromedriverPath": "/Applications/Liminal.app/.../chromedriver",
  "debugPort": 9222,
  "debugAddress": "127.0.0.1:9222",
  "externalIp": "203.0.113.10",
  "countryCode": "DE"
}

Отказы

  • 401 invalid_key — Ключ не передан, испорчен или с тех пор перевыпущен.
  • 403 not_signed_in — В приложение никто не вошёл, а операциям каталога нужен аккаунт.
  • 503 runtime_unavailable — Приложение сейчас не может ни запустить, ни остановить профиль.
  • 404 profile_not_found — Профиля с таким идентификатором в каталоге нет.
  • 409 profile_already_running — Профиль уже открыт, его запуск ещё идёт или выданный раньше адрес драйвера всё ещё действует.
  • 400 invalid_start_options — Тело запроса не JSON-объект.
  • 400 invalid_port — debugPort не равен нулю и не попадает в допустимый диапазон.
  • 400 invalid_start_timeout — timeoutSeconds вне допустимых границ.
  • 400 masking_unavailable — maskAutomation пришёл как false, а такого режима у приложения нет.
  • 409 port_in_use — Запрошенный отладочный порт занят.
  • 409 listen_failed — Отладочный порт не удалось занять по другой причине.
  • 409 start_failed — Профиль не поднялся. Когда приложение может это сказать, безопасное поле detail называет стадию.
  • 409 proxy_unavailable — Профилю нужен его прокси, а прокси не ответил.
  • 502 start_failed — Подготовка профиля не удалась ещё до того, как браузер попросили запуститься.
  • 503 cdp_unavailable — Профиль запустился, но его CDP-endpoint не поднялся.
  • 504 start_timeout — Профиль не поднялся за timeoutSeconds.

POST /v1/profiles/{id}/attach-automation

Есть в выпущенной сборке

Получить адрес драйвера для уже открытого профиля

Для профиля, который вы открыли руками и теперь хотите вести скриптом. Ничего не перезапускает: сессия, её вкладки и cookies остаются как есть.

Параметры

idpath · string · обязательный
Идентификатор профиля — такой, каким его возвращают операции каталога.
debugPortbody · integer · необязательный · от 1024 до 65534 · по умолчанию 0
Порт, на котором поднимается CDP-endpoint. Ноль — приложение выберет свободный само; любое другое значение должно быть портом, который ваш пользователь может занять.
timeoutSecondsbody · integer · необязательный · от 1 до 180 · по умолчанию 60
Сколько вызов ждёт, пока профиль поднимется, прежде чем сдаться.
maskAutomationbody · boolean · необязательный · по умолчанию true
Принимается только true. Маскировку автоматизации выключить нельзя: профиль под скриптом — тот же профиль, который сайт видит, когда вы открываете его руками.

Ответ

{
  "id": "<profile-id>",
  "status": "running",
  "cdpDiscoveryUrl": "http://127.0.0.1:48361/devtools/<token>/json/version",
  "cdpBrowserUrl": "ws://127.0.0.1:9222/devtools/browser/<token>",
  "seleniumDebuggerUrl": "127.0.0.1:9222",
  "chromedriverPath": "/Applications/Liminal.app/.../chromedriver",
  "debugPort": 9222,
  "debugAddress": "127.0.0.1:9222",
  "externalIp": "203.0.113.10",
  "countryCode": "DE"
}

Отказы

  • 401 invalid_key — Ключ не передан, испорчен или с тех пор перевыпущен.
  • 403 not_signed_in — В приложение никто не вошёл, а операциям каталога нужен аккаунт.
  • 503 runtime_unavailable — Приложение сейчас не может ни запустить, ни остановить профиль.
  • 404 profile_not_found — Профиля с таким идентификатором в каталоге нет.
  • 409 profile_already_running — Профиль уже открыт, его запуск ещё идёт или выданный раньше адрес драйвера всё ещё действует.
  • 400 invalid_start_options — Тело запроса не JSON-объект.
  • 400 invalid_port — debugPort не равен нулю и не попадает в допустимый диапазон.
  • 400 invalid_start_timeout — timeoutSeconds вне допустимых границ.
  • 400 masking_unavailable — maskAutomation пришёл как false, а такого режима у приложения нет.
  • 409 port_in_use — Запрошенный отладочный порт занят.
  • 409 listen_failed — Отладочный порт не удалось занять по другой причине.
  • 409 start_failed — Профиль не поднялся. Когда приложение может это сказать, безопасное поле detail называет стадию.
  • 409 proxy_unavailable — Профилю нужен его прокси, а прокси не ответил.
  • 502 start_failed — Подготовка профиля не удалась ещё до того, как браузер попросили запуститься.
  • 503 cdp_unavailable — Профиль запустился, но его CDP-endpoint не поднялся.
  • 504 start_timeout — Профиль не поднялся за timeoutSeconds.
  • 409 profile_not_running — Профиль не открыт, подключаться не к чему.

POST /v1/profiles/{id}/stop

Есть в выпущенной сборке

Закрыть профиль так, как закрыло бы его окно

Даёт сессии дописать своё состояние. Это тот вызов, который стоит делать между прогонами скрипта на одном профиле.

Параметры

idpath · string · обязательный
Идентификатор профиля — такой, каким его возвращают операции каталога.

Ответ

{
  "id": "<profile-id>",
  "status": "closed"
}

Отказы

  • 401 invalid_key — Ключ не передан, испорчен или с тех пор перевыпущен.
  • 403 not_signed_in — В приложение никто не вошёл, а операциям каталога нужен аккаунт.
  • 503 runtime_unavailable — Приложение сейчас не может ни запустить, ни остановить профиль.
  • 404 profile_not_found — Профиля с таким идентификатором в каталоге нет.
  • 409 profile_not_running — Профиль не открыт, закрывать нечего.
  • 409 stop_failed — Приложение не смогло закрыть профиль.

POST /v1/profiles/{id}/force-stop

Есть в выпущенной сборке

Закрыть профиль, который не закрывается

Тот же ответ, но без ожидания, пока сессия уляжется. Нужен, когда остановка не вернулась, а не по умолчанию.

Параметры

idpath · string · обязательный
Идентификатор профиля — такой, каким его возвращают операции каталога.

Ответ

{
  "id": "<profile-id>",
  "status": "closed"
}

Отказы

  • 401 invalid_key — Ключ не передан, испорчен или с тех пор перевыпущен.
  • 403 not_signed_in — В приложение никто не вошёл, а операциям каталога нужен аккаунт.
  • 503 runtime_unavailable — Приложение сейчас не может ни запустить, ни остановить профиль.
  • 404 profile_not_found — Профиля с таким идентификатором в каталоге нет.
  • 409 profile_not_running — Профиль не открыт, закрывать нечего.
  • 409 stop_failed — Приложение не смогло закрыть профиль.

GET /v1/running-profiles

Есть в выпущенной сборке

Что открыто прямо сейчас и чем можно управлять

Каждый открытый профиль — окно его открыло или вызов, об этом говорит startedBy — с адресом драйвера, когда он есть. Отсюда скрипт переподключается после своего перезапуска.

Параметры

Нет.

Ответ

{
  "items": [
    {
      "id": "<profile-id>",
      "workspaceId": "<workspace-id>",
      "status": "running",
      "automationAvailable": true,
      "startedBy": "api",
      "cdpBrowserUrl": "ws://127.0.0.1:9222/devtools/browser/<token>",
      "seleniumDebuggerUrl": "127.0.0.1:9222",
      "chromedriverPath": "/Applications/Liminal.app/.../chromedriver",
      "debugPort": 9222,
      "debugAddress": "127.0.0.1:9222"
    }
  ]
}

Отказы

  • 401 invalid_key — Ключ не передан, испорчен или с тех пор перевыпущен.
  • 403 not_signed_in — В приложение никто не вошёл, а операциям каталога нужен аккаунт.
  • 503 catalog_unavailable — Каталог профилей ещё не готов. Спросите снова.

Как вести профиль из своего скрипта

Каждый блок целый: читает файл адреса, вызывает API и подключает драйвер. Скопируйте один, впишите имя своего профиля — и он работает. Значок говорит, отвечает ли сборка, которую можно скачать сегодня, на всё, что нужно этому блоку.

Puppeteer (JavaScript)

Есть в выпущенной сборке

Подключается по CDP к адресу из ответа на запуск. Самое близкое к скрипту, уже написанному под AdsPower или Octo.

Вызывает /v1/profiles · /v1/profiles/{id}/start

import { readFileSync } from 'node:fs'
import puppeteer from 'puppeteer-core'

// Local API/status.json, inside Liminal's user-data directory. The settings
// screen shows the absolute path; that path and the key are your script’s to
// keep — the app puts neither into the environment for you.
const { address } = JSON.parse(readFileSync(process.env.LIMINAL_ADDRESS_FILE, 'utf8'))

async function call(path, init) {
  const response = await fetch(address + path, {
    ...init,
    headers: { Authorization: `Bearer ${process.env.LIMINAL_API_KEY}` },
  })
  const body = await response.json()
  if (!response.ok) throw new Error(body.error.code)
  return body
}

const { items } = await call('/v1/profiles?name=' + encodeURIComponent('Shop EU'))
const started = await call(`/v1/profiles/${items[0].id}/start`, { method: 'POST' })

const browser = await puppeteer.connect({ browserWSEndpoint: started.cdpBrowserUrl })
const page = await browser.newPage()
await page.goto('https://example.com')
console.log(await page.title())
await browser.disconnect()

Playwright (JavaScript)

Есть в выпущенной сборке

То же подключение через точку входа Playwright по CDP — и в контекст, который у профиля уже есть, а не в новый.

Вызывает /v1/profiles · /v1/profiles/{id}/start

import { readFileSync } from 'node:fs'
import { chromium } from 'playwright'

// Local API/status.json, inside Liminal's user-data directory. The settings
// screen shows the absolute path; that path and the key are your script’s to
// keep — the app puts neither into the environment for you.
const { address } = JSON.parse(readFileSync(process.env.LIMINAL_ADDRESS_FILE, 'utf8'))

async function call(path, init) {
  const response = await fetch(address + path, {
    ...init,
    headers: { Authorization: `Bearer ${process.env.LIMINAL_API_KEY}` },
  })
  const body = await response.json()
  if (!response.ok) throw new Error(body.error.code)
  return body
}

const { items } = await call('/v1/profiles?name=' + encodeURIComponent('Shop EU'))
const started = await call(`/v1/profiles/${items[0].id}/start`, { method: 'POST' })

const browser = await chromium.connectOverCDP(started.cdpBrowserUrl)
const page = await browser.contexts()[0].newPage()
await page.goto('https://example.com')
console.log(await page.title())
await browser.close()

Playwright (Python)

Есть в выпущенной сборке

Питоновская сторона того же самого: файл адреса читается без единой зависимости вне стандартной библиотеки.

Вызывает /v1/profiles · /v1/profiles/{id}/start

import json
import os
import urllib.parse
import urllib.request
from playwright.sync_api import sync_playwright

# Local API/status.json, inside Liminal's user-data directory. The settings
# screen shows the absolute path; that path and the key are your script’s to
# keep — the app puts neither into the environment for you.
with open(os.environ["LIMINAL_ADDRESS_FILE"], encoding="utf-8") as handle:
    address = json.load(handle)["address"]


def call(path, method="GET"):
    request = urllib.request.Request(address + path, method=method)
    request.add_header("Authorization", "Bearer " + os.environ["LIMINAL_API_KEY"])
    with urllib.request.urlopen(request) as response:
        return json.load(response)


profile = call("/v1/profiles?name=" + urllib.parse.quote("Shop EU"))["items"][0]
started = call(f"/v1/profiles/{profile['id']}/start", method="POST")

with sync_playwright() as playwright:
    browser = playwright.chromium.connect_over_cdp(started["cdpBrowserUrl"])
    page = browser.contexts[0].new_page()
    page.goto("https://example.com")
    print(page.title())

Selenium (Python)

Есть в выпущенной сборке

Selenium присоединяется к уже запущенному браузеру по адресу отладчика и берёт chromedriver, на который указывает ответ, — ничего не скачивается и не угадывается.

Вызывает /v1/profiles · /v1/profiles/{id}/start

import json
import os
import urllib.parse
import urllib.request
from selenium import webdriver
from selenium.webdriver.chrome.service import Service

# Local API/status.json, inside Liminal's user-data directory. The settings
# screen shows the absolute path; that path and the key are your script’s to
# keep — the app puts neither into the environment for you.
with open(os.environ["LIMINAL_ADDRESS_FILE"], encoding="utf-8") as handle:
    address = json.load(handle)["address"]


def call(path, method="GET"):
    request = urllib.request.Request(address + path, method=method)
    request.add_header("Authorization", "Bearer " + os.environ["LIMINAL_API_KEY"])
    with urllib.request.urlopen(request) as response:
        return json.load(response)


profile = call("/v1/profiles?name=" + urllib.parse.quote("Shop EU"))["items"][0]
started = call(f"/v1/profiles/{profile['id']}/start", method="POST")

options = webdriver.ChromeOptions()
options.debugger_address = started["seleniumDebuggerUrl"]
driver = webdriver.Chrome(service=Service(started["chromedriverPath"]), options=options)
driver.get("https://example.com")
print(driver.title)
driver.quit()

Selenium (JavaScript)

Есть в выпущенной сборке

То же для Node: и адрес отладчика, и путь к подходящему chromedriver приходят из ответа на запуск.

Вызывает /v1/profiles · /v1/profiles/{id}/start

import { readFileSync } from 'node:fs'
import { Builder } from 'selenium-webdriver'
import chrome from 'selenium-webdriver/chrome.js'

// Local API/status.json, inside Liminal's user-data directory. The settings
// screen shows the absolute path; that path and the key are your script’s to
// keep — the app puts neither into the environment for you.
const { address } = JSON.parse(readFileSync(process.env.LIMINAL_ADDRESS_FILE, 'utf8'))

async function call(path, init) {
  const response = await fetch(address + path, {
    ...init,
    headers: { Authorization: `Bearer ${process.env.LIMINAL_API_KEY}` },
  })
  const body = await response.json()
  if (!response.ok) throw new Error(body.error.code)
  return body
}

const { items } = await call('/v1/profiles?name=' + encodeURIComponent('Shop EU'))
const started = await call(`/v1/profiles/${items[0].id}/start`, { method: 'POST' })

const driver = await new Builder()
  .forBrowser('chrome')
  .setChromeOptions(new chrome.Options().debuggerAddress(started.seleniumDebuggerUrl))
  .setChromeService(new chrome.ServiceBuilder(started.chromedriverPath))
  .build()
await driver.get('https://example.com')
console.log(await driver.getTitle())
await driver.quit()

Описание, которое можно импортировать

Тот же контракт файлом, по одному адресу, который не меняется: OpenAPI в JSON, собранный из тех же данных, что рисует эта страница. Импортируйте его в Postman или Insomnia или соберите по нему клиент. У каждой операции в нём стоит та же доступность, что и значок выше.

openapi.json

Выпуск, который его привёз, и каждый следующий, который его расширит, — в списке изменений. Вопрос о контракте или значение, которое не совпало с тем, что делает ваша сборка: [email protected].