PromoPilot API

Единый REST API экосистемы PromoPilot: запускайте ссылочное продвижение, отслеживайте позиции, проводите SEO-аудиты и сканы безопасности из своего кода — CRM, SaaS, панелей агентства.

  • Базовый адрес: https://promopilot.link/api/v1
  • Формат — JSON поверх HTTPS; все ответы содержат поле ok.
  • Проекты, созданные по API, показываются в кабинете отдельным разделом «Проекты API» и помечаются бейджем — они не смешиваются с личными.
  • Машиночитаемая спецификация: openapi.json
curl https://promopilot.link/api/v1/ping
# {"ok":true,"pong":true,"time":"2026-07-20T12:00:00Z"}

Авторизация и ключи

Ключи API выпускаются в клиентском кабинете: Кабинет → API для разработчиков. При создании ключа вы выбираете, к каким сервисам он имеет доступ (скоупы): promotion, rank, audit, shield. Ключ показывается полностью один раз — мы храним только его SHA-256 отпечаток.

Authorization: Bearer ppk_ваш_ключ
# или
X-Api-Key: ppk_ваш_ключ
Безопасность: храните ключ в переменных окружения или секрет-хранилище, не публикуйте в клиентском коде и репозиториях. Скомпрометированный ключ немедленно отзывайте в кабинете — отзыв действует мгновенно. Для разных интеграций используйте разные ключи с минимально необходимыми скоупами.

Биллинг и баланс

  • Все платные операции списываются с личного баланса аккаунта — того же, что и в кабинете. Отдельного «API-баланса» нет.
  • Продвижение: цена по тарифу / гибкому каскаду / глобальной цене, минус ваша персональная скидка; подписчики Pro автоматически получают дополнительную скидку 10%.
  • Перед запуском можно узнать точную цену: POST /promotion/quote (без списания).
  • Rank Tracker — списание по факту проверок; SEO Audit full — предоплата по оценке размера; Shield deep/comprehensive — фиксированная цена скана.
  • Если средств не хватает — HTTP 402 insufficient_funds с полями required, balance, shortfall и ссылкой на пополнение topup_url. Пополнение баланса выполняется в кабинете.

Формат ошибок

{
  "ok": false,
  "error": {
    "code": "insufficient_funds",
    "message": "Недостаточно средств на балансе. Пополните баланс в кабинете и повторите запрос.",
    "details": {"required": 44.1, "balance": 10.0, "shortfall": 34.1, "topup_url": "…"}
  }
}
КодHTTPОписание
unauthorized401Ключ не передан, не найден или отозван
forbidden_scope403У ключа нет доступа к этому сервису — выпустите ключ с нужным скоупом
pro_required403Функция доступна на тарифе Pro (например, white-label брендинг)
forbidden403Действие запрещено (например, удаление личного проекта через API)
not_found404Объект не найден или принадлежит другому аккаунту
insufficient_funds402Недостаточно средств: в details придут required, balance, shortfall и topup_url
validation_error422Некорректные параметры; поле указано в details.field
domain_mismatch422Ссылка ведёт не на домен проекта
already_running409Процесс уже выполняется (например, аудит проекта)
needs_verify409Shield: домен не подтверждён
need_estimate409Audit: сначала express-аудит для оценки размера
rate_limited429Превышен лимит запросов; повторите через Retry-After секунд
report_archived410Отчёт заархивирован по сроку хранения; Pro получает доступ автоматически
level1_disabled503Продвижение временно выключено платформой
api_disabled503Публичный API временно отключён
server_error500Внутренняя ошибка — повторите позже

Лимиты

  • 120 запросов в минуту на ключ (базовый лимит).
  • Запуск продвижения — до 20 в минуту; создание проектов — до 30 в час; запуск аудитов/сканов — до 10 в минуту.
  • При превышении — HTTP 429 с заголовком Retry-After. Рекомендуемая частота поллинга статусов — раз в 30–60 секунд.

Локализация

Человекочитаемые сообщения (message) возвращаются на языке из параметра ?lang= (ru, en, uk, pl) или заголовка Accept-Language; по умолчанию — язык вашего аккаунта. Машинные коды ошибок и статусы не переводятся — привязывайте логику к ним.

Справочник эндпоинтов

Аккаунт

GET /me
Профиль: баланс, валюта, персональная скидка, Pro-статус, скоупы ключа.
Пример ответа
{"ok":true,"user":{"id":2,"balance":124.50,"currency":"USD","promotion_discount_percent":10,"is_pro":true,...},"key":{"label":"CRM","scopes":["promotion"]}}
GET /balance
Текущий баланс и ссылка на пополнение.
Пример ответа
{"ok":true,"balance":124.50,"currency":"USD","topup_url":"…/client/balance.php"}
GET /key
Информация о предъявленном ключе: скоупы, статистика использования.
Пример ответа
{"ok":true,"key":{"label":"CRM","scopes":["promotion","rank"],"requests_total":1520,...}}

Проекты и ссылки скоуп: promotion

GET /projects
Список проектов. По умолчанию — только созданные через API; ?all=1 — вместе с личными.
ПараметрГдеОписание
all query «1» — вернуть и личные, и API-проекты
Пример ответа
{"ok":true,"projects":[{"id":311,"name":"Client A","created_via":"api","links_count":3,"active_runs":1,...}]}
POST /projects
Создать проект. Он получит пометку created_via=api и появится в кабинете в разделе «Проекты API». Можно сразу передать ссылки — их домен должен совпадать с доменом проекта.
ПараметрГдеОписание
name * body Название проекта
url * body Адрес сайта (главная страница)
language body Язык контента (ru/en/uk/pl/…), по умолчанию ru
region body Регион аудитории
topic body Тематика
wishes body Пожелания к текстам
links body Массив объектов {url, anchor, language, wish}
Пример ответа
{"ok":true,"project":{"id":312,"created_via":"api",...},"links":{"added":2,"domain_errors":0}}
GET /projects/{id}
Проект со ссылками и последними запусками.
Пример ответа
{"ok":true,"project":{"id":312,"links":[...],"runs":[...]}}
PATCH /projects/{id}
Обновить name / description / language / wishes / region / topic.
Пример ответа
{"ok":true,"project":{...}}
DELETE /projects/{id}
Удалить проект. Разрешено только для проектов, созданных через API.
Пример ответа
{"ok":true,"deleted":true}
POST /projects/{id}/links
Добавить продвигаемую ссылку. Домен ссылки должен совпадать с доменом проекта (или быть его поддоменом) — иначе ошибка domain_mismatch.
ПараметрГдеОписание
url * body Полный URL страницы
anchor body Анкор (пусто — подберём автоматически)
language body Язык страницы
wish body Пожелание к текстам для этой ссылки
Пример ответа
{"ok":true,"link":{"id":915,"url":"https://site.com/page",...},"links_count":4}
DELETE /projects/{id}/links/{linkId}
Удалить ссылку из проекта.
Пример ответа
{"ok":true,"deleted":true}

Продвижение (каскады) скоуп: promotion

GET /promotion/tariffs
Активные тарифы (для tariff_id), юнит-цены гибкого каскада, глобальная цена и ваша персональная скидка.
Пример ответа
{"ok":true,"tariffs":[{"id":3,"slug":"pro","name":"Pro","price_per_link":49.0,"cascade":{...}}],"your_discount_percent":10}
POST /promotion/quote
Предрасчёт стоимости запуска без списания — те же параметры, что у запуска.
ПараметрГдеОписание
tariff_id body ID тарифа
level1_count…crowd_per_article body Параметры гибкого каскада
Пример ответа
{"ok":true,"pricing_mode":"tariff","base_price":49.0,"discount_percent":10,"total":44.1,"balance":124.5,"sufficient_funds":true}
POST /promotion/runs
Запустить продвижение ссылки. Списывает стоимость с личного баланса (скидки и Pro-бонус применяются автоматически). Повторный запуск той же ссылки при активном каскаде вернёт already_active.
ПараметрГдеОписание
project_id * body ID проекта
url * body Продвигаемая ссылка (или передайте link_id)
link_id body ID ссылки проекта — альтернатива url
tariff_id body Запуск по тарифу
level1_count body Гибкий каскад: публикаций L1
level2_per_level1 body L2 на каждую L1
level3_per_level2 body L3 на каждую L2
crowd_per_article body Крауд-ссылок на статью
schedule_start_at body Отложенный старт (ISO-датавремя)
Пример ответа
{"ok":true,"run_id":501,"status":"queued","charged":44.1,"discount_percent":10,"balance_after":80.4,"currency":"USD"}
GET /promotion/runs
Список запусков с пагинацией. Фильтры: project_id, status (active | queued | running | completed | failed | …), initiated_via (api | web).
ПараметрГдеОписание
page, per_page query Пагинация (до 100 на страницу)
Пример ответа
{"ok":true,"runs":[{"id":501,"status":"level1_active","progress":{"done":3,"total":12,"pct":25},...}],"pagination":{...}}
GET /promotion/runs/{id}
Статус запуска: стадия, прогресс, разбивка по уровням (total / success / verified).
Пример ответа
{"ok":true,"run":{"id":501,"status":"level2_active","levels":[{"level":1,"total":5,"verified":5},...]}}
GET /promotion/runs/{id}/nodes
Все размещения каскада: сеть, URL публикации, статус верификации. ?verified=1 — только подтверждённые ссылки.
ПараметрГдеОписание
verified query «1» — только проверенные размещения
Пример ответа
{"ok":true,"run_id":501,"nodes":[{"level":1,"network":"telegraph","url":"https://telegra.ph/…","verified":true,...}]}

Отчёты и white-label (Pro) скоуп: promotion

GET /promotion/runs/{id}/report
Итоговый отчёт (только проверенные размещения, как в кабинете). ?format=pdf вернёт ссылку на PDF. На тарифе Pro отчёт брендируется вашим white-label (см. /branding); поля можно переопределить параметрами brand_*.
ПараметрГдеОписание
format query json (по умолчанию) или pdf
brand_company query Pro: название компании в шапке
brand_logo_url query Pro: URL логотипа
brand_accent_color query Pro: HEX-цвет акцента
brand_footer_text query Pro: текст футера
Пример ответа
{"ok":true,"run":{...},"summary":{"verified":12,...},"report":{"level1":[...],"level2":[...],"crowd":[...]},"whitelabel":{...}} — или {"ok":true,"pdf":{"url":"…/uploads/reports/api-cascade-501-….pdf"},"branded":true}
GET /branding
Текущий white-label бренд и статус Pro.
Пример ответа
{"ok":true,"is_pro":true,"branding_active":true,"branding":{"company_name":"Acme","logo_url":"…","accent_color":"#6366f1",...}}
PUT /branding
Сохранить white-label бренд (только Pro). Тот же бренд действует в кабинете (Настройки → White-label).
ПараметрГдеОписание
company_name * body Название компании (обязательно)
logo_url, website, email, phone, address, footer_text, accent_color body Остальные поля бренда
Пример ответа
{"ok":true,"branding":{...}}

Rank Tracker скоуп: rank

GET /rank/projects
Проекты мониторинга позиций.
Пример ответа
{"ok":true,"projects":[{"id":7,"domain":"site.com","region":"UA","keywords_count":50,"top10":12,...}]}
POST /rank/projects
Создать проект: домен, регион (UA/US/PL/…), устройство (desktop/mobile).
ПараметрГдеОписание
domain * body Домен или URL сайта
name body Название (по умолчанию — домен)
region body Регион поиска, по умолчанию UA
device body настольный | мобильный
Пример ответа
{"ok":true,"project":{"id":8,...}}
GET /rank/projects/{id}
Проект + все ключевые слова с последними позициями.
Пример ответа
{"ok":true,"project":{...},"keywords":[{"id":91,"keyword":"купить окна","last_position":4,"last_checked_at":"…"}]}
POST /rank/projects/{id}/keywords
Добавить ключевые слова: строка с переносами или массив.
ПараметрГдеОписание
keywords * body Строка «kw1\nkw2» или массив строк
tag body Метка группы
target_url body Целевая страница
Пример ответа
{"ok":true,"added":25,"skipped":0}
POST /rank/projects/{id}/check
Поставить проверку позиций в очередь (все слова или keyword_ids[]). Списание — по факту проверок по внутренним тарифам сервиса. Результаты забирайте из GET /rank/projects/{id}.
ПараметрГдеОписание
keyword_ids body Массив ID слов (по умолчанию — все)
Пример ответа
{"ok":true,"queued":50,"note":"…"}

SEO Audit скоуп: audit

GET /audit/projects
Проекты аудита.
Пример ответа
{"ok":true,"projects":[{"id":4,"domain":"site.com","start_url":"https://site.com/",...}]}
POST /audit/projects
Создать проект аудита.
ПараметрГдеОписание
url * body Адрес сайта
name body Название
include_subdomains body true — сканировать поддомены
Пример ответа
{"ok":true,"project":{"id":5,...}}
GET /audit/projects/{id}/estimate
Оценка размера сайта и цена полного аудита (нужен хотя бы один express-аудит для оценки).
Пример ответа
{"ok":true,"has_estimate":true,"estimated_pages":430,"full_audit_price":5.0,"currency":"USD"}
POST /audit/projects/{id}/crawls
Запустить аудит. express — бесплатно (дневной лимит), full — полный, предоплата с баланса по оценке размера.
ПараметрГдеОписание
mode body express (по умолчанию) | full
Пример ответа
{"ok":true,"crawl_id":88,"mode":"full","max_pages":500,"charged":5.0}
GET /audit/crawls/{id}
Статус и итоги: Health Score, ошибки/предупреждения, свод проблем по категориям (когда завершён).
Пример ответа
{"ok":true,"crawl":{"id":88,"status":"done","health_score":79,"pages_crawled":430,"issues":{...}}}
GET /audit/projects/{id}/crawls
История аудитов проекта.
Пример ответа
{"ok":true,"crawls":[...]}

Security Shield скоуп: shield

GET /shield/projects
Сайты под защитой.
Пример ответа
{"ok":true,"projects":[{"id":3,"domain":"site.com","verified":true,...}]}
POST /shield/projects
Создать/найти проект по URL. В ответе — verify_token для подтверждения домена.
ПараметрГдеОписание
url * body Адрес сайта
name body Название
Пример ответа
{"ok":true,"project":{"id":3,"verified":false,"verify_token":"pp-verify-…"},"verification_hint":"…"}
POST /shield/projects/{id}/verify
Проверить подтверждение домена (DNS TXT-запись, файл .well-known или meta-тег с verify_token). Требуется для платных сканов.
Пример ответа
{"ok":true,"verified":true,"method":"dns"}
POST /shield/projects/{id}/scans
Запустить скан: free (базовый, дневной лимит), deep или comprehensive — списываются с баланса.
ПараметрГдеОписание
mode body бесплатный | глубокий | комплексный
lang body Язык ИИ-отчёта (ru/uk/en/pl)
Пример ответа
{"ok":true,"scan_id":41,"mode":"deep","charged":19.0}
GET /shield/scans/{id}
Статус, оценка (score/grade), светофор-вердикт, свод находок по критичности и публичная ссылка на отчёт.
Пример ответа
{"ok":true,"scan":{"id":41,"status":"done","score":86,"grade":"B","verdict":{...},"findings_summary":{"critical":0,"high":1,...},"share_report_url":"…"}}

Примеры кода

cURL — создать проект и запустить продвижение

curl -X POST https://promopilot.link/api/v1/projects \
  -H "Authorization: Bearer ppk_ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Client Site",
    "url": "https://client-site.com",
    "language": "ru",
    "links": [{"url": "https://client-site.com/services", "anchor": "услуги компании"}]
  }'
curl -X POST https://promopilot.link/api/v1/promotion/runs \
  -H "Authorization: Bearer ppk_ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{"project_id": 312, "url": "https://client-site.com/services"}'

PHP

<?php
$key = getenv('PROMOPILOT_API_KEY'); // никогда не храните ключ в коде
function pp_api(string $method, string $path, array $data = null) {
    global $key;
    $ch = curl_init('https://promopilot.link/api/v1' . $path);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_CUSTOMREQUEST => $method,
        CURLOPT_HTTPHEADER => [
            'Authorization: Bearer ' . $key,
            'Content-Type: application/json',
            'Accept-Language: ru',
        ],
        CURLOPT_POSTFIELDS => $data !== null ? json_encode($data) : null,
    ]);
    $res = json_decode(curl_exec($ch), true);
    $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
    if (($res['ok'] ?? false) !== true) {
        throw new RuntimeException($res['error']['code'] ?? ('http_' . $code));
    }
    return $res;
}

$project = pp_api('POST', '/projects', ['name' => 'Client', 'url' => 'https://client-site.com']);
try {
    $run = pp_api('POST', '/promotion/runs', [
        'project_id' => $project['project']['id'],
        'url' => 'https://client-site.com/services',
    ]);
    echo "Запущено, run_id={$run['run_id']}, списано {$run['charged']}";
} catch (RuntimeException $e) {
    if ($e->getMessage() === 'insufficient_funds') {
        // пополните баланс и повторите
    }
}

Python

import os, requests

API = "https://promopilot.link/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['PROMOPILOT_API_KEY']}"}

def api(method, path, **json_body):
    r = requests.request(method, API + path, headers=HEADERS, json=json_body or None, timeout=30)
    data = r.json()
    if not data.get("ok"):
        code = data.get("error", {}).get("code", f"http_{r.status_code}")
        if code == "insufficient_funds":
            details = data["error"]["details"]
            raise RuntimeError(f"Пополните баланс: не хватает {details['shortfall']}")
        raise RuntimeError(code)
    return data

project = api("POST", "/projects", name="Client", url="https://client-site.com")
run = api("POST", "/promotion/runs", project_id=project["project"]["id"],
          url="https://client-site.com/services")
print("run_id:", run["run_id"], "charged:", run["charged"])

# Поллинг статуса
import time
while True:
    st = api("GET", f"/promotion/runs/{run['run_id']}")["run"]
    print(st["status"], st["progress"]["pct"], "%")
    if st["status"] in ("completed", "failed", "cancelled"):
        break
    time.sleep(60)

report = api("GET", f"/promotion/runs/{run['run_id']}/report")

Node.js

const API = "https://promopilot.link/api/v1";
const KEY = process.env.PROMOPILOT_API_KEY;

async function api(method, path, body) {
  const res = await fetch(API + path, {
    method,
    headers: {
      "Authorization": `Bearer ${KEY}`,
      "Content-Type": "application/json",
      "Accept-Language": "ru",
    },
    body: body ? JSON.stringify(body) : undefined,
  });
  const data = await res.json();
  if (!data.ok) {
    const code = data.error?.code || `http_${res.status}`;
    if (code === "insufficient_funds") {
      console.error("Пополните баланс:", data.error.details.topup_url);
    }
    throw new Error(code);
  }
  return data;
}

const { project } = await api("POST", "/projects", { name: "Client", url: "https://client-site.com" });
const run = await api("POST", "/promotion/runs", { project_id: project.id, url: "https://client-site.com/services" });
console.log("run:", run.run_id, "charged:", run.charged);

OpenAPI

Полная машиночитаемая спецификация для генерации клиентов и импорта в Postman/Insomnia: https://promopilot.link/api/v1/openapi.json

Вопросы и предложения по API — через поддержку в кабинете или Telegram. Обратная совместимость: изменения, ломающие контракт, выходят только новой версией (/api/v2), текущие поля не удаляются без анонса в changelog.

Готовы начать?
Создайте ключ API в кабинете — это займёт минуту.
Получить ключ API