Kalkan logoKalkan
Руководство пользователя

API

Kalkan Local API позволяет вашим собственным скриптам управлять профилями, прокси и папками, запускать и останавливать браузеры и управлять ими через Playwright, Puppeteer или Selenium. Это REST API, который предоставляет запущенное настольное приложение Kalkan на вашем компьютере:

http://127.0.0.1:50360/api/v1

Все запросы и ответы передаются в формате JSON. API входит в тарифы Starter, Pro и Unlimited (кроме пробного периода).

Включение и получение ключа

  1. Откройте Kalkan → Настройки → API и включите API.
  2. Измените порт, если 50360 на вашем компьютере занят.
  3. Скопируйте ключ API (он начинается с kk_). Кнопка Перевыпустить создаёт новый ключ, а старый сразу перестаёт работать.

API доступен, пока открыто приложение Kalkan. Для серверов и скриптов без окна есть также команда для разработчиков adb api, которая предоставляет тот же API.

Аутентификация и безопасность

  • Передавайте ключ в каждом запросе в заголовке X-Token: <key>. Также принимается Authorization: Bearer <key>.
  • При отсутствии ключа или неверном ключе возвращается 401 unauthorized. После 10 неверных ключей в течение минуты API отвечает 429 locked в течение 60 секунд.
  • API слушает только 127.0.0.1 и отвечает только на запросы, адресованные 127.0.0.1 или localhost (иначе 403 forbidden_host). С других компьютеров он недоступен.
  • API предназначен для скриптов, а не для веб-страниц: запросы с заголовком Origin (его отправляют браузеры) отклоняются с 403 forbidden_origin, CORS не поддерживается.
  • Ключ — единственный секрет. Не добавляйте его в репозитории кода; если он утёк, перевыпустите его.
  • Если API не входит в ваш тариф, любой вызов возвращает 403 plan. GET /status работает всегда и в поле api_allowed сообщает, входит ли API в ваш тариф.

Ответы и ошибки

Успешный вызов возвращает {"ok": true, "data": …}; неудачный — {"ok": false, "error": {"code": "…", "message": "…"}}.

{"ok": true, "data": {"id": "…", "running": true, "ws": "ws://127.0.0.1:49731/devtools/browser/…", "debug_port": 49731, "pid": 12345}}

{"ok": false, "error": {"code": "unauthorized", "message": "missing or wrong API key (send it as the X-Token header)"}}
СтатусКодыЗначение
200 / 201 / 202Выполнено / создано / запущено в фоне (wait: false)
400bad_requestОтсутствует или некорректно поле
401unauthorizedКлюч API отсутствует или неверен
403plan, forbidden_host, forbidden_originТариф без API, неверный Host, запрос с веб-страницы
404 / 405not_found, method_not_allowedНет такого профиля / прокси / эндпоинта или неверный HTTP-метод
409running, direct, ledger, cross_osПрофиль открыт, или для запуска требуется решение. direct: у профиля нет прокси (повторите с allow_direct: true). ledger: выходной IP прокси недавно использовался другими профилями; cross_os: ОС профиля не подходит для этого движка на данном компьютере (повторите с force: true). В сообщении поясняется конкретный случай.
429lockedСлишком много неверных ключей, подождите 60 секунд
500internal, launch_failedНепредвиденная ошибка или браузер не удалось запустить
504timeoutБраузер не запустился в течение timeout

Движки

В Kalkan четыре движка: Kalkan Native (рекомендуется; наш собственный Chromium с более чем 150 патчами на C++, которые формируют отпечаток прямо в его коде; приложение устанавливает и обновляет его в фоне), Kalkan (патч Chrome: ваш настоящий Google Chrome с модифицированным драйвером), Kalkan Edge (тот же слой на установленном Microsoft Edge; при создании "engine": "edge") и Firefox (профили Windows, macOS и Linux). Профили Kalkan, Kalkan Edge и Kalkan Native запускаются с эндпоинтом DevTools (CDP) для автоматизации; профили Firefox запускаются без него (ws равен null).

Эндпоинты

Все пути указаны относительно http://127.0.0.1:50360/api/v1.

ЗапросЧто делает
GET /statusСостояние приложения и API: app, version, api, api_allowed, plan, running (число открытых профилей).
GET /profilesСписок профилей. Фильтры: folder, q (поиск), tag, engine (значение engine, которое профиль сообщает в этом списке), proxy (yes | no), status (running | used | new | frozen). Сортировка: sort (name | created | last_used | folder | engine | proxy), desc=1. Пагинация: page, page_size (по умолчанию 100, максимум 500). Возвращает {total, page, page_size, items}.
POST /profilesСоздаёт профиль (201). Поля: name (обязательно), engine, os (windows | macos | linux), mode (native | balanced | maximum | gmail | custom; рекомендуется balanced), folder, tags, notes, start_url, color. Прокси: объект {type, host, port, username, password} (type: http | https | socks5), или URL вида "socks5://host:port" с proxy_user / proxy_pass, или pool_id (id сохранённого прокси или "auto").
GET /profiles/{id}Один профиль со всеми данными. Пароли прокси никогда не возвращаются.
PATCH /profiles/{id}Изменяет name, notes, tags, color, start_url, folder, allow_direct, frozen, extensions, bookmarks, proxy (+ proxy_user, proxy_pass). Поля отпечатка и устройства изменить нельзя (400).
DELETE /profiles/{id}Удаляет профиль (409, пока он открыт).
POST /profiles/{id}/cloneКопирует профиль, при необходимости с новым name (201).
POST /profiles/{id}/startОткрывает профиль. Необязательные параметры: url, allow_direct, force, cdp (по умолчанию true), wait (по умолчанию true), timeout в секундах (5–180, по умолчанию 60). Возвращает id, name, engine, running, already_running, ws, cdp, debug_port, http, pid. При wait: false сразу отвечает 202 {starting: true}.
POST /profiles/{id}/stopЗакрывает профиль: {stopped}.
GET /profiles/{id}/statusrunning, ws, cdp, debug_port, warnings, errors.
GET /profiles/{id}/cookiesЭкспортирует cookies, ?format=json (по умолчанию) или netscape. Профиль должен быть закрыт (иначе 409).
POST /profiles/{id}/cookiesИмпортирует {cookies: …} в виде JSON-списка или текста Netscape и возвращает количество импортированных. Профиль должен быть закрыт.
PUT /profiles/{id}/proxyЗадаёт прокси: {proxy_id} для сохранённого прокси, {proxy: {…}} для нового или {proxy: null}, чтобы удалить его.
GET /runningОткрытые профили: [{id, engine, cdp, debug_port, pid}].
POST /stop-allЗакрывает все открытые профили: {stopped: [ids]}.
GET /folders
POST /folders
Список папок / создание папки с {name, color?} (201).
GET /proxiesСохранённые прокси (пароли никогда не возвращаются).
POST /proxiesДобавляет прокси (201): {text: "…"} по одному прокси на строку или {proxies: [...], scheme?, label?}.
DELETE /proxies/{id}Удаляет сохранённый прокси. Прокси, привязанный к профилям, удаляется только с ?force=true (эти профили остаются без прокси).
POST /proxies/{id}/checkПроверяет прокси: выходной IP, страна, тип, задержка, что видят сайты-детекторы, и репутация.

Примеры: curl

В PowerShell вводите curl.exe, так как curl там является псевдонимом Invoke-WebRequest. JSON-тела проще всего отправлять из Git Bash, cmd или скрипта.

curl -H "X-Token: kk_..." http://127.0.0.1:50360/api/v1/status

curl -H "X-Token: kk_..." "http://127.0.0.1:50360/api/v1/profiles?status=running&sort=last_used&desc=1"

curl -X POST -H "X-Token: kk_..." -H "Content-Type: application/json" \
     -d '{"name": "shop-01", "os": "windows", "mode": "balanced", "proxy": "socks5://1.2.3.4:1080", "proxy_user": "user", "proxy_pass": "pass"}' \
     http://127.0.0.1:50360/api/v1/profiles

curl -X POST -H "X-Token: kk_..." -H "Content-Type: application/json" \
     -d '{"url": "https://example.com"}' \
     http://127.0.0.1:50360/api/v1/profiles/PROFILE_ID/start

Примеры: Python + Playwright

Создаёт профиль с прокси, открывает его и подключается через connect_over_cdp (pip install requests playwright).

import requests
from playwright.sync_api import sync_playwright

API = "http://127.0.0.1:50360/api/v1"
H = {"X-Token": "kk_..."}

def call(method, path, **kw):
    r = requests.request(method, API + path, headers=H, timeout=200, **kw).json()
    if not r["ok"]:
        raise RuntimeError(r["error"]["code"] + ": " + r["error"]["message"])
    return r["data"]

prof = call("POST", "/profiles", json={
    "name": "shop-01", "os": "windows", "mode": "balanced",
    "proxy": {"type": "socks5", "host": "1.2.3.4", "port": 1080,
              "username": "user", "password": "pass"},
})
run = call("POST", f"/profiles/{prof['id']}/start")

with sync_playwright() as pw:
    browser = pw.chromium.connect_over_cdp(run["ws"])
    ctx = browser.contexts[0]                  # the profile's own context
    page = ctx.pages[0] if ctx.pages else ctx.new_page()
    page.goto("https://example.com")
    print(page.title())

call("POST", f"/profiles/{prof['id']}/stop")

Примеры: Node.js + Puppeteer

Используйте puppeteer-core: браузер запускает Kalkan, а Puppeteer только подключается к нему.

const puppeteer = require("puppeteer-core");

const API = "http://127.0.0.1:50360/api/v1";
const H = { "X-Token": "kk_...", "Content-Type": "application/json" };

(async () => {
  const res = await fetch(`${API}/profiles/PROFILE_ID/start`, { method: "POST", headers: H, body: "{}" });
  const { ok, data, error } = await res.json();
  if (!ok) throw new Error(`${error.code}: ${error.message}`);

  const browser = await puppeteer.connect({ browserWSEndpoint: data.ws, defaultViewport: null });
  const [page] = await browser.pages();
  await page.goto("https://example.com");
  console.log(await page.title());
  await browser.disconnect();
})();

Примеры: Selenium

Selenium подключается через debug_port. Основная версия chromedriver должна совпадать с версией браузера (Kalkan использует установленный у вас Chrome).

import requests
from selenium import webdriver

API = "http://127.0.0.1:50360/api/v1"
H = {"X-Token": "kk_..."}

run = requests.post(f"{API}/profiles/PROFILE_ID/start", headers=H, json={}).json()["data"]

opts = webdriver.ChromeOptions()
opts.debugger_address = f"127.0.0.1:{run['debug_port']}"
driver = webdriver.Chrome(options=opts)
driver.get("https://example.com")
print(driver.title)

Советы

  • Не задавайте viewport из скрипта. Размер экрана профиля — часть его отпечатка. Используйте defaultViewport: null в Puppeteer и не передавайте viewport в Playwright.
  • Используйте существующий контекст профиля. В Playwright берите browser.contexts[0] вместо new_context(): в новом контексте нет ни cookies, ни настроек профиля.
  • По завершении скрипта отключайтесь, а не закрывайте браузер, и останавливайте профиль через POST /profiles/{id}/stop, чтобы Kalkan корректно сохранил сессию.
  • Запуск уже открытого профиля возвращает тот же эндпоинт с already_running: true, поэтому скрипты могут безопасно вызывать start повторно.
  • Обрабатывайте 409 осознанно: allow_direct и force существуют для того, чтобы скрипт никогда случайно не открыл профиль без прокси или на повторно используемом IP.