OutreachPilot.pl
Strona główna

Dla agencji i integratorów · API v1

Dokumentacja API OutreachPilot

Czytaj i zapisuj leady, buduj kampanie, odbieraj odpowiedzi i podłącz własne narzędzia. JSON, klucze z zakresami, stronicowanie kursorem i podpisane webhooki. API jest w planach Pro, Business i Agencja (także w 14-dniowym okresie próbnym).

Pobierz OpenAPI 3.1 (JSON)Utwórz klucz APIOstatnia aktualizacja: 1 października 2026

Szybki start

  1. Upewnij się, że masz plan Pro, Business lub Agencja (albo trwa Twój okres próbny).
  2. W aplikacji wejdź w Ustawienia → API i integracje, nazwij klucz i wybierz zakresy. Klucz (op_live_…) pokazujemy tylko raz: skopiuj go od razu.
  3. Wyślij pierwsze żądanie i sprawdź plan, limity oraz zakresy klucza:
curl
export OUTREACHPILOT_API_KEY="op_live_TWÓJ_KLUCZ"

curl "https://outreachpilot.pl/api/v1/me" \
  -H "Authorization: Bearer $OUTREACHPILOT_API_KEY"

Cały opis API w formacie OpenAPI 3.1 jest pod adresem https://outreachpilot.pl/api/v1/openapi.json. Zaimportujesz go w Postmanie, Insomnii albo generatorze klienta. Wszystkie daty są w ISO 8601 (UTC), a nazwy pól w snake_case.

Klucze i zakresy (scope)

Każde żądanie wymaga nagłówka Authorization: Bearer op_live_…. Klucz jest powiązany z Twoim kontem, ma nazwę, zakresy i opcjonalną datę wygaśnięcia. Możesz mieć do 10 aktywnych kluczy (osobny na każde narzędzie), odwołać każdy w sekundę i zobaczyć, kiedy był ostatnio użyty.

ZakresCo pozwala
leads:readCzytanie leadów. Twoja baza firm i kontaktów oraz tagi (tylko odczyt)
leads:writeEdycja leadów. Dodawanie, aktualizacja, statusy, tagowanie, wzbogacanie i import
leads:deleteUsuwanie leadów. Trwałe kasowanie - akcja nieodwracalna Operacja nieodwracalna.
leads:scrapeWyszukiwanie firm. Wyszukiwanie firm w Google Maps, PKT, OSM i Panoramie: zapisuje leady i zużywa miesięczny limit
campaigns:readCzytanie kampanii. Lista kampanii, sekwencje wiadomości, statystyki i historia wysyłki
campaigns:writeTworzenie kampanii. Tworzenie szkiców kampanii, dodawanie leadów, uruchamianie i pauza (start wysyła prawdziwe maile)
inbox:readCzytanie inboxu. Odpowiedzi od leadów (pełna treść) i propozycje agentów AI
analytics:readStatystyki kampanii. Wyniki kampanii i kroków: wysłane, otwarcia, odpowiedzi, zwroty (tylko odczyt)
webhooks:readCzytanie webhooków. Lista Twoich webhooków (bez sekretów)
webhooks:writeZarządzanie webhookami. Dodawanie i usuwanie webhooków; sekret pokazujemy jeden raz przy tworzeniu. Zdarzenia lead.* i email.* zawierają adresy e-mail i nazwy firm, więc ich subskrypcja wymaga też leads:read

Nazwy zakresów są te same co w naszym konektorze MCP dla asystentów AI, więc jeden słownik obejmuje oba sposoby dostępu. Zasada najmniejszych uprawnień: odczyt nie pozwala na zapis, zapis nie obejmuje kasowania.

Klucz API a MCP. API jest dla Twoich integracji i skryptów. Jeśli chcesz, żeby asystent AI (Claude, ChatGPT) pracował na Twoim koncie, użyj konektora MCP.

Plany i limity

PlanDostęp do APILimit żądań na klucz
Freebrak (403 z informacją o planie)-
Starterbrak (403 z informacją o planie)-
Pro (także okres próbny)tak60 na minutę
Businesstak120 na minutę
Agencjatak300 na minutę

Plan sprawdzamy przy każdym żądaniu: po spadku planu klucze przestają działać (403 plan_upgrade_required), ale zostają zapisane i wracają do pracy po powrocie na plan z API. Każda odpowiedź ma nagłówki X-RateLimit-Limit, X-RateLimit-Remaining i X-RateLimit-Reset (sekundy Unix). Po przekroczeniu limitu dostajesz 429 z nagłówkiem Retry-After.

Leady z API zużywają ten sam miesięczny limit leadów co aplikacja (zapis jest atomowy, równoległe żądania nie przekroczą limitu). Duplikaty nie kosztują. Wyszukiwarka firm (POST /leads/search) liczy limit tak samo jak w aplikacji.

Odpowiedzi, błędy i paginacja

Koperta sukcesu

Pojedynczy zasób: { "data": { ... } }. Lista: { "data": [ ... ], "pagination": { "limit", "has_more", "next_cursor" } }.

Paginacja kursorem

Listy są od najnowszych. Parametr limit (1-100, domyślnie 25) ustala rozmiar strony, a next_cursor z odpowiedzi podajesz jako cursor w kolejnym żądaniu. Kursor jest nieprzezroczysty i odporny na dopisywanie nowych rekordów w trakcie pobierania (nie gubi ani nie dubluje wierszy).

JavaScript
// Pobranie wszystkich leadów ze statusem "zainteresowany" (strony po 100)
const wait = (seconds) => new Promise((resolve) => setTimeout(resolve, seconds * 1000))
let cursor = null
const all = []
for (;;) {
  const url = new URL("https://outreachpilot.pl/api/v1/leads")
  url.searchParams.set("status", "zainteresowany")
  url.searchParams.set("limit", "100")
  if (cursor) url.searchParams.set("cursor", cursor)
  const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.OUTREACHPILOT_API_KEY}` } })
  if (res.status === 429) { await wait(Number(res.headers.get("retry-after") ?? 5)); continue } // ponów TĘ SAMĄ stronę
  if (!res.ok) throw new Error(`HTTP ${res.status}`)
  const page = await res.json()
  all.push(...page.data)
  if (!page.pagination.next_cursor) break
  cursor = page.pagination.next_cursor
}

Błędy

Błąd zawsze ma ten sam kształt, a request_id podaj przy zgłaszaniu problemu:

403
{
  "error": {
    "code": "plan_upgrade_required",
    "message": "API jest dostępne od planu Pro (…)",
    "details": {
      "current_plan": "starter",
      "required_plan": "pro"
    },
    "request_id": "req_3f9a1c2b7d8e4f5a6b7c8d9e"
  }
}
KodHTTPZnaczenie
invalid_request400Nieprawidłowe żądanie: zepsuty JSON lub kursor (400), zły Content-Type (415), za duże ciało (413); także reguły biznesowe kampanii (422).
validation_error422Dane nie przeszły walidacji; szczegóły w `details`.
missing_api_key401Brak nagłówka Authorization: Bearer op_live_…
invalid_api_key401Klucz nie istnieje albo ma zły format.
api_key_revoked401Klucz został odwołany.
api_key_expired401Klucz wygasł.
account_inactive401Konto jest nieaktywne lub zablokowane.
plan_upgrade_required403Plan konta nie obejmuje API (Starter, Free). Odpowiedź zawiera informacje o planie.
insufficient_scope403Klucz nie ma wymaganego zakresu (scope).
not_found404Nie ma takiego zasobu (albo nie należy do Twojego konta).
conflict409Konflikt stanu, np. niedozwolona zmiana statusu kampanii.
duplicate_lead409Lead z takim adresem e-mail (lub tą samą firmą) już jest w bazie.
confirmation_required409Uruchomienie wymaga potwierdzenia ostrzeżeń (`acknowledge_warnings: true`).
lead_limit_reached402Wyczerpany miesięczny lub dzienny limit leadów.
rate_limit_exceeded429Za dużo żądań. Zobacz nagłówek Retry-After.
upstream_unavailable503Usługa zależna (np. wyszukiwarka firm) chwilowo niedostępna. Nic nie zapisano.
service_unavailable503Usługa chwilowo niedostępna. Spróbuj ponownie.
internal_error500Nieoczekiwany błąd serwera.

Endpointy

Adres bazowy: https://outreachpilot.pl/api/v1. Każdy endpoint pokazuje wymagany zakres, parametry, przykład curl i odpowiedź. Operacje, które wysyłają prawdziwe maile (start kampanii) albo zużywają limit (zapis leadów, wyszukiwarka), są opisane wprost.

Konto

get/api/v1/medowolny klucz

Konto, plan, limity i zużycie

Zwraca właściciela klucza: plan, limity (leady, wysyłka, limit żądań API), bieżące zużycie oraz metadane samego klucza (nazwa, prefiks, zakresy, wygaśnięcie). Nie wymaga żadnego zakresu: dobry pierwszy test klucza.

Przykład (curl)
curl "https://outreachpilot.pl/api/v1/me" \
  -H "Authorization: Bearer $OUTREACHPILOT_API_KEY"
Odpowiedź 200
{
  "data": {
    "user": {
      "id": "11111111-1111-4111-8111-111111111111",
      "email": "anna@agencja.pl",
      "name": "Anna Nowak",
      "company_name": "Agencja Nowak"
    },
    "plan": {
      "id": "pro",
      "status": "active",
      "trial_ends_at": null
    },
    "limits": {
      "leads_per_month": 1000,
      "daily_sends": 300,
      "campaigns": null,
      "mailboxes": 3,
      "api_requests_per_minute": 60
    },
    "usage": {
      "leads_this_month": 212,
      "leads_remaining": 788,
      "sends_today": 41,
      "total_leads": 3120,
      "open_campaigns": 4
    },
    "key": {
      "id": "9c1f0a3e-2b7d-4e55-8a10-1d2e3f4a5b6c",
      "name": "Make - produkcja",
      "prefix": "op_live_AbCdEfGh",
      "scopes": [
        "leads:read",
        "campaigns:read"
      ],
      "expires_at": null
    }
  }
}
get/api/v1/workspacesdowolny klucz

Klienci agencji (workspace'y)

Lista klientów konta agencji (plan Business: do 3, Agencja bez limitu) plus `default` = „Moja firma” (dane bez klienta). ID klienta podaj jako `workspace_id` w filtrach list (`GET /leads`, `GET /campaigns`) i w żądaniach zapisu (`POST /leads`, `/leads/bulk`, `/leads/search`, `/campaigns`). Bez `workspace_id` listy obejmują całe konto, a nowe dane trafiają do „Mojej firmy”. Limity i rachunek są wspólne dla konta. Nie wymaga zakresu.

Przykład (curl)
curl "https://outreachpilot.pl/api/v1/workspaces" \
  -H "Authorization: Bearer $OUTREACHPILOT_API_KEY"
Odpowiedź 200
{
  "data": [
    {
      "id": "default",
      "name": "Moja firma",
      "status": "default",
      "created_at": null
    },
    {
      "id": "7c4e2d10-5b3a-4f6e-9a21-8d0c1b2e3f45",
      "name": "Salon Magnolia",
      "status": "active",
      "created_at": "2026-10-01T08:00:00.000Z"
    }
  ]
}

Leady

get/api/v1/leadszakres leads:read

Lista leadów

Leady z Twojej bazy, od najnowszych, stronicowane kursorem. Filtry łączą się logicznym AND. `workspace_id` zawęża do klienta agencji (albo `default` = „Moja firma”).

Czytanie leadów: Twoja baza firm i kontaktów oraz tagi (tylko odczyt)

Parametry
ParametrTypOpis
limitqueryinteger (min 1, max 100)Rozmiar strony (1-100, domyślnie 25).
cursorquerystringKursor następnej strony: wartość `next_cursor` z poprzedniej odpowiedzi.
statusquery"nowy" | "w_kampanii" | "odpowiedzial" | "zainteresowany" | "brak_zainteresowania" | "odrzucony"Filtr: status.
sourcequery"ceidg" | "krs" | "google_maps" | "pkt" | "firmy_net" | "panorama" | "osm" | "linkedin" | "import_csv" | "manual"Filtr: źródło.
tagquerystringFiltr: lead ma ten tag.
emailquerystringFiltr: dokładny adres e-mail.
qquerystringSzukaj we fragmencie nazwy firmy, imienia, nazwiska lub e-maila.
has_emailquery"true" | "false"Filtr: lead ma (true) lub nie ma (false) adresu e-mail.
created_afterquerystring (date-time)Filtr: utworzony od (ISO 8601).
created_beforequerystring (date-time)Filtr: utworzony do (ISO 8601).
workspace_idquerystringFiltr: klient agencji (ID z `GET /workspaces`) albo `default` dla "Mojej firmy". Bez parametru: cale konto.
Przykład (curl)
curl "https://outreachpilot.pl/api/v1/leads?limit=25&status=zainteresowany" \
  -H "Authorization: Bearer $OUTREACHPILOT_API_KEY"
Odpowiedź 200
{
  "data": [
    {
      "id": "0b6f2c1e-8a41-4f7e-9d3a-5c1f2a7b9e10",
      "email": "biuro@kowalski-instalacje.pl",
      "first_name": "Jan",
      "last_name": "Kowalski",
      "job_title": null,
      "phone": "+48 600 100 200",
      "linkedin_url": null,
      "company_name": "Kowalski Instalacje",
      "company_nip": "5252000001",
      "company_regon": null,
      "company_website": "https://kowalski-instalacje.pl",
      "company_city": "Kraków",
      "company_voivodeship": "małopolskie",
      "company_address": null,
      "company_industry": "Hydraulik",
      "company_pkd": "43.22.Z",
      "company_country": null,
      "status": "nowy",
      "source": "manual",
      "tags": [
        "hydraulicy-krakow"
      ],
      "notes": null,
      "ai_score": null,
      "email_status": "valid",
      "website_status": "has",
      "registry_status": "active",
      "origin": {
        "source": "manual",
        "source_detail": "api",
        "acquired_at": "2026-10-01T09:30:00.000Z"
      },
      "times_contacted": 0,
      "times_opened": 0,
      "times_clicked": 0,
      "times_replied": 0,
      "last_contacted_at": null,
      "created_at": "2026-10-01T09:30:00.000Z",
      "updated_at": "2026-10-01T09:30:00.000Z"
    }
  ],
  "pagination": {
    "limit": 25,
    "has_more": false,
    "next_cursor": null
  }
}
post/api/v1/leadszakres leads:write

Dodaj lead

Dodaje jeden lead. Zapis jest atomowy i zużywa miesięczny limit leadów (tak jak w aplikacji). Duplikat (ten sam e-mail, strona, telefon albo nazwa i miasto) zwraca 409 `duplicate_lead` i nic nie kosztuje. Wymagane: `company_name` lub `email`.

Edycja leadów: Dodawanie, aktualizacja, statusy, tagowanie, wzbogacanie i import

Pola ciała żądania
Pole (JSON)TypOpis
company_namestringNazwa firmy.
first_namestringImię.
last_namestringNazwisko.
emailstringAdres e-mail (unikalny w Twojej bazie).
phonestringTelefon.
job_titlestringStanowisko.
company_citystringMiasto.
company_industrystringBranża.
company_websitestringStrona WWW firmy.
company_addressstringAdres.
company_nipstringNIP.
company_regonstringREGON.
company_voivodeshipstringWojewództwo.
company_pkdstringKod PKD.
notesstringNotatki.
tagsarray<string>Tagi (maks. 20).
workspace_idstringKlient agencji, do ktorego trafia zapis (ID z `GET /workspaces`, klient musi byc aktywny). Bez pola: "Moja firma".
Przykład (curl)
curl -X POST "https://outreachpilot.pl/api/v1/leads" \
  -H "Authorization: Bearer $OUTREACHPILOT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "company_name": "Kowalski Instalacje",
    "email": "biuro@kowalski-instalacje.pl",
    "company_city": "Kraków",
    "tags": [
      "hydraulicy-krakow"
    ]
  }'
Odpowiedź 201
{
  "data": {
    "id": "0b6f2c1e-8a41-4f7e-9d3a-5c1f2a7b9e10",
    "email": "biuro@kowalski-instalacje.pl",
    "first_name": "Jan",
    "last_name": "Kowalski",
    "job_title": null,
    "phone": "+48 600 100 200",
    "linkedin_url": null,
    "company_name": "Kowalski Instalacje",
    "company_nip": "5252000001",
    "company_regon": null,
    "company_website": "https://kowalski-instalacje.pl",
    "company_city": "Kraków",
    "company_voivodeship": "małopolskie",
    "company_address": null,
    "company_industry": "Hydraulik",
    "company_pkd": "43.22.Z",
    "company_country": null,
    "status": "nowy",
    "source": "manual",
    "tags": [
      "hydraulicy-krakow"
    ],
    "notes": null,
    "ai_score": null,
    "email_status": "valid",
    "website_status": "has",
    "registry_status": "active",
    "origin": {
      "source": "manual",
      "source_detail": "api",
      "acquired_at": "2026-10-01T09:30:00.000Z"
    },
    "times_contacted": 0,
    "times_opened": 0,
    "times_clicked": 0,
    "times_replied": 0,
    "last_contacted_at": null,
    "created_at": "2026-10-01T09:30:00.000Z",
    "updated_at": "2026-10-01T09:30:00.000Z"
  }
}

Błędy tej operacji: validation_error (422), duplicate_lead (409), lead_limit_reached (402). Wspólne dla wszystkich: 401, 403, 429.

post/api/v1/leads/bulkzakres leads:write

Dodaj wiele leadów (do 500)

Importuje do 500 leadów w jednym żądaniu. Każdy element jest walidowany osobno: błędny trafia do `rejected`, duplikat do `duplicates`, reszta jest zapisywana do wyczerpania limitu (nadmiar w `omitted_by_limit`). Odpowiedź 200 nawet przy częściowym sukcesie; wynik per indeks.

Edycja leadów: Dodawanie, aktualizacja, statusy, tagowanie, wzbogacanie i import

Pola ciała żądania
Pole (JSON)TypOpis
leads*array<any>Lista leadów (1-500). Każdy element ma pola jak w POST /leads; błędny element nie psuje reszty.
tagstringTag dodawany do wszystkich zapisanych leadów.
workspace_idstringKlient agencji, do ktorego trafia zapis (ID z `GET /workspaces`, klient musi byc aktywny). Bez pola: "Moja firma".
Przykład (curl)
curl -X POST "https://outreachpilot.pl/api/v1/leads/bulk" \
  -H "Authorization: Bearer $OUTREACHPILOT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "tag": "import-api",
    "leads": [
      {
        "company_name": "Firma A",
        "email": "a@firma-a.pl"
      },
      {
        "company_name": "Firma B",
        "email": "b@firma-b.pl",
        "company_city": "Gdańsk"
      }
    ]
  }'
Odpowiedź 200
{
  "data": {
    "created": [
      {
        "index": 0,
        "id": "0b6f2c1e-8a41-4f7e-9d3a-5c1f2a7b9e10"
      }
    ],
    "duplicates": [
      {
        "index": 1,
        "reason": "already_in_database_or_request"
      }
    ],
    "rejected": [],
    "omitted_by_limit": [],
    "limited_by": null,
    "remaining_quota": 787
  }
}

Błędy tej operacji: validation_error (422), lead_limit_reached (402). Wspólne dla wszystkich: 401, 403, 429.

get/api/v1/leads/{id}zakres leads:read

Pobierz lead

Jeden lead po identyfikatorze.

Czytanie leadów: Twoja baza firm i kontaktów oraz tagi (tylko odczyt)

Parametry
ParametrTypOpis
id*ścieżkauuidIdentyfikator zasobu.
Przykład (curl)
curl "https://outreachpilot.pl/api/v1/leads/0b6f2c1e-8a41-4f7e-9d3a-5c1f2a7b9e10" \
  -H "Authorization: Bearer $OUTREACHPILOT_API_KEY"
Odpowiedź 200
{
  "data": {
    "id": "0b6f2c1e-8a41-4f7e-9d3a-5c1f2a7b9e10",
    "email": "biuro@kowalski-instalacje.pl",
    "first_name": "Jan",
    "last_name": "Kowalski",
    "job_title": null,
    "phone": "+48 600 100 200",
    "linkedin_url": null,
    "company_name": "Kowalski Instalacje",
    "company_nip": "5252000001",
    "company_regon": null,
    "company_website": "https://kowalski-instalacje.pl",
    "company_city": "Kraków",
    "company_voivodeship": "małopolskie",
    "company_address": null,
    "company_industry": "Hydraulik",
    "company_pkd": "43.22.Z",
    "company_country": null,
    "status": "nowy",
    "source": "manual",
    "tags": [
      "hydraulicy-krakow"
    ],
    "notes": null,
    "ai_score": null,
    "email_status": "valid",
    "website_status": "has",
    "registry_status": "active",
    "origin": {
      "source": "manual",
      "source_detail": "api",
      "acquired_at": "2026-10-01T09:30:00.000Z"
    },
    "times_contacted": 0,
    "times_opened": 0,
    "times_clicked": 0,
    "times_replied": 0,
    "last_contacted_at": null,
    "created_at": "2026-10-01T09:30:00.000Z",
    "updated_at": "2026-10-01T09:30:00.000Z"
  }
}

Błędy tej operacji: not_found (404). Wspólne dla wszystkich: 401, 403, 429.

patch/api/v1/leads/{id}zakres leads:write

Zmień lead

Częściowa aktualizacja pól, statusu i tagów (`tags` zastępuje całą listę). `null` czyści wartość. Zmiana `status` wysyła webhook `lead.status_changed` (`changed_by: api`).

Edycja leadów: Dodawanie, aktualizacja, statusy, tagowanie, wzbogacanie i import

Parametry
ParametrTypOpis
id*ścieżkauuidIdentyfikator zasobu.
Pola ciała żądania
Pole (JSON)TypOpis
company_nameany
first_nameany
last_nameany
emailany
phoneany
job_titleany
company_cityany
company_industryany
company_websiteany
company_addressany
company_nipany
company_regonany
company_voivodeshipany
company_pkdany
linkedin_urlany
notesany
tagsarray<string>Zastępuje całą listę tagów.
status"nowy" | "w_kampanii" | "odpowiedzial" | "zainteresowany" | "brak_zainteresowania" | "odrzucony"Status leada. Zmiana wywołuje webhook `lead.status_changed`.
Przykład (curl)
curl -X PATCH "https://outreachpilot.pl/api/v1/leads/0b6f2c1e-8a41-4f7e-9d3a-5c1f2a7b9e10" \
  -H "Authorization: Bearer $OUTREACHPILOT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "zainteresowany",
    "notes": "Chce wycenę do piątku"
  }'
Odpowiedź 200
{
  "data": {
    "id": "0b6f2c1e-8a41-4f7e-9d3a-5c1f2a7b9e10",
    "email": "biuro@kowalski-instalacje.pl",
    "first_name": "Jan",
    "last_name": "Kowalski",
    "job_title": null,
    "phone": "+48 600 100 200",
    "linkedin_url": null,
    "company_name": "Kowalski Instalacje",
    "company_nip": "5252000001",
    "company_regon": null,
    "company_website": "https://kowalski-instalacje.pl",
    "company_city": "Kraków",
    "company_voivodeship": "małopolskie",
    "company_address": null,
    "company_industry": "Hydraulik",
    "company_pkd": "43.22.Z",
    "company_country": null,
    "status": "zainteresowany",
    "source": "manual",
    "tags": [
      "hydraulicy-krakow"
    ],
    "notes": "Chce wycenę do piątku",
    "ai_score": null,
    "email_status": "valid",
    "website_status": "has",
    "registry_status": "active",
    "origin": {
      "source": "manual",
      "source_detail": "api",
      "acquired_at": "2026-10-01T09:30:00.000Z"
    },
    "times_contacted": 0,
    "times_opened": 0,
    "times_clicked": 0,
    "times_replied": 0,
    "last_contacted_at": null,
    "created_at": "2026-10-01T09:30:00.000Z",
    "updated_at": "2026-10-01T09:30:00.000Z"
  }
}

Błędy tej operacji: not_found (404), validation_error (422), duplicate_lead (409). Wspólne dla wszystkich: 401, 403, 429.

delete/api/v1/leads/{id}zakres leads:delete

Usuń lead

Trwale usuwa lead (operacja nieodwracalna). Wymaga osobnego, destrukcyjnego zakresu `leads:delete`. Odpowiedź 204 bez treści.

Usuwanie leadów: Trwałe kasowanie - akcja nieodwracalna

Parametry
ParametrTypOpis
id*ścieżkauuidIdentyfikator zasobu.
Przykład (curl)
curl -X DELETE "https://outreachpilot.pl/api/v1/leads/0b6f2c1e-8a41-4f7e-9d3a-5c1f2a7b9e10" \
  -H "Authorization: Bearer $OUTREACHPILOT_API_KEY"
Odpowiedź 204
(bez treści)

Błędy tej operacji: not_found (404). Wspólne dla wszystkich: 401, 403, 429.

Kampanie

get/api/v1/campaignszakres campaigns:read

Lista kampanii

Kampanie konta od najnowszych, stronicowane kursorem.

Czytanie kampanii: Lista kampanii, sekwencje wiadomości, statystyki i historia wysyłki

Parametry
ParametrTypOpis
limitqueryinteger (min 1, max 100)Rozmiar strony (1-100, domyślnie 25).
cursorquerystringKursor następnej strony: wartość `next_cursor` z poprzedniej odpowiedzi.
statusquery"szkic" | "aktywna" | "wstrzymana" | "zakonczona" | "blad"Filtr: status kampanii.
workspace_idquerystringFiltr: klient agencji (ID z `GET /workspaces`) albo `default` dla "Mojej firmy". Bez parametru: cale konto.
Przykład (curl)
curl "https://outreachpilot.pl/api/v1/campaigns?status=aktywna" \
  -H "Authorization: Bearer $OUTREACHPILOT_API_KEY"
Odpowiedź 200
{
  "data": [
    {
      "id": "5d0e7a43-2c19-4b8a-a6f1-3e9b8c4d2a77",
      "name": "Hydraulicy Kraków · 1 paź",
      "status": "szkic",
      "email_account_id": "a1b2c3d4-0000-4000-8000-000000000001",
      "daily_limit": 20,
      "sending_window_start": "08:00",
      "sending_window_end": "17:00",
      "track_opens": true,
      "track_clicks": true,
      "total_leads": 1,
      "total_sent": 0,
      "total_opened": 0,
      "total_clicked": 0,
      "total_replied": 0,
      "total_bounced": 0,
      "started_at": null,
      "completed_at": null,
      "created_at": "2026-10-01T09:35:00.000Z",
      "updated_at": "2026-10-01T09:35:00.000Z"
    }
  ],
  "pagination": {
    "limit": 25,
    "has_more": false,
    "next_cursor": null
  }
}
post/api/v1/campaignszakres campaigns:write

Utwórz kampanię (szkic)

Tworzy kampanię w statusie `szkic` z pierwszymi leadami (do 200; kolejne dodasz przez `POST /campaigns/{id}/leads`): NIC nie jest wysyłane. Uruchomienie to osobne wywołanie `POST /campaigns/{id}/status`. Te same walidacje i limity planu co w aplikacji (limit kampanii, poprawność skrzynki, pomijanie leadów odrzuconych i wypisanych).

Tworzenie kampanii: Tworzenie szkiców kampanii, dodawanie leadów, uruchamianie i pauza (start wysyła prawdziwe maile)

Pola ciała żądania
Pole (JSON)TypOpis
namestringNazwa kampanii. Bez niej powstaje nazwa automatyczna (branża, miasto, data).
lead_ids*array<string>Leady do kampanii (1-200). Resztę dodaj przez POST /campaigns/{id}/leads (do 1000 na żądanie). Odrzuceni, z bounce'em i wypisani są pomijani.
email_account_idstringSkrzynka nadawcza. Domyślnie pierwsza podłączona.
steps*array<object>Sekwencja wiadomości (1-10 kroków).
daily_limitinteger (min 1, max 200)Maks. wiadomości dziennie z tej kampanii.
sending_window_startstringPoczątek okna wysyłki (HH:MM, czas Warszawy).
sending_window_endstringKoniec okna wysyłki (HH:MM).
track_opensboolean
track_clicksboolean
workspace_idstringKlient agencji, do którego należy kampania (ID z `GET /workspaces`). Bez pola kampania dziedziczy klienta po swoich leadach (leady z różnych klientów to błąd 422). Leady i skrzynka muszą być tego klienta.
Przykład (curl)
curl -X POST "https://outreachpilot.pl/api/v1/campaigns" \
  -H "Authorization: Bearer $OUTREACHPILOT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Hydraulicy Kraków",
    "lead_ids": [
      "0b6f2c1e-8a41-4f7e-9d3a-5c1f2a7b9e10"
    ],
    "steps": [
      {
        "subject": "Pytanie o instalacje w {{firma}}",
        "body": "Dzień dobry {{imie}}, ..."
      },
      {
        "subject": "Re: Pytanie o instalacje w {{firma}}",
        "body": "Wracam do tematu ...",
        "delay_days": 3
      }
    ],
    "daily_limit": 20
  }'
Odpowiedź 201
{
  "data": {
    "id": "5d0e7a43-2c19-4b8a-a6f1-3e9b8c4d2a77",
    "name": "Hydraulicy Kraków · 1 paź",
    "status": "szkic",
    "email_account_id": "a1b2c3d4-0000-4000-8000-000000000001",
    "daily_limit": 20,
    "sending_window_start": "08:00",
    "sending_window_end": "17:00",
    "track_opens": true,
    "track_clicks": true,
    "total_leads": 1,
    "total_sent": 0,
    "total_opened": 0,
    "total_clicked": 0,
    "total_replied": 0,
    "total_bounced": 0,
    "started_at": null,
    "completed_at": null,
    "created_at": "2026-10-01T09:35:00.000Z",
    "updated_at": "2026-10-01T09:35:00.000Z",
    "steps": [
      {
        "step_number": 1,
        "delay_days": 0,
        "condition": "brak_odpowiedzi",
        "subject": "Pytanie o instalacje w {{firma}}",
        "body": "Dzień dobry {{imie}}, ..."
      },
      {
        "step_number": 2,
        "delay_days": 3,
        "condition": "brak_odpowiedzi",
        "subject": "Re: Pytanie o instalacje w {{firma}}",
        "body": "Wracam do tematu ..."
      }
    ]
  }
}

Błędy tej operacji: validation_error (422), invalid_request (400), plan_upgrade_required (403). Wspólne dla wszystkich: 401, 403, 429.

get/api/v1/campaigns/{id}zakres campaigns:read

Pobierz kampanię

Kampania z ustawieniami, licznikami i sekwencją wiadomości.

Czytanie kampanii: Lista kampanii, sekwencje wiadomości, statystyki i historia wysyłki

Parametry
ParametrTypOpis
id*ścieżkauuidIdentyfikator zasobu.
Przykład (curl)
curl "https://outreachpilot.pl/api/v1/campaigns/0b6f2c1e-8a41-4f7e-9d3a-5c1f2a7b9e10" \
  -H "Authorization: Bearer $OUTREACHPILOT_API_KEY"
Odpowiedź 200
{
  "data": {
    "id": "5d0e7a43-2c19-4b8a-a6f1-3e9b8c4d2a77",
    "name": "Hydraulicy Kraków · 1 paź",
    "status": "szkic",
    "email_account_id": "a1b2c3d4-0000-4000-8000-000000000001",
    "daily_limit": 20,
    "sending_window_start": "08:00",
    "sending_window_end": "17:00",
    "track_opens": true,
    "track_clicks": true,
    "total_leads": 1,
    "total_sent": 0,
    "total_opened": 0,
    "total_clicked": 0,
    "total_replied": 0,
    "total_bounced": 0,
    "started_at": null,
    "completed_at": null,
    "created_at": "2026-10-01T09:35:00.000Z",
    "updated_at": "2026-10-01T09:35:00.000Z",
    "steps": [
      {
        "step_number": 1,
        "delay_days": 0,
        "condition": "brak_odpowiedzi",
        "subject": "Pytanie o instalacje w {{firma}}",
        "body": "Dzień dobry {{imie}}, ..."
      },
      {
        "step_number": 2,
        "delay_days": 3,
        "condition": "brak_odpowiedzi",
        "subject": "Re: Pytanie o instalacje w {{firma}}",
        "body": "Wracam do tematu ..."
      }
    ]
  }
}

Błędy tej operacji: not_found (404). Wspólne dla wszystkich: 401, 403, 429.

post/api/v1/campaigns/{id}/leadszakres campaigns:write

Dodaj leady do kampanii

Dodaje leady do istniejącej kampanii (do 1000). Pomija leady już obecne, odrzucone, z bounce'em i wypisane. Gdy kampania jest aktywna, nowe leady od razu wchodzą do wysyłki.

Tworzenie kampanii: Tworzenie szkiców kampanii, dodawanie leadów, uruchamianie i pauza (start wysyła prawdziwe maile)

Parametry
ParametrTypOpis
id*ścieżkauuidIdentyfikator zasobu.
Pola ciała żądania
Pole (JSON)TypOpis
lead_ids*array<string>Leady do dodania (1-1000).
Przykład (curl)
curl -X POST "https://outreachpilot.pl/api/v1/campaigns/0b6f2c1e-8a41-4f7e-9d3a-5c1f2a7b9e10/leads" \
  -H "Authorization: Bearer $OUTREACHPILOT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "lead_ids": [
      "0b6f2c1e-8a41-4f7e-9d3a-5c1f2a7b9e10"
    ]
  }'
Odpowiedź 200
{
  "data": {
    "added": 1,
    "skipped": 0,
    "campaign_status": "szkic",
    "notice": null
  }
}

Błędy tej operacji: not_found (404), validation_error (422), invalid_request (400). Wspólne dla wszystkich: 401, 403, 429.

post/api/v1/campaigns/{id}/statuszakres campaigns:write

Uruchom, wstrzymaj lub wznów kampanię

`start` uruchamia szkic, `pause` wstrzymuje aktywną, `resume` wznawia wstrzymaną. Start i wznowienie WYSYŁAJĄ PRAWDZIWE MAILE. Jeśli są ostrzeżenia (warmup, DNS, limit domeny), odpowiedź ma kod `confirmation_required`; ponów żądanie z `acknowledge_warnings: true`, gdy świadomie chcesz kontynuować. Powtórzenie tej samej akcji jest bezpieczne (`changed: false`).

Tworzenie kampanii: Tworzenie szkiców kampanii, dodawanie leadów, uruchamianie i pauza (start wysyła prawdziwe maile)

Parametry
ParametrTypOpis
id*ścieżkauuidIdentyfikator zasobu.
Pola ciała żądania
Pole (JSON)TypOpis
action*"start" | "pause" | "resume"`start` uruchamia szkic, `pause` wstrzymuje, `resume` wznawia wstrzymaną. Start i wznowienie wysyłają prawdziwe maile.
acknowledge_warningsbooleanPotwierdź ostrzeżenia (np. warmup, DNS), gdy poprzednia odpowiedź miała kod `confirmation_required`.
Przykład (curl)
curl -X POST "https://outreachpilot.pl/api/v1/campaigns/0b6f2c1e-8a41-4f7e-9d3a-5c1f2a7b9e10/status" \
  -H "Authorization: Bearer $OUTREACHPILOT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "start"
  }'
Odpowiedź 200
{
  "data": {
    "id": "5d0e7a43-2c19-4b8a-a6f1-3e9b8c4d2a77",
    "name": "Hydraulicy Kraków · 1 paź",
    "status": "aktywna",
    "email_account_id": "a1b2c3d4-0000-4000-8000-000000000001",
    "daily_limit": 20,
    "sending_window_start": "08:00",
    "sending_window_end": "17:00",
    "track_opens": true,
    "track_clicks": true,
    "total_leads": 1,
    "total_sent": 0,
    "total_opened": 0,
    "total_clicked": 0,
    "total_replied": 0,
    "total_bounced": 0,
    "started_at": "2026-10-01T10:00:00.000Z",
    "completed_at": null,
    "created_at": "2026-10-01T09:35:00.000Z",
    "updated_at": "2026-10-01T09:35:00.000Z",
    "steps": [
      {
        "step_number": 1,
        "delay_days": 0,
        "condition": "brak_odpowiedzi",
        "subject": "Pytanie o instalacje w {{firma}}",
        "body": "Dzień dobry {{imie}}, ..."
      },
      {
        "step_number": 2,
        "delay_days": 3,
        "condition": "brak_odpowiedzi",
        "subject": "Re: Pytanie o instalacje w {{firma}}",
        "body": "Wracam do tematu ..."
      }
    ]
  },
  "changed": true
}

Błędy tej operacji: not_found (404), conflict (409), confirmation_required (409), plan_upgrade_required (403). Wspólne dla wszystkich: 401, 403, 429.

get/api/v1/campaigns/{id}/statszakres analytics:read

Statystyki kampanii

Sumy i wskaźniki kampanii oraz wyniki per krok. Odpowiedzi liczone są jako odpowiedzi LUDZI (zwroty i autorespondery systemowe wykluczone). Otwarcia są orientacyjne (Apple Mail je zawyża), dlatego wskaźnikiem jakości jest `reply_rate`.

Statystyki kampanii: Wyniki kampanii i kroków: wysłane, otwarcia, odpowiedzi, zwroty (tylko odczyt)

Parametry
ParametrTypOpis
id*ścieżkauuidIdentyfikator zasobu.
Przykład (curl)
curl "https://outreachpilot.pl/api/v1/campaigns/0b6f2c1e-8a41-4f7e-9d3a-5c1f2a7b9e10/stats" \
  -H "Authorization: Bearer $OUTREACHPILOT_API_KEY"
Odpowiedź 200
{
  "data": {
    "campaign_id": "5d0e7a43-2c19-4b8a-a6f1-3e9b8c4d2a77",
    "status": "aktywna",
    "totals": {
      "leads": 120,
      "sent": 210,
      "opened": 96,
      "clicked": 11,
      "replied": 9,
      "bounced": 3
    },
    "rates": {
      "open_rate": 0.4571,
      "click_rate": 0.0524,
      "reply_rate": 0.0429,
      "bounce_rate": 0.0143
    },
    "steps": [
      {
        "step_number": 1,
        "sent": 120,
        "opened": 70,
        "clicked": 8,
        "replied": 6
      },
      {
        "step_number": 2,
        "sent": 90,
        "opened": 26,
        "clicked": 3,
        "replied": 3
      }
    ]
  }
}

Błędy tej operacji: not_found (404). Wspólne dla wszystkich: 401, 403, 429.

Odpowiedzi

get/api/v1/replieszakres inbox:read

Odpowiedzi od leadów

Odpowiedzi LUDZI na Twoje wiadomości, od najnowszych. Zwroty (bounce, NDR, autorespondery systemowe) nie trafiają na listę. Domyślnie zwracamy początek odpowiedzi (`snippet`); pełna treść tylko z `include_text=true`.

Czytanie inboxu: Odpowiedzi od leadów (pełna treść) i propozycje agentów AI

Parametry
ParametrTypOpis
limitqueryinteger (min 1, max 100)Rozmiar strony (1-100, domyślnie 25).
cursorquerystringKursor następnej strony: wartość `next_cursor` z poprzedniej odpowiedzi.
campaign_idquerystringFiltr: kampania.
lead_idquerystringFiltr: lead.
sentimentquery"interested" | "question" | "maybe" | "not_interested" | "unsubscribe"Filtr: klasyfikacja odpowiedzi.
unreadquery"true" | "false"`true` = tylko nieprzeczytane.
sincequerystring (date-time)Tylko odpowiedzi od tej chwili (ISO 8601).
include_textquery"true" | "false"Dołącz pełną treść odpowiedzi (`text`).
workspace_idquerystringFiltr: klient agencji (ID z `GET /workspaces`) albo `default` dla "Mojej firmy". Bez parametru: cale konto.
Przykład (curl)
curl "https://outreachpilot.pl/api/v1/replies?sentiment=interested&unread=true" \
  -H "Authorization: Bearer $OUTREACHPILOT_API_KEY"
Odpowiedź 200
{
  "data": [
    {
      "id": "9a7c1d52-6e3b-4f08-8d21-7b5e0c9a4f36",
      "replied_at": "2026-10-01T12:05:00.000Z",
      "subject": "Pytanie o instalacje w Kowalski Instalacje",
      "snippet": "Dzień dobry, chętnie porozmawiam, proszę o kontakt w czwartek...",
      "sentiment": "interested",
      "is_read": false,
      "campaign": {
        "id": "5d0e7a43-2c19-4b8a-a6f1-3e9b8c4d2a77",
        "name": "Hydraulicy Kraków"
      },
      "lead": {
        "id": "0b6f2c1e-8a41-4f7e-9d3a-5c1f2a7b9e10",
        "email": "biuro@kowalski-instalacje.pl",
        "company_name": "Kowalski Instalacje",
        "first_name": "Jan",
        "last_name": "Kowalski"
      }
    }
  ],
  "pagination": {
    "limit": 25,
    "has_more": false,
    "next_cursor": null
  }
}

Webhooki

get/api/v1/webhookszakres webhooks:read

Lista webhooków

Twoje webhooki (bez sekretów). Format zdarzeń, nagłówki i weryfikacja podpisu: sekcja Webhooki w dokumentacji.

Czytanie webhooków: Lista Twoich webhooków (bez sekretów)

Przykład (curl)
curl "https://outreachpilot.pl/api/v1/webhooks" \
  -H "Authorization: Bearer $OUTREACHPILOT_API_KEY"
Odpowiedź 200
{
  "data": [
    {
      "id": "c3d4e5f6-0000-4000-8000-000000000002",
      "url": "https://hook.eu2.make.com/abc123",
      "events": [
        "lead.replied",
        "lead.status_changed"
      ],
      "is_active": true,
      "created_at": "2026-09-30T08:00:00.000Z",
      "last_triggered_at": "2026-10-01T12:05:01.000Z",
      "fail_count": 0
    }
  ]
}
post/api/v1/webhookszakres webhooks:write

Dodaj webhook

Rejestruje publiczny adres HTTPS, na który wysyłamy podpisane (HMAC-SHA256) zdarzenia. Sekret podpisu jest zwracany TYLKO w tej odpowiedzi. Limit: 10 webhooków na konto. Zdarzenia `lead.*` i `email.*` zawierają adresy e-mail i nazwy firm, więc ich subskrypcja wymaga też zakresu `leads:read` (403 `insufficient_scope`).

Zarządzanie webhookami: Dodawanie i usuwanie webhooków; sekret pokazujemy jeden raz przy tworzeniu. Zdarzenia lead.* i email.* zawierają adresy e-mail i nazwy firm, więc ich subskrypcja wymaga też leads:read

Pola ciała żądania
Pole (JSON)TypOpis
url*string (uri)Publiczny adres HTTPS (Make, Zapier, n8n, własny serwer).
events*array<"lead.created" | "lead.status_changed" | "lead.replied" | "lead.scored" | "email.opened" | "email.clicked" | "email.bounced" | "email.unsubscribed" | "campaign.started" | "campaign.completed" | "rule.triggered">Zdarzenia do wysyłania.
Przykład (curl)
curl -X POST "https://outreachpilot.pl/api/v1/webhooks" \
  -H "Authorization: Bearer $OUTREACHPILOT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://hook.eu2.make.com/abc123",
    "events": [
      "lead.replied",
      "lead.status_changed"
    ]
  }'
Odpowiedź 201
{
  "data": {
    "id": "c3d4e5f6-0000-4000-8000-000000000002",
    "url": "https://hook.eu2.make.com/abc123",
    "events": [
      "lead.replied",
      "lead.status_changed"
    ],
    "is_active": true,
    "created_at": "2026-10-01T10:00:00.000Z",
    "last_triggered_at": null,
    "fail_count": 0,
    "secret": "5f2c…(64 znaki hex)"
  }
}

Błędy tej operacji: validation_error (422), conflict (409), insufficient_scope (403). Wspólne dla wszystkich: 401, 403, 429.

delete/api/v1/webhooks/{id}zakres webhooks:write

Usuń webhook

Usuwa webhook i jego dziennik dostaw. Odpowiedź 204 bez treści.

Zarządzanie webhookami: Dodawanie i usuwanie webhooków; sekret pokazujemy jeden raz przy tworzeniu. Zdarzenia lead.* i email.* zawierają adresy e-mail i nazwy firm, więc ich subskrypcja wymaga też leads:read

Parametry
ParametrTypOpis
id*ścieżkauuidIdentyfikator zasobu.
Przykład (curl)
curl -X DELETE "https://outreachpilot.pl/api/v1/webhooks/0b6f2c1e-8a41-4f7e-9d3a-5c1f2a7b9e10" \
  -H "Authorization: Bearer $OUTREACHPILOT_API_KEY"
Odpowiedź 204
(bez treści)

Błędy tej operacji: not_found (404). Wspólne dla wszystkich: 401, 403, 429.

Webhooki

Webhooki wysyłają do Twojego systemu podpisany POST z JSON-em, gdy coś się wydarzy. Rejestrujesz je w Ustawieniach albo przez POST /webhooks. Ładunek nie zawiera treści wiadomości, tylko identyfikatory, adres e-mail leada i metadane. Treść odpowiedzi pobierzesz przez GET /replies?include_text=true.

ZdarzenieKiedy
lead.createdNowy lead trafił do Twojej bazy (wyszukiwarka, import, ręcznie, czat/MCP).
lead.status_changedStatus leada zmienił się ręcznie albo automatycznie (odpowiedź, wypisanie, zwrot).
lead.repliedLead odpowiedział na wiadomość z kampanii (bez zwrotów i autoodpowiedzi).
lead.scoredAI oceniło leada (skala 1-10).
email.openedPierwsze otwarcie wiadomości. Apple Mail zawyża otwarcia, traktuj je orientacyjnie.
email.clickedPierwsze kliknięcie linku w wiadomości.
email.bouncedWiadomość nie została dostarczona (zły adres, skrzynka nie istnieje).
email.unsubscribedOdbiorca wypisał się (link w stopce, nagłówek List-Unsubscribe albo Apple Mail).
campaign.startedKampania przeszła ze szkicu do wysyłki.
campaign.completedKampania skończyła sekwencję dla wszystkich leadów albo została zakończona ręcznie.
rule.triggeredReguła z akcją „Wyślij webhook” zadziałała dla firmy (np. po odpowiedzi zainteresowanej albo po 3 otwarciach bez odpowiedzi).
Przykładowe żądanie (lead.replied)
{
  "id": "evt_4f1c2a9e-7b3d-4e8a-9c05-1d6a2b7e3f90",
  "type": "lead.replied",
  "created_at": "2026-10-01T12:05:00.000Z",
  "data": {
    "lead_id": "0b6f2c1e-8a41-4f7e-9d3a-5c1f2a7b9e10",
    "email": "biuro@example.pl",
    "company_name": "Przykładowa Sp. z o.o.",
    "campaign_id": "5d0e7a43-2c19-4b8a-a6f1-3e9b8c4d2a77",
    "email_id": "9a7c1d52-6e3b-4f08-8d21-7b5e0c9a4f36",
    "replied_at": "2026-09-30T12:00:00.000Z"
  }
}

Nagłówki i weryfikacja podpisu

  • X-OutreachPilot-Signature: sha256= i HMAC-SHA256 dokładnej treści żądania, kluczem jest sekret webhooka (pokazywany jeden raz przy tworzeniu).
  • X-OutreachPilot-Event (typ), X-OutreachPilot-Delivery (id zdarzenia, stałe przy ponowieniu), X-OutreachPilot-Timestamp (sekundy Unix, nie jest objęty podpisem).
  • Ochrona przed powtórzeniem: sprawdzaj created_at z treści (jest podpisane) i deduplikuj zdarzenia po polu id.
  • Odpowiedz kodem 2xx w ciągu 10 sekund. Przekierowania (3xx) liczymy jako błąd. Po 10 nieudanych dostawach z rzędu wstrzymujemy wysyłkę na dany adres, dopóki go nie włączysz ponownie.
Node.js
const crypto = require("crypto")

// rawBody = dokładna treść żądania (Buffer albo string, PRZED parsowaniem JSON)
function isValid(rawBody, headers, secret) {
  const expected = "sha256=" + crypto.createHmac("sha256", secret).update(rawBody).digest("hex")
  const received = headers["x-outreachpilot-signature"] || ""
  const signatureOk = expected.length === received.length && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received))
  if (!signatureOk) return false
  // Podpis obejmuje treść, ale NIE nagłówek Timestamp: wiek sprawdzaj po created_at z treści
  // i deduplikuj zdarzenia po polu id (to samo id = to samo zdarzenie).
  const { created_at } = JSON.parse(rawBody)
  return Math.abs(Date.now() - Date.parse(created_at)) < 5 * 60 * 1000
}
Python
import hashlib, hmac, json, time
from datetime import datetime

def is_valid(raw_body: bytes, headers: dict, secret: str) -> bool:
    expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    received = headers.get("x-outreachpilot-signature", "")
    if not hmac.compare_digest(expected, received):
        return False
    # Podpis obejmuje treść, ale NIE nagłówek Timestamp: wiek sprawdzaj po created_at z treści
    # i deduplikuj zdarzenia po polu id.
    created = datetime.fromisoformat(json.loads(raw_body)["created_at"].replace("Z", "+00:00"))
    return abs(time.time() - created.timestamp()) < 300

Make, Zapier i n8n

Nie publikujemy jeszcze własnych aplikacji w Make ani Zapierze. Wszystko, co potrzebne, działa dziś przez dwa elementy: webhooki (OutreachPilot woła Twój scenariusz) i klucz API (Twój scenariusz woła OutreachPilot).

Make

  1. Wyzwalacz: moduł Webhooks → Custom webhook, skopiuj adres URL.
  2. W OutreachPilot dodaj ten adres w Ustawienia → Webhooki, wybierz zdarzenia (np. lead.replied) i kliknij „Wyślij test”. Make rozpozna strukturę danych.
  3. Akcja: moduł HTTP → Make a request woła API:
Make: HTTP → Make a request
URL:          https://outreachpilot.pl/api/v1/leads
Method:       POST
Headers:      Authorization: Bearer op_live_TWÓJ_KLUCZ
Body type:    Raw
Content type: JSON (application/json)
Request body: {"company_name": "{{1.firma}}", "email": "{{1.email}}", "tags": ["make"]}
Parse response: Yes

Typowe scenariusze: odpowiedź pozytywna → dodaj deal w CRM i wyślij powiadomienie; nowy wpis z formularza → POST /leads → POST /campaigns/{id}/leads.

Zapier

  1. Wyzwalacz: Webhooks by Zapier → Catch Hook; adres dodaj w Ustawienia → Webhooki i wyślij test.
  2. Akcja: Webhooks by Zapier → Custom Request: metoda POST, URL https://outreachpilot.pl/api/v1/leads, nagłówki Authorization: Bearer op_live_… i Content-Type: application/json, dane jako JSON.
  3. Do pobierania danych użyj Custom Request z metodą GET (np. /replies?sentiment=interested).

n8n

  1. Wyzwalacz: węzeł Webhook (metoda POST). Adres „Production URL” dodaj w Ustawienia → Webhooki.
  2. Akcja: węzeł HTTP Request z poświadczeniem Header Auth (nazwa Authorization, wartość Bearer op_live_…).
  3. Opcjonalnie sprawdź podpis (zalecane, gdy adres webhooka jest publiczny):
n8n: węzeł Code
// Node "Code" po węźle Webhook (Options → Raw Body: włączone)
const crypto = require("crypto")
const raw = $input.first().binary?.data ? Buffer.from($input.first().binary.data.data, "base64") : Buffer.from(JSON.stringify($input.first().json.body))
const expected = "sha256=" + crypto.createHmac("sha256", $env.OUTREACHPILOT_WEBHOOK_SECRET).update(raw).digest("hex")
if (expected !== $input.first().json.headers["x-outreachpilot-signature"]) throw new Error("Zły podpis")
return $input.all()

Wskazówki: trzymaj klucz w poświadczeniach narzędzia (nie w treści scenariusza), nadaj mu tylko potrzebne zakresy i obsłuż 429 (czekaj Retry-After sekund).

Integracje CRM (push z OutreachPilot)

OutreachPilot wysyła leady do Twojego CRM w jedną stronę. Konfiguracja jest w Ustawienia → API i integracje → Integracje CRM. Dostępne w planach Pro, Business i Agencja. Wysyłka dzieje się w tle (z ponowieniami), więc nie spowalnia aplikacji.

Kiedy wysyłamy

  • Odpowiedź pozytywna lub pytanie (domyślnie włączone). Odmowy, wypisy i zwroty nie są wysyłane.
  • Status leada zmieniony na „zainteresowany” (domyślnie włączone).
  • Ręcznie: przycisk „Wyślij do CRM” na karcie leada i w menu zaznaczenia na liście leadów.

Dla każdego leada pamiętamy identyfikatory obiektów w CRM, więc ponowna wysyłka niczego nie dubluje. Istniejących danych w CRM nie nadpisujemy (uzupełniamy tylko puste pola). Treści maili nie wysyłamy, chyba że włączysz „Dołącz treść odpowiedzi”. Ostatnie 20 zdarzeń widać w dzienniku synchronizacji.

Pipedrive

  1. W Pipedrive: Ustawienia osobiste → API → skopiuj osobisty token.
  2. W OutreachPilot podaj domenę firmy (część przed .pipedrive.com) i token, kliknij „Połącz i sprawdź”.
  3. Opcjonalnie włącz „Twórz deal” i wybierz lejek oraz etap.
OutreachPilotPipedrive
Nazwa firmy, NIPOrganizacja (szukamy po polu niestandardowym „NIP”, jeśli je masz, potem po nazwie)
E-mail, imię i nazwisko, telefonOsoba (szukamy po e-mailu), powiązana z organizacją
Kampania, link do leadaNotatka przy deal/osobie/organizacji
(opcja) dealDeal w wybranym lejku i etapie, powiązany z osobą i organizacją

HubSpot

  1. W HubSpot: Ustawienia → Integracje → Prywatne aplikacje → utwórz aplikację z zakresami crm.objects.contacts, crm.objects.companies (odczyt i zapis) oraz, dla dealów, crm.objects.deals.
  2. Skopiuj token (pat-…) do OutreachPilot i kliknij „Połącz i sprawdź”.
OutreachPilotHubSpot
Nazwa firmy, domena stronyCompany (szukamy po domenie, potem po nazwie)
E-mail, imię i nazwisko, telefon, stanowiskoContact (szukamy po e-mailu), powiązany z Company
Kampania, link do leadaNotatka powiązana z kontaktem, firmą i dealem
(opcja) dealDeal w wybranym lejku i etapie, powiązany z kontaktem i firmą

Livespace

Livespace ma API (klucz i sekret konta, adres https://TWOJA-DOMENA.livespace.io/api/public/json/…; według pomocy Livespace dostępne od pakietu Automation), ale nie utrzymujemy natywnego konektora: pełna specyfikacja pól dealów nie jest publicznie dostępna. Dwie drogi, które działają dziś:

  1. Bez kodu (zalecane): webhook + Make albo Zapier. W karcie „Livespace i inne (webhook)” wklej adres webhooka ze scenariusza. OutreachPilot wyśle zdarzenie crm.lead_push (przykład poniżej), a w Make użyjesz modułów Livespace CRM: Create a Company, Create a Person, Create a Deal (z identyfikatorem procesu sprzedaży) i Create a Note for a Deal. W Zapierze analogicznie: akcje Create Person i Create Deal aplikacji Livespace.
  2. Własny kod: odbierz webhook i wywołaj API Livespace. Uwierzytelnienie: klucz i sekret z Konto → API; po token zgłaszasz się metodą _Api/auth_call/_api_method/getToken, a podpis żądania to sha1(klucz + token + sekret). Pola dealów i notatek sprawdź w dokumentacji API Livespace.
Webhook crm.lead_push (POST na Twój adres)
{
  "id": "crm_6f1d2c3e-4b5a-4c6d-8e7f-0a1b2c3d4e5f",
  "type": "crm.lead_push",
  "created_at": "2026-10-01T12:05:03.000Z",
  "data": {
    "trigger": "reply_positive",
    "lead": {
      "id": "0b6f2c1e-8a41-4f7e-9d3a-5c1f2a7b9e10",
      "email": "biuro@kowalski-instalacje.pl",
      "first_name": "Jan",
      "last_name": "Kowalski",
      "job_title": null,
      "phone": "+48 600 100 200",
      "company_name": "Kowalski Instalacje",
      "company_nip": "5252000001",
      "company_website": "https://kowalski-instalacje.pl",
      "company_city": "Kraków",
      "company_address": null,
      "company_voivodeship": "małopolskie",
      "company_industry": "Hydraulik",
      "status": "zainteresowany",
      "ai_score": 8
    },
    "lead_url": "https://outreachpilot.pl/leady?search=Kowalski%20Instalacje",
    "campaign": {
      "id": "5d0e7a43-2c19-4b8a-a6f1-3e9b8c4d2a77",
      "name": "Hydraulicy Kraków",
      "url": "https://outreachpilot.pl/kampanie/5d0e7a43-2c19-4b8a-a6f1-3e9b8c4d2a77"
    },
    "reply": {
      "sentiment": "interested"
    }
  }
}

Zdarzenie ma te same nagłówki i podpis HMAC co zwykłe webhooki (sekret pokazujemy raz przy połączeniu). Pole reply.text pojawia się tylko przy włączonej opcji „Dołącz treść odpowiedzi”.

Bezpieczeństwo

  • Klucz API to hasło do Twojego konta w ramach nadanych zakresów. Nie wklejaj go do kodu przeglądarki ani repozytorium. API celowo nie ma nagłówków CORS.
  • Przechowujemy wyłącznie skrót SHA-256 klucza i jego prefiks. Utraconego klucza nie odzyskamy: odwołaj go i utwórz nowy.
  • Nadawaj najmniejsze możliwe zakresy i osobny klucz każdemu narzędziu. Ustaw datę wygaśnięcia tam, gdzie to możliwe.
  • Tworzenie i odwołanie klucza trafia do dziennika zdarzeń widocznego w Ustawieniach. Przy każdym żądaniu zapisujemy czas ostatniego użycia klucza.
  • Tokeny CRM szyfrujemy (AES-256-GCM) i nigdy nie pokazujemy ich po zapisaniu. Rozłączenie CRM kasuje token i stan synchronizacji.
  • Znalazłeś lukę? Napisz na kontakt@outreachpilot.pl.