Ir para o conteúdo

DocumentaçãoAutomação

API local

Última verificação com o contrato: setembro de 2026

Lançada, e já na versão que você pode baixar hoje — tanto no macOS quanto no Windows. O interruptor fica nas configurações do app, e a API permanece desligada até você ligá-la. A quais chamadas essa versão responde está marcado em cada operação e em cada exemplo abaixo, em vez de escrito aqui em uma frase que o próximo lançamento deixaria errada.

Um script na mesma máquina transforma o navegador de um perfil em algo que ele pode conduzir. A interface é um serviço, uma chave e um arquivo de endereço — descritos primeiro, porque tudo se apoia neles — e um punhado de rotas por cima: encontrar um perfil, abri-lo, conectar o Puppeteer, o Playwright ou o Selenium ao endereço com que ele responde e fechá-lo de novo.

A interface do app existe apenas em inglês e em russo. Os botões e as configurações são citados aqui como a interface em inglês os mostra.

Como ativar

Desligada por padrão e acessível apenas a partir deste computador.

O interruptor
Abra Settings → API & Automation e ative Local API. Até você fazer isso, nada escuta: a porta não é aberta, e um script recebe uma conexão recusada em vez de uma resposta vazia.
Somente este computador
O serviço aceita conexões em 127.0.0.1 e em nenhum outro lugar. Outra máquina da sua rede não consegue alcançá-lo, e não existe configuração que o abra — um script roda ao lado do Liminal, não contra ele pela rede.
A porta
O padrão é 48361, uma porta própria do Liminal para não colidir com outro navegador que já esteja escutando na máquina. Digite outra ou pressione Random free port e deixe o app escolher. A mudança vale na hora, sem reiniciar o Liminal.
Quando a porta está ocupada
Se a porta não puder ser aberta, a tela mostra Port in use e o arquivo de endereço informa port_in_use. Nada escuta até você escolher outra porta, então um script que lê o arquivo sabe a diferença entre “desligada” e “não conseguiu iniciar”.

A chave

Uma chave, mostrada uma única vez, guardada onde o seu sistema operacional guarda segredos.

Copie quando ela aparecer
Ativar a API emite uma chave de 64 caracteres hexadecimais minúsculos e a mostra uma única vez. Saia da tela e ela fica oculta: o app não consegue mostrá-la de novo, só emitir uma nova.
Onde ela fica guardada
No Keychain no macOS e no Credential Manager no Windows — nunca em um arquivo de configurações, em um snapshot de sincronização ou em um log, e nunca no arquivo de endereço abaixo. Guarde a sua cópia como você guarda qualquer outra credencial que o seu script usa.
Como revogar
Regenerate key emite uma nova chave e deixa de aceitar a antiga imediatamente, sem reiniciar o app e sem mudar a porta. Um script que ainda tenha a chave antiga é recusado a partir da requisição seguinte.
Como enviar
Toda requisição leva a chave em um cabeçalho: Authorization: Bearer seguido da própria chave. Não há parâmetro de consulta nem cookie, e uma requisição sem o cabeçalho é recusada como uma requisição com a chave errada.

Onde um script encontra o endereço

O app grava o seu endereço em um arquivo, para que nada precise ser digitado duas vezes.

O arquivo
A tela de configurações mostra o caminho absoluto de Local API/status.json dentro do diretório de dados de usuário do Liminal. Leia-o em vez de fixar uma porta no código: ele acompanha a porta que você definiu e a que o Random free port escolheu.
O que ele contém
Quatro campos — enabled, status, address, port — e nenhum segredo de nenhum tipo.
{"enabled":true,"status":"running","address":"http://127.0.0.1:48361","port":48361}
Quando a API está desligada
O arquivo continua onde está e diz isso: off, com o endereço vazio e sem porta. A mesma forma, outro estado — um script lê um único arquivo para saber tudo.
{"enabled":false,"status":"off","address":"","port":0}
Ele é substituído por inteiro, nunca fica pela metade
Toda mudança — ligar ou desligar a API, uma nova porta, uma porta que não pôde ser aberta — regrava o arquivo de forma atômica. Um script que o lê durante uma mudança vê o conteúdo antigo ou o novo, nunca uma linha truncada.

A requisição de status

A chamada por onde começar: se o Liminal está em execução e se alguém entrou na conta.

A chamada
Um GET em /v1/status no endereço do arquivo, com a chave no cabeçalho.
curl -H 'Authorization: Bearer <key>' http://127.0.0.1:48361/v1/status
A resposta
HTTP 200, com a versão do app em execução e a informação de se alguém entrou na conta no app. Um signedIn falso não é um erro — a API responde; são as operações que leem o catálogo ou abrem um perfil que precisam de uma conta.
{"status":"running","version":"<version>","signedIn":true}
Uma chave ausente ou revogada
HTTP 401 e o código invalid_key — permanente, e é por ele que se deve ramificar. A mensagem ao lado é a redação do próprio app para uma pessoa que lê um log; trate-a como texto que pode mudar, não como parte do contrato.
{"error":{"code":"invalid_key","message":"Неверный ключ"}}
Use o endereço exatamente como está no arquivo
Use 127.0.0.1 e a porta do arquivo. Uma requisição que chega nomeando qualquer outro host não é respondida, mesmo quando alcança a mesma porta — o serviço responde apenas ao seu próprio endereço.
O que um Liminal mais antigo faz
Nada: nenhuma porta é aberta, então um script que se conecta é recusado. Essa recusa é como um script distingue uma versão sem a API local de uma que a tem desligada — no segundo caso o arquivo de endereço existe e diz off.

O que esta versão faz e o que não faz

Dito com clareza, para que nada aqui pareça uma promessa que não é.

Uma operação chega a esta página antes de chegar à sua versão
A interface cresce a cada lançamento, e uma chamada já escrita é documentada aqui antes da versão que a responde — marcada, nunca escondida. Então leia a marca, não a promessa: ela está em cada operação e em cada exemplo abaixo, e é o que diz se aquilo que você vai automatizar funciona no app que você tem.
Para quais planos
Todos os planos, inclusive o gratuito. A API local não é um nível pago: um script na sua própria máquina é como muita gente usa um navegador antidetecção, e colocá-la atrás de um plano seria cobrar um pedágio por isso.Planos
Com que frequência um script pode perguntar
Não há cota ligada ao seu plano. O serviço se protege de enxurradas de requisições, o que um script escrito para ele não vai notar.
Um perfil sob automação é o mesmo perfil
Ele mantém a impressão digital, o proxy e os cookies, e chega à rede pelo seu proxy exatamente como quando você o abre à mão. A API é uma porta de entrada, não um segundo modo do navegador.Dados e segurança

Cada operação, uma por uma

Para que serve cada rota, o que ela recebe, o que responde e toda recusa que pode dar. O selo ao lado de uma operação diz se a versão que você pode baixar hoje a responde.

GET /v1/status

Na versão lançada

Se o Liminal está em execução e se alguém entrou na conta

A chamada mais barata que existe, e a que vale repetir enquanto você espera pelo app. Ela responde sem conta: um signedIn falso é um estado, não um erro.

Parâmetros

Nenhum.

Resposta

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

Recusas

  • 401 invalid_key — A chave está ausente, malformada ou foi gerada de novo desde então.

GET /v1/profiles

Na versão lançada

Os perfis do catálogo, filtrados e paginados

Lê o mesmo catálogo que o app mostra. Os filtros se combinam, e todos são exatos, menos name, que corresponde a qualquer perfil cujo nome contenha o que você digitou, sem diferenciar maiúsculas de minúsculas.

Parâmetros

pagequery · integer · opcional · de 1 a 1000000 · padrão: 1
Qual página ler, contando a partir da primeira.
pageSizequery · integer · opcional · de 1 a 100 · padrão: 100
Quantos perfis uma página contém. O teto é o valor padrão.
workspaceIdquery · string · opcional
Somente os perfis desse espaço de trabalho.
folderIdquery · string · opcional
Somente os perfis dessa pasta.
statusquery · string · opcional
Somente os perfis nesse estado; running é o estado de um perfil aberto.
namequery · string · opcional
Somente os perfis cujo nome contenha isto, sem diferenciar maiúsculas de minúsculas.

Resposta

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

Recusas

  • 401 invalid_key — A chave está ausente, malformada ou foi gerada de novo desde então.
  • 403 not_signed_in — Ninguém entrou na conta no app; as operações do catálogo precisam de uma conta.
  • 403 team_closed — A equipe ativa está fechada: renove o plano dela ou mude para outra equipe.
  • 503 catalog_loading — O catálogo de perfis ainda está carregando. Pergunte de novo.
  • 503 catalog_unavailable — O app não conseguiu abrir o catálogo de perfis.
  • 400 invalid_pagination — page ou pageSize está fora do intervalo aceito.

GET /v1/profiles/{id}

Na versão lançada

Um perfil, com o proxy e a impressão digital dele

O mesmo objeto que a lista retorna, para um único identificador. O endereço do proxy é mostrado; a senha dele, nunca.

Parâmetros

idpath · string · obrigatório
O identificador do perfil, como as operações do catálogo o retornam.

Resposta

{
  "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"
}

Recusas

  • 401 invalid_key — A chave está ausente, malformada ou foi gerada de novo desde então.
  • 403 not_signed_in — Ninguém entrou na conta no app; as operações do catálogo precisam de uma conta.
  • 403 team_closed — A equipe ativa está fechada: renove o plano dela ou mude para outra equipe.
  • 503 catalog_loading — O catálogo de perfis ainda está carregando. Pergunte de novo.
  • 503 catalog_unavailable — O app não conseguiu abrir o catálogo de perfis.
  • 404 profile_not_found — Não há nenhum perfil com esse identificador no catálogo.

POST /v1/profiles/{id}/start

Na versão lançada

Abrir um perfil e obter o endereço ao qual um driver se conecta

Inicia o perfil como o app faz — a impressão digital, o proxy, os cookies dele — e responde com o endereço CDP assim que o navegador está no ar. A chamada espera; ela não retorna uma tarefa para consultar depois.

Parâmetros

idpath · string · obrigatório
O identificador do perfil, como as operações do catálogo o retornam.
debugPortbody · integer · opcional · de 1024 a 65534 · padrão: 0
A porta à qual o endpoint CDP se vincula. Com 0 o app escolhe uma livre; qualquer outro valor precisa ser uma porta que o seu usuário consiga abrir.
timeoutSecondsbody · integer · opcional · de 1 a 180 · padrão: 60
Quanto tempo a chamada espera o perfil subir antes de desistir.
maskAutomationbody · boolean · opcional · padrão: true
Somente true é aceito. O mascaramento da automação não pode ser desligado: um perfil sob um script é o mesmo perfil que um site vê quando você o abre à mão.

Resposta

{
  "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"
}

Recusas

  • 401 invalid_key — A chave está ausente, malformada ou foi gerada de novo desde então.
  • 403 not_signed_in — Ninguém entrou na conta no app; as operações do catálogo precisam de uma conta.
  • 503 runtime_unavailable — O app não consegue iniciar nem parar um perfil neste momento.
  • 403 team_closed — A equipe ativa está fechada: renove o plano dela ou mude para outra equipe.
  • 503 catalog_loading — O catálogo de perfis ainda está carregando. Pergunte de novo.
  • 503 catalog_unavailable — O app não conseguiu abrir o catálogo de perfis.
  • 404 profile_not_found — Não há nenhum perfil com esse identificador no catálogo.
  • 409 profile_already_running — O perfil já está aberto, a inicialização dele ainda está em andamento ou um endereço de driver entregue antes continua válido.
  • 400 invalid_start_options — O corpo não é um objeto JSON.
  • 400 invalid_port — debugPort não é 0 nem uma porta do intervalo aceito.
  • 400 invalid_start_timeout — timeoutSeconds está fora do intervalo aceito.
  • 400 masking_unavailable — maskAutomation foi enviado como false, o que o app não oferece.
  • 409 port_in_use — A porta de depuração que você pediu está ocupada.
  • 409 listen_failed — A porta de depuração não pôde ser aberta por outro motivo.
  • 409 start_failed — O perfil não subiu. Um detail seguro nomeia a etapa quando o app consegue dizê-la.
  • 409 proxy_unavailable — O perfil exige o proxy dele, e o proxy não respondeu.
  • 502 start_failed — A preparação do perfil falhou antes de o navegador ser solicitado a iniciar.
  • 503 cdp_unavailable — O perfil iniciou, mas o endpoint CDP dele não subiu.
  • 504 start_timeout — O perfil não subiu dentro de timeoutSeconds.

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

Na versão lançada

Obter um endereço de driver para um perfil que já está aberto

Para um perfil que você abriu à mão e agora quer conduzir. Não reinicia nada: a sessão, as abas e os cookies ficam como estão.

Parâmetros

idpath · string · obrigatório
O identificador do perfil, como as operações do catálogo o retornam.
debugPortbody · integer · opcional · de 1024 a 65534 · padrão: 0
A porta à qual o endpoint CDP se vincula. Com 0 o app escolhe uma livre; qualquer outro valor precisa ser uma porta que o seu usuário consiga abrir.
timeoutSecondsbody · integer · opcional · de 1 a 180 · padrão: 60
Quanto tempo a chamada espera o perfil subir antes de desistir.
maskAutomationbody · boolean · opcional · padrão: true
Somente true é aceito. O mascaramento da automação não pode ser desligado: um perfil sob um script é o mesmo perfil que um site vê quando você o abre à mão.

Resposta

{
  "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"
}

Recusas

  • 401 invalid_key — A chave está ausente, malformada ou foi gerada de novo desde então.
  • 403 not_signed_in — Ninguém entrou na conta no app; as operações do catálogo precisam de uma conta.
  • 503 runtime_unavailable — O app não consegue iniciar nem parar um perfil neste momento.
  • 403 team_closed — A equipe ativa está fechada: renove o plano dela ou mude para outra equipe.
  • 503 catalog_loading — O catálogo de perfis ainda está carregando. Pergunte de novo.
  • 503 catalog_unavailable — O app não conseguiu abrir o catálogo de perfis.
  • 404 profile_not_found — Não há nenhum perfil com esse identificador no catálogo.
  • 409 profile_already_running — O perfil já está aberto, a inicialização dele ainda está em andamento ou um endereço de driver entregue antes continua válido.
  • 400 invalid_start_options — O corpo não é um objeto JSON.
  • 400 invalid_port — debugPort não é 0 nem uma porta do intervalo aceito.
  • 400 invalid_start_timeout — timeoutSeconds está fora do intervalo aceito.
  • 400 masking_unavailable — maskAutomation foi enviado como false, o que o app não oferece.
  • 409 port_in_use — A porta de depuração que você pediu está ocupada.
  • 409 listen_failed — A porta de depuração não pôde ser aberta por outro motivo.
  • 409 start_failed — O perfil não subiu. Um detail seguro nomeia a etapa quando o app consegue dizê-la.
  • 409 proxy_unavailable — O perfil exige o proxy dele, e o proxy não respondeu.
  • 502 start_failed — A preparação do perfil falhou antes de o navegador ser solicitado a iniciar.
  • 503 cdp_unavailable — O perfil iniciou, mas o endpoint CDP dele não subiu.
  • 504 start_timeout — O perfil não subiu dentro de timeoutSeconds.
  • 409 profile_not_running — O perfil não está aberto, então não há nada a que se conectar.

POST /v1/profiles/{id}/stop

Na versão lançada

Fechar um perfil como a janela dele faria

Deixa a sessão terminar de gravar o estado. É a chamada a fazer entre execuções automatizadas no mesmo perfil.

Parâmetros

idpath · string · obrigatório
O identificador do perfil, como as operações do catálogo o retornam.

Resposta

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

Recusas

  • 401 invalid_key — A chave está ausente, malformada ou foi gerada de novo desde então.
  • 403 not_signed_in — Ninguém entrou na conta no app; as operações do catálogo precisam de uma conta.
  • 503 runtime_unavailable — O app não consegue iniciar nem parar um perfil neste momento.
  • 403 team_closed — A equipe ativa está fechada: renove o plano dela ou mude para outra equipe.
  • 503 catalog_loading — O catálogo de perfis ainda está carregando. Pergunte de novo.
  • 503 catalog_unavailable — O app não conseguiu abrir o catálogo de perfis.
  • 404 profile_not_found — Não há nenhum perfil com esse identificador no catálogo.
  • 409 profile_not_running — O perfil não está aberto, então não há nada para fechar.
  • 409 stop_failed — O app não conseguiu fechar o perfil.

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

Na versão lançada

Fechar um perfil que não fecha

A mesma resposta, sem esperar a sessão se acomodar. Recorra a ela quando uma parada não retornou, não por hábito.

Parâmetros

idpath · string · obrigatório
O identificador do perfil, como as operações do catálogo o retornam.

Resposta

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

Recusas

  • 401 invalid_key — A chave está ausente, malformada ou foi gerada de novo desde então.
  • 403 not_signed_in — Ninguém entrou na conta no app; as operações do catálogo precisam de uma conta.
  • 503 runtime_unavailable — O app não consegue iniciar nem parar um perfil neste momento.
  • 403 team_closed — A equipe ativa está fechada: renove o plano dela ou mude para outra equipe.
  • 503 catalog_loading — O catálogo de perfis ainda está carregando. Pergunte de novo.
  • 503 catalog_unavailable — O app não conseguiu abrir o catálogo de perfis.
  • 404 profile_not_found — Não há nenhum perfil com esse identificador no catálogo.
  • 409 profile_not_running — O perfil não está aberto, então não há nada para fechar.
  • 409 stop_failed — O app não conseguiu fechar o perfil.

GET /v1/running-profiles

Na versão lançada

O que está aberto agora e o que pode ser conduzido

Todo perfil que está aberto, tenha sido aberto por uma janela ou por uma chamada — startedBy diz qual —, com o endereço de driver quando há um disponível. Reconecte-se a partir daqui depois que o seu script reiniciar.

Parâmetros

Nenhum.

Resposta

{
  "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"
    }
  ]
}

Recusas

  • 401 invalid_key — A chave está ausente, malformada ou foi gerada de novo desde então.
  • 403 not_signed_in — Ninguém entrou na conta no app; as operações do catálogo precisam de uma conta.
  • 403 team_closed — A equipe ativa está fechada: renove o plano dela ou mude para outra equipe.
  • 503 catalog_loading — O catálogo de perfis ainda está carregando. Pergunte de novo.
  • 503 catalog_unavailable — O app não conseguiu abrir o catálogo de perfis.

Conduzir um perfil a partir do seu próprio script

Cada bloco é completo: lê o arquivo de endereço, chama a API e conecta o driver. Copie um, coloque o nome do seu próprio perfil e ele roda. O selo diz se a versão que você pode baixar hoje responde a tudo de que esse bloco precisa.

Puppeteer (JavaScript)

Na versão lançada

Conecta-se por CDP ao endereço com que a inicialização respondeu — do jeito que um script do Puppeteer alcança um navegador que já está em execução.

Chama /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)

Na versão lançada

A mesma conexão pela entrada CDP do Playwright, no contexto que o perfil já tem, e não em um novo.

Chama /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)

Na versão lançada

O lado Python da mesma coisa: lê o arquivo de endereço sem nada fora da biblioteca padrão.

Chama /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)

Na versão lançada

O Selenium se anexa a um navegador que já está em execução, pelo endereço do depurador, e usa o chromedriver para o qual a resposta aponta — nada é baixado e nada é adivinhado.

Chama /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)

Na versão lançada

O mesmo para Node: tanto o endereço do depurador quanto o caminho de um chromedriver compatível vêm da resposta da inicialização.

Chama /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()

A descrição que uma ferramenta pode importar

O mesmo contrato como arquivo, em um endereço que não muda: OpenAPI, em JSON, gerado a partir dos dados que esta página exibe. Importe-o no Postman ou no Insomnia, ou gere um cliente a partir dele. Cada operação nele traz a mesma disponibilidade do selo acima.

openapi.json

O lançamento que a trouxe, e cada lançamento que a amplia, estão nas novidades. Uma dúvida sobre o contrato, ou um valor daqui que não bate com o que a sua versão faz: [email protected].