До вмісту

ДокументаціяАвтоматизація

Локальний 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 — У застосунок ніхто не увійшов, а операціям каталогу потрібен акаунт.
  • 403 team_closed — Активну команду закрито: продовжте її тариф або виберіть іншу команду.
  • 503 catalog_loading — Каталог профілів ще завантажується. Запитайте знову.
  • 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 — У застосунок ніхто не увійшов, а операціям каталогу потрібен акаунт.
  • 403 team_closed — Активну команду закрито: продовжте її тариф або виберіть іншу команду.
  • 503 catalog_loading — Каталог профілів ще завантажується. Запитайте знову.
  • 503 catalog_unavailable — Застосунок не зміг відкрити каталог профілів.
  • 404 profile_not_found — Профілю з таким ідентифікатором у каталозі немає.

POST /v1/profiles/{id}/start

Є у випущеній збірці

Відкрити профіль і отримати адресу, до якої під’єднується драйвер

Запускає профіль так само, як це робить застосунок, — його відбиток, його проксі, його cookies — і відповідає CDP-адресою, щойно браузер піднявся. Виклик чекає, а не повертає завдання, яке треба опитувати.

Параметри

idpath · string · обов’язковий
Ідентифікатор профілю — такий, яким його повертають операції каталогу.
debugPortbody · integer · необов’язковий · від 1024 до 65534 · типово 0
Порт, на якому піднімається кінцева точка CDP. 0 — застосунок сам вибере вільний; будь-яке інше значення має бути портом, який ваш користувач може зайняти.
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 — Застосунок зараз не може ні запустити, ні зупинити профіль.
  • 403 team_closed — Активну команду закрито: продовжте її тариф або виберіть іншу команду.
  • 503 catalog_loading — Каталог профілів ще завантажується. Запитайте знову.
  • 503 catalog_unavailable — Застосунок не зміг відкрити каталог профілів.
  • 404 profile_not_found — Профілю з таким ідентифікатором у каталозі немає.
  • 409 profile_already_running — Профіль уже відкрито, його запуск ще триває або видана раніше адреса драйвера досі дійсна.
  • 400 invalid_start_options — Тіло запиту не є JSON-об’єктом.
  • 400 invalid_port — debugPort не дорівнює 0 і не є портом із допустимого діапазону.
  • 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 не піднялася.
  • 504 start_timeout — Профіль не піднявся за timeoutSeconds.

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

Є у випущеній збірці

Отримати адресу драйвера для вже відкритого профілю

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

Параметри

idpath · string · обов’язковий
Ідентифікатор профілю — такий, яким його повертають операції каталогу.
debugPortbody · integer · необов’язковий · від 1024 до 65534 · типово 0
Порт, на якому піднімається кінцева точка CDP. 0 — застосунок сам вибере вільний; будь-яке інше значення має бути портом, який ваш користувач може зайняти.
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 — Застосунок зараз не може ні запустити, ні зупинити профіль.
  • 403 team_closed — Активну команду закрито: продовжте її тариф або виберіть іншу команду.
  • 503 catalog_loading — Каталог профілів ще завантажується. Запитайте знову.
  • 503 catalog_unavailable — Застосунок не зміг відкрити каталог профілів.
  • 404 profile_not_found — Профілю з таким ідентифікатором у каталозі немає.
  • 409 profile_already_running — Профіль уже відкрито, його запуск ще триває або видана раніше адреса драйвера досі дійсна.
  • 400 invalid_start_options — Тіло запиту не є JSON-об’єктом.
  • 400 invalid_port — debugPort не дорівнює 0 і не є портом із допустимого діапазону.
  • 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 не піднялася.
  • 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 — Застосунок зараз не може ні запустити, ні зупинити профіль.
  • 403 team_closed — Активну команду закрито: продовжте її тариф або виберіть іншу команду.
  • 503 catalog_loading — Каталог профілів ще завантажується. Запитайте знову.
  • 503 catalog_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 — Застосунок зараз не може ні запустити, ні зупинити профіль.
  • 403 team_closed — Активну команду закрито: продовжте її тариф або виберіть іншу команду.
  • 503 catalog_loading — Каталог профілів ще завантажується. Запитайте знову.
  • 503 catalog_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 — У застосунок ніхто не увійшов, а операціям каталогу потрібен акаунт.
  • 403 team_closed — Активну команду закрито: продовжте її тариф або виберіть іншу команду.
  • 503 catalog_loading — Каталог профілів ще завантажується. Запитайте знову.
  • 503 catalog_unavailable — Застосунок не зміг відкрити каталог профілів.

Як вести профіль із власного скрипта

Кожен блок цілісний: читає файл адреси, викликає API і під’єднує драйвер. Скопіюйте один, впишіть назву свого профілю — і він працює. Позначка каже, чи відповідає збірка, яку можна завантажити сьогодні, на все, що потрібно цьому блоку.

Puppeteer (JavaScript)

Є у випущеній збірці

Під’єднується через CDP до адреси з відповіді на запуск — так скрипт Puppeteer дістається до браузера, який уже працює.

Викликає /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)

Є у випущеній збірці

Той самий приклад мовою 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].