API PromoPilot

Jednolity REST API ekosystemu PromoPilot: uruchamiaj link building, śledź pozycje, przeprowadzaj audyty SEO i skany bezpieczeństwa z własnego kodu — CRM, SaaS, paneli agencji.

  • Adres podstawowy: https://promopilot.link/api/v1
  • Format — JSON na HTTPS; wszystkie odpowiedzi zawierają pole ok.
  • Projekty utworzone za pomocą API są wyświetlane w panelu w osobnej sekcji „Projekty API” i oznaczone są odznaką — nie mieszają się z osobistymi.
  • Specyfikacja maszynowo czytelna: openapi.json
curl https://promopilot.link/api/v1/ping
# {"ok":true,"pong":true,"time":"2026-07-20T12:00:00Z"}

Autoryzacja i klucze

Klucze API są wydawane w panelu klienta: Panel → API dla deweloperów. Podczas tworzenia klucza wybierasz, do jakich usług ma dostęp (zakresy): promotion, rank, audit, shield. Klucz jest wyświetlany w całości tylko raz — przechowujemy tylko jego odcisk SHA-256.

Authorization: Bearer ppk_ваш_ключ
# или
X-Api-Key: ppk_ваш_ключ
Bezpieczeństwo: przechowuj klucz w zmiennych środowiskowych lub w magazynie sekretów, nie publikuj w kodzie klienta i repozytoriach. Skonfiskowany klucz natychmiast wycofaj w panelu — wycofanie działa natychmiast. Do różnych integracji używaj różnych kluczy z minimalnie wymaganymi zakresami.

Fakturowanie i saldo

  • Wszystkie płatne operacje są odejmowane z osobistego salda konta — tego samego, co w panelu. Nie ma osobnego „salda API”.
  • Promocja: cena według taryfy / elastycznego kaskadu / ceny globalnej, minus twoja osobista zniżka; subskrybenci Pro automatycznie otrzymują dodatkową zniżkę 10%.
  • Przed uruchomieniem można poznać dokładną cenę: POST /promotion/quote (bez obciążenia).
  • Rank Tracker — obciążenie na podstawie rzeczywistych kontroli; SEO Audit full — zaliczka na podstawie oceny rozmiaru; Shield deep/comprehensive — stała cena skanowania.
  • Jeśli brakuje środków — HTTP 402 insufficient_funds z polami required, balance, shortfall i linkiem do doładowania topup_url. Doładowanie salda odbywa się w panelu.

Format błędów

{
  "ok": false,
  "error": {
    "code": "insufficient_funds",
    "message": "Niewystarczające środki na saldzie. Doładuj saldo w panelu i powtórz żądanie.",
    "details": {"required": 44.1, "balance": 10.0, "shortfall": 34.1, "topup_url": "…"}
  }
}
CodeHTTPDescription
unauthorized401Klucz nie został przekazany, nie znaleziono go lub został odwołany.
forbidden_scope403Klucz nie ma dostępu do tej usługi — wydaj klucz z odpowiednim zakresem.
pro_required403Funkcja dostępna w planie Pro (np. branding white-label).
forbidden403Działanie zabronione (np. usunięcie osobistego projektu przez API).
not_found404Obiekt nie został znaleziony lub należy do innego konta.
insufficient_funds402Niewystarczające środki: w szczegółach znajdą się required, balance, shortfall i topup_url.
validation_error422Nieprawidłowe parametry; pole wskazane w details.field.
domain_mismatch422Link nie prowadzi do domeny projektu.
already_running409Proces już jest w toku (np. audyt projektu).
needs_verify409Shield: domena niepotwierdzona.
need_estimate409Audyt: najpierw audyt ekspresowy w celu oceny rozmiaru.
rate_limited429Przekroczono limit zapytań; spróbuj ponownie po Retry-After sekund.
report_archived410Raport został zarchiwizowany zgodnie z okresem przechowywania; Pro uzyskuje dostęp automatycznie.
level1_disabled503Promocja tymczasowo wyłączona przez platformę.
api_disabled503Publiczne API tymczasowo wyłączone.
server_error500Błąd wewnętrzny — spróbuj ponownie później.

Limity

  • 120 zapytań na minutę na klucz (podstawowy limit).
  • Uruchomienie promocji — do 20 na minutę; tworzenie projektów — do 30 na godzinę; uruchomienie audytów/skanów — do 10 na minutę.
  • W przypadku przekroczenia — HTTP 429 z nagłówkiem Retry-After. Zalecana częstotliwość pollingu statusów — co 30–60 sekund.

Lokalizacja

Czytelne wiadomości (message) są zwracane w języku z parametru ?lang= (ru, en, uk, pl) lub nagłówka Accept-Language; domyślnie — język twojego konta. Kody błędów i statusy maszynowe nie są tłumaczone — powiąż logikę z nimi.

Słownik punktów końcowych

Konto

GET /me
Profil: saldo, waluta, osobisty rabat, status Pro, zakresy klucza.
Przykład odpowiedzi
{"ok":true,"user":{"id":2,"balance":124.50,"currency":"USD","promotion_discount_percent":10,"is_pro":true,...},"key":{"label":"CRM","scopes":["promotion"]}}
GET /balance
Aktualne saldo i link do doładowania.
Przykład odpowiedzi
{"ok":true,"balance":124.50,"currency":"USD","topup_url":"…/client/balance.php"}
GET /key
Informacje o przedstawionym kluczu: zakresy, statystyki użycia.
Przykład odpowiedzi
{"ok":true,"key":{"label":"CRM","scopes":["promotion","rank"],"requests_total":1520,...}}

Projekty i linki zakres: promotion

GET /projects
Lista projektów. Domyślnie — tylko te utworzone przez API; ?all=1 — razem z osobistymi.
ParametrGdzieDescription
all query «1» — zwróć zarówno osobiste, jak i projekty API
Przykład odpowiedzi
{"ok":true,"projects":[{"id":311,"name":"Client A","created_via":"api","links_count":3,"active_runs":1,...}]}
POST /projects
Utwórz projekt. Otrzyma oznaczenie created_via=api i pojawi się w panelu w sekcji „Projekty API”. Można od razu przekazać linki — ich domena musi odpowiadać domenie projektu.
ParametrGdzieDescription
name * body Project Name
url * body Adres strony (strona główna)
language body Język treści (ru/en/uk/pl/…), domyślnie ru
region body Region odbiorców
topic body Topic
wishes body Życzenia dotyczące tekstów
links body Tablica obiektów {url, anchor, language, wish}
Przykład odpowiedzi
{"ok":true,"project":{"id":312,"created_via":"api",...},"links":{"added":2,"domain_errors":0}}
GET /projects/{id}
Projekt z linkami i ostatnimi uruchomieniami.
Przykład odpowiedzi
{"ok":true,"project":{"id":312,"links":[...],"runs":[...]}}
PATCH /projects/{id}
Zaktualizuj name / description / language / wishes / region / topic.
Przykład odpowiedzi
{"ok":true,"project":{...}}
DELETE /projects/{id}
Usuń projekt. Dozwolone tylko dla projektów utworzonych przez API.
Przykład odpowiedzi
{"ok":true,"deleted":true}
POST /projects/{id}/links
Dodaj promowany link. Domeny linku muszą odpowiadać domenie projektu (lub być jego subdomeną) — w przeciwnym razie błąd domain_mismatch.
ParametrGdzieDescription
url * body Pełny URL strony
anchor body Ankora (puste — dobierzemy automatycznie)
language body Page language
wish body Życzenie dotyczące tekstów dla tego linku
Przykład odpowiedzi
{"ok":true,"link":{"id":915,"url":"https://site.com/page",...},"links_count":4}
DELETE /projects/{id}/links/{linkId}
Usuń link z projektu.
Przykład odpowiedzi
{"ok":true,"deleted":true}

Promocja (kaskady) zakres: promotion

GET /promotion/tariffs
Aktywne stawki (dla tariff_id), ceny jednostkowe elastycznej kaskady, cena globalna i Twój osobisty rabat.
Przykład odpowiedzi
{"ok":true,"tariffs":[{"id":3,"slug":"pro","name":"Pro","price_per_link":49.0,"cascade":{...}}],"your_discount_percent":10}
POST /promotion/quote
Wstępny koszt uruchomienia bez obciążania — te same parametry co przy uruchomieniu.
ParametrGdzieDescription
tariff_id body ID planu
level1_count…crowd_per_article body Parametry elastycznego kaskadu
Przykład odpowiedzi
{"ok":true,"pricing_mode":"tariff","base_price":49.0,"discount_percent":10,"total":44.1,"balance":124.5,"sufficient_funds":true}
POST /promotion/runs
Uruchom promocję linku. Koszt zostanie pobrany z osobistego salda (zniżki i bonus Pro są stosowane automatycznie). Ponowne uruchomienie tego samego linku przy aktywnym kaskadzie zwróci already_active.
ParametrGdzieDescription
project_id * body ID projektu
url * body Promowany link (lub przekaż link_id)
link_id body ID linku projektu — alternatywa dla url
tariff_id body Uruchomienie według planu
level1_count body Elastyczny kaskad: publikacje L1
level2_per_level1 body L2 na każdą L1
level3_per_level2 body L3 na każdą L2
crowd_per_article body Linki z crowdsourcingu do artykułu
schedule_start_at body Opóźniony start (ISO-data-czas)
Przykład odpowiedzi
{"ok":true,"run_id":501,"status":"queued","charged":44.1,"discount_percent":10,"balance_after":80.4,"currency":"USD"}
GET /promotion/runs
Lista uruchomień z paginacją. Filtry: project_id, status (aktywny | w kolejce | w trakcie | zakończony | nieudany | …), initiated_via (api | web).
ParametrGdzieDescription
page, per_page query Paginacja (do 100 na stronę)
Przykład odpowiedzi
{"ok":true,"runs":[{"id":501,"status":"level1_active","progress":{"done":3,"total":12,"pct":25},...}],"pagination":{...}}
GET /promotion/runs/{id}
Status uruchomienia: etap, postęp, podział na poziomy (całkowity / sukces / zweryfikowany).
Przykład odpowiedzi
{"ok":true,"run":{"id":501,"status":"level2_active","levels":[{"level":1,"total":5,"verified":5},...]}}
GET /promotion/runs/{id}/nodes
Wszystkie umiejscowienia kaskadu: sieć, URL publikacji, status weryfikacji. ?verified=1 — tylko potwierdzone linki.
ParametrGdzieDescription
verified query „1” — tylko zweryfikowane umiejscowienia
Przykład odpowiedzi
{"ok":true,"run_id":501,"nodes":[{"level":1,"network":"telegraph","url":"https://telegra.ph/…","verified":true,...}]}

Raporty i white-label (Pro) zakres: promotion

GET /promotion/runs/{id}/report
Raport końcowy (tylko zweryfikowane umiejscowienia, jak w panelu). ?format=pdf zwróci link do PDF. W planie Pro raport jest brandowany waszym white-label (zob. /branding); pola można nadpisać parametrami brand_*.
ParametrGdzieDescription
format query json (domyślnie) lub pdf
brand_company query Pro: nazwa firmy w nagłówku
brand_logo_url query Pro: URL logo
brand_accent_color query Pro: kolor akcentu HEX
brand_footer_text query Pro: tekst stopki
Przykład odpowiedzi
{"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
Aktualna marka white-label i status Pro.
Przykład odpowiedzi
{"ok":true,"is_pro":true,"branding_active":true,"branding":{"company_name":"Acme","logo_url":"…","accent_color":"#6366f1",...}}
PUT /branding
Zapisz markę white-label (tylko Pro). Ta sama marka działa w panelu (Ustawienia → White-label).
ParametrGdzieDescription
company_name * body Nazwa firmy (wymagana)
logo_url, website, email, phone, address, footer_text, accent_color body Pozostałe pola marki
Przykład odpowiedzi
{"ok":true,"branding":{...}}

Rank Tracker zakres: rank

GET /rank/projects
Projekty monitorowania pozycji.
Przykład odpowiedzi
{"ok":true,"projects":[{"id":7,"domain":"site.com","region":"UA","keywords_count":50,"top10":12,...}]}
POST /rank/projects
Utwórz projekt: domena, region (UA/US/PL/…), urządzenie (desktop/mobile).
ParametrGdzieDescription
domain * body Domena lub URL strony
name body Nazwa (domyślnie — domena)
region body Region wyszukiwania, domyślnie UA
device body desktop | mobile
Przykład odpowiedzi
{"ok":true,"project":{"id":8,...}}
GET /rank/projects/{id}
Projekt + wszystkie słowa kluczowe z ostatnimi pozycjami.
Przykład odpowiedzi
{"ok":true,"project":{...},"keywords":[{"id":91,"keyword":"купить окна","last_position":4,"last_checked_at":"…"}]}
POST /rank/projects/{id}/keywords
Dodaj słowa kluczowe: ciąg z nowymi liniami lub tablica.
ParametrGdzieDescription
keywords * body Ciąg «kw1\nkw2» lub tablica ciągów
tag body Etykieta grupy
target_url body Target page
Przykład odpowiedzi
{"ok":true,"added":25,"skipped":0}
POST /rank/projects/{id}/check
Umieść sprawdzenie pozycji w kolejce (wszystkie słowa lub keyword_ids[]). Opłata — zgodnie z faktycznymi sprawdzeniami według wewnętrznych stawek usługi. Wyniki pobierz z GET /rank/projects/{id}.
ParametrGdzieDescription
keyword_ids body Tablica ID słów (domyślnie — wszystkie)
Przykład odpowiedzi
{"ok":true,"queued":50,"note":"…"}

SEO Audit zakres: audit

GET /audit/projects
Projekty audytów.
Przykład odpowiedzi
{"ok":true,"projects":[{"id":4,"domain":"site.com","start_url":"https://site.com/",...}]}
POST /audit/projects
Utwórz projekt audytu.
ParametrGdzieDescription
url * body Adres strony
name body Name
include_subdomains body true — skanować subdomeny
Przykład odpowiedzi
{"ok":true,"project":{"id":5,...}}
GET /audit/projects/{id}/estimate
Ocena rozmiaru strony i cena pełnego audytu (potrzebny przynajmniej jeden audyt express do oceny).
Przykład odpowiedzi
{"ok":true,"has_estimate":true,"estimated_pages":430,"full_audit_price":5.0,"currency":"USD"}
POST /audit/projects/{id}/crawls
Uruchom audyt. express — za darmo (limit dzienny), full — pełny, przedpłata z salda według oceny rozmiaru.
ParametrGdzieDescription
mode body express (domyślnie) | full
Przykład odpowiedzi
{"ok":true,"crawl_id":88,"mode":"full","max_pages":500,"charged":5.0}
GET /audit/crawls/{id}
Status i podsumowanie: Health Score, błędy/ostrzeżenia, zestawienie problemów według kategorii (po zakończeniu).
Przykład odpowiedzi
{"ok":true,"crawl":{"id":88,"status":"done","health_score":79,"pages_crawled":430,"issues":{...}}}
GET /audit/projects/{id}/crawls
Historia audytów projektu.
Przykład odpowiedzi
{"ok":true,"crawls":[...]}

Security Shield zakres: shield

GET /shield/projects
Strony pod ochroną.
Przykład odpowiedzi
{"ok":true,"projects":[{"id":3,"domain":"site.com","verified":true,...}]}
POST /shield/projects
Utwórz/znajdź projekt po URL. W odpowiedzi — verify_token do potwierdzenia domeny.
ParametrGdzieDescription
url * body Adres strony
name body Name
Przykład odpowiedzi
{"ok":true,"project":{"id":3,"verified":false,"verify_token":"pp-verify-…"},"verification_hint":"…"}
POST /shield/projects/{id}/verify
Sprawdź potwierdzenie domeny (rekord TXT DNS, plik .well-known lub meta-tag z verify_token). Wymagane do płatnych skanów.
Przykład odpowiedzi
{"ok":true,"verified":true,"method":"dns"}
POST /shield/projects/{id}/scans
Uruchom skan: free (podstawowy, dzienny limit), deep lub comprehensive — będą pobierane z salda.
ParametrGdzieDescription
mode body darmowy | głęboki | kompleksowy
lang body Język raportu AI (ru/uk/en/pl)
Przykład odpowiedzi
{"ok":true,"scan_id":41,"mode":"deep","charged":19.0}
GET /shield/scans/{id}
Status, ocena (score/grade), werdykt sygnalizacyjny, podsumowanie wyników według krytyczności i publiczny link do raportu.
Przykład odpowiedzi
{"ok":true,"scan":{"id":41,"status":"done","score":86,"grade":"B","verdict":{...},"findings_summary":{"critical":0,"high":1,...},"share_report_url":"…"}}

Przykłady kodu

cURL — utwórz projekt i uruchom promocję

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

Pełna specyfikacja maszynowo czytelna do generowania klientów i importu do Postman/Insomnia: https://promopilot.link/api/v1/openapi.json

Pytania i sugestie dotyczące API — przez wsparcie w panelu lub Telegram. Kompatybilność wsteczna: zmiany łamiące kontrakt są wprowadzane tylko w nowej wersji (/api/v2), bieżące pola nie są usuwane bez ogłoszenia w changelogu.

Gotowy do rozpoczęcia?
Utwórz klucz API w panelu — to zajmie minutę.
Uzyskaj klucz API