DocumentaçãoAutomação
Automatize com scripts
Um script no mesmo computador pode abrir um perfil e assumir o navegador dele. O perfil que ele recebe é o mesmo que você abre manualmente — a mesma impressão digital, a mesma conexão, os mesmos cookies.
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.
O que é a API local
A API local é um pequeno serviço dentro do app. Ela responde apenas neste computador e apenas a solicitações que levam a sua chave. Com ela, um script pode listar perfis, iniciá-los e pará-los, e receber o endereço em que uma biblioteca de automação se conecta ao navegador iniciado.
- Todos os planos, inclusive o gratuito.
- Apenas este computador: ela escuta em
127.0.0.1e não pode ser aberta para a rede. - Desativada até você ativar.
- Os perfis são criados no app. A API executa os perfis que você tem; ela não os cria nem os edita.
Ative
- Abra Settings › API & Automation e ative Local API. A linha ao lado muda para Running on this computer only.
- Mantenha a Port —
48361, se você não a alterou — ou clique em Random free port. - Copie a API key agora com Copy: a chave em si nunca é exibida e, depois que você sai da página, não pode mais ser copiada — apenas substituída. Ela tem 64 caracteres.

- Address
- Onde o serviço responde, com um botão Copy — por padrão,
http://127.0.0.1:48361. - Regenerate…
- Cria uma chave nova. A antiga para de funcionar na hora, assim como os endereços de navegador fornecidos com ela.
- API documentation
- Abre a referência.
Nota. A chave fica no armazenamento de credenciais do sistema, não em um arquivo. Um script deve ler o endereço do arquivo que o app grava — Local API/status.json na pasta de dados do Liminal — em vez de fixar uma porta no código; a referência explica onde ele fica.
A primeira solicitação
Toda solicitação leva a chave em um cabeçalho. Pergunte ao serviço se ele está no ar:
curl -H 'Authorization: Bearer <key>' http://127.0.0.1:48361/v1/status{"status":"running","version":"<version>","signedIn":true}signedIn informa se alguém está conectado ao app. As operações que mexem em perfis precisam disso: sem isso, respondem not_signed_in.
Encontre um perfil e inicie-o
Liste os perfis — a resposta vem em páginas e pode ser filtrada por nome, espaço de trabalho, pasta e status:
curl -H 'Authorization: Bearer <key>' http://127.0.0.1:48361/v1/profilesPegue o id do perfil desejado e inicie-o:
curl -X POST -H 'Authorization: Bearer <key>' http://127.0.0.1:48361/v1/profiles/<profile-id>/start{
"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"
}cdpBrowserUrl- O endereço ao qual o Puppeteer e o Playwright se conectam.
seleniumDebuggerUrl,chromedriverPath- O que o Selenium precisa: o endereço do depurador e o driver compatível com o navegador, que vem com o app.
externalIp,countryCode- Como estava a conexão do perfil quando ele foi iniciado.
O app inicia o perfil como se você tivesse clicado em Open. Um perfil que já está aberto não é iniciado de novo — a referência descreve como se conectar a um perfil em execução.
Conecte a sua biblioteca
Cada exemplo é completo: lê o arquivo de endereço, encontra um perfil pelo nome, inicia-o e abre uma página.
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()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()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())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()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()Dica. Conecte-se ao navegador que o perfil já tem. Não deixe a biblioteca abrir um navegador próprio — esse não teria a impressão digital nem os cookies do perfil.
Pare o perfil
curl -X POST -H 'Authorization: Bearer <key>' http://127.0.0.1:48361/v1/profiles/<profile-id>/stopParar fecha o perfil do mesmo jeito que Close. Há também uma parada forçada para um perfil que não fecha.
Bom saber
| Pergunta | Resposta |
|---|---|
| Um script vê um navegador diferente do que eu vejo? | Não. A impressão digital e a conexão são aplicadas exatamente como quando você abre o perfil manualmente, e as mesmas proteções valem. |
| Os sites percebem que o navegador é automatizado? | Os sinais comuns de automação são mascarados em um perfil iniciado pela API. Isso não pode ser desativado. |
| Existe um modo headless? | Não. A API inicia um perfil do mesmo jeito que o app. |
| Há um limite de solicitações? | Nenhuma cota está ligada ao plano. |
| Existe uma ferramenta de linha de comando ou cenários gravados? | Não. A API local é o caminho para automatizar. |
| O que recebo quando algo dá errado? | Um status e um code curto para tratar no código — invalid_key, not_signed_in, profile_already_running e outros. A referência os lista por operação. |
Conferido com o app lançado: outubro de 2026. Se o app faz algo diferente do que esta página diz, a página está errada — escreva para [email protected]. O que mudou em cada versão está em Novidades.