Server API pozwala Twojemu backendowi czytać dane identyfikacyjne, które TRACIO już zebrało dla Twojego workspace: historię odwiedzającego, pojedyncze sesje oraz liczniki aktywności z krótkiego okna.
Uzupełnia Webhooki, a nie zastępuje ich:
| Webhooki | Server API | |
|---|---|---|
| Kierunek | TRACIO wysyła na Twój endpoint | Twój backend pobiera na żądanie |
| Moment | W chwili każdej identyfikacji | W dowolnym momencie, w granicach Twojego okna przechowywania |
| Najlepsze do | Reagowania na zdarzenie | Sprawdzenia danych podczas decyzji, uzupełnień, dochodzeń |
Obie powierzchnie są dostępne od planu Pro wzwyż.
https://api.tracio.ai/v1To inny host niż endpoint przeglądarkowy (edge.tracio.ai) i niż panel
(app.tracio.ai). Wszystkie trzy są osobne: przeglądarka rozmawia z edge Twoim
kluczem publicznym, a Twój backend rozmawia z Server API Twoim kluczem
sekretnym.
Każde żądanie niesie Twój klucz sekretny jako token bearer:
curl -H "Authorization: Bearer tracio_sk_XXXX...XXXX" \ "https://api.tracio.ai/v1/visitors/X7fh2Hg9LkMn3pQr5tBvQw3xZa9mK2pL4nR8dT6y"Server API działa wyłącznie w trybie server-to-server. Nagłówki CORS celowo nie są zwracane, więc przeglądarka nie może go wywołać — właśnie to trzyma Twój klucz sekretny poza kodem klienckim. Nigdy nie wysyłaj klucza sekretnego do przeglądarki.
Utwórz go w panelu w sekcji API Keys, wybierając typ secret.
tracio_sk_ plus 43 znaki, łącznie 53. Panel wyświetla
go po kilku pierwszych znakach, żebyś mógł odróżniać klucze od siebie.Rotacja wydaje nowy klucz i utrzymuje stary w działaniu przez 7 dni, więc możesz go wdrożyć bez przestoju. Wdróż nowy klucz, potwierdź, że ruch się przeniósł, i pozwól staremu wygasnąć. Kluczy publicznych się nie rotuje — nie są sekretami i z założenia są widoczne w źródle Twojej strony.
Każda trasa to GET. W Server API nie ma operacji zapisu: czyta dane, a Twoja
konfiguracja żyje w panelu.
| Metoda | Ścieżka | Zwraca |
|---|---|---|
GET | /v1/visitors/{visitorId} | Zagregowaną historię jednego odwiedzającego plus jego ostatnią sesję |
GET | /v1/visitors/{visitorId}/sessions | Stronicowaną listę sesji tego odwiedzającego |
GET | /v1/visitors/{visitorId}/sessions/latest | Jedną, najnowszą sesję |
GET | /v1/visitors/{visitorId}/velocity | Liczniki aktywności z krótkiego okna |
GET | /v1/sessions/{requestId} | Jedną sesję po identyfikatorze jej żądania |
GET | /.well-known/webhook-keys | Klucze publiczne podpisu platformy dla webhooków (bez uwierzytelniania) |
Końcowy ukośnik jest akceptowany i ignorowany. Nieznana ścieżka lub zła metoda zwraca tę samą kopertę błędu w JSON co wszystko inne, nigdy stronę w HTML ani czystym tekście.
Każdy odczyt jest ograniczony oknem czasowym, którym sterują dwa opcjonalne parametry zapytania:
| Parametr | Przyjmuje |
|---|---|
from | YYYY-MM-DD albo pełny znacznik czasu RFC 3339 |
to | YYYY-MM-DD albo pełny znacznik czasu RFC 3339 |
to obejmuje cały ten dzień.400 invalid_request i komunikatem
time must be YYYY-MM-DD or RFC3339.meta, więc sprawdzaj
meta.from i meta.to, zamiast zakładać, że Twoje żądanie zostało spełnione
dosłownie.curl -H "Authorization: Bearer tracio_sk_XXXX...XXXX" \ "https://api.tracio.ai/v1/visitors/X7fh2Hg9LkMn3pQr5tBvQw3xZa9mK2pL4nR8dT6y"Odpowiedź niesie zagregowaną historię i osadza w sobie ostatnią sesję, więc typowy przypadek wymaga jednego żądania, a nie dwóch:
{ "visitorId": "X7fh2Hg9LkMn3pQr5tBvQw3xZa9mK2pL4nR8dT6y", "firstSeenAt": "2026-05-02T10:11:12Z", "lastSeenAt": "2026-07-25T08:00:00Z", "visits": 42, "incognitoVisits": 3, "uniqueIps": 5, "uniqueCountries": 2, "browsers": ["Chrome"], "os": ["macOS"], "devices": ["desktop"], "risk": { "maxRiskScore": 63, "avgBotScore": 12.5, "botSessions": 7, "lastDecision": "real" }, "network": { "vpnSeen": false, "proxySeen": false, "torSeen": false, "datacenterSeen": true, "lastIsp": "Deutsche Telekom" }, "lastSession": { "requestId": "9c1f6a2e-3b7d-4c58-a1e2-6f0d8b4a7c31", "visitorId": "X7fh2Hg9LkMn3pQr5tBvQw3xZa9mK2pL4nR8dT6y", "accountId": "user_8842", "timestamp": "2026-07-25T08:00:00Z", "tag": "checkout", "url": "https://shop.example.com/checkout", "ip": "203.0.113.42", "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36", "browser": { "name": "Chrome", "version": "126.0" }, "os": { "name": "macOS", "version": "14.5" }, "device": "desktop", "geo": { "country": "DE", "city": "Berlin", "timezone": "Europe/Berlin", "isp": "Deutsche Telekom" }, "network": { "vpn": false, "proxy": false, "tor": false, "datacenter": false, "asn": 3320 }, "bot": { "result": "human", "score": 4.5, "antidetectScore": 2.1 }, "identification": { "confidence": 0.97, "incognito": false, "matchType": "exact", "matchConfidence": 0.99 }, "decision": { "action": "real", "riskScore": 12 } }, "meta": { "plan": "business", "retentionDays": 90, "from": "2026-04-26T00:00:00Z", "to": "2026-07-25T12:00:00Z" }}| Pole | Znaczenie |
|---|---|
visits, incognitoVisits | Łączna liczba wizyt w oknie i ile z nich odbyło się w oknie prywatnym |
uniqueIps, uniqueCountries | Odrębne adresy i kraje zaobserwowane w oknie |
browsers, os, devices | Odrębne środowiska, w których pojawiał się ten odwiedzający |
risk.maxRiskScore | Najwyższy wskaźnik ryzyka zanotowany w oknie, 0..100 |
risk.lastDecision | Decyzja zanotowana dla najnowszej wizyty |
risk.avgBotScore, risk.botSessions | Średni wskaźnik bota i liczba sesji botów — Business i wyżej |
network.*Seen | Czy u tego odwiedzającego kiedykolwiek widziano VPN, proxy, węzeł wyjściowy Tor lub adres centrum danych |
network.lastIsp | Najnowszy ISP — Business i wyżej |
lastSession | Pełny obiekt sesji dla najnowszej wizyty |
meta | Plan, jego okres przechowywania w dniach oraz faktycznie zastosowane okno |
Odwiedzający, dla którego nie ma danych wewnątrz okna przechowywania, zwraca
404 not_found z komunikatem visitor not found in the retention window — to nie
błąd w Twojej integracji, lecz znak, że odwiedzający jest nowy albo wypadł poza okres
przechowywania.
Sesja niesie dwa werdykty i odpowiadają one na różne pytania — czy klient był zautomatyzowany oraz do jakiego ogólnego wniosku doszedł silnik ryzyka:
| Pole | Wartości |
|---|---|
bot.result | human, bot, uncertain |
decision.action | real, fake, suspicious |
bot.score i decision.riskScore mieszczą się oba w skali 0..100. W planach
Business i wyżej guidance zamienia je w rekomendacje dla poszczególnych scenariuszy
na drabinie allow → challenge → review → deny — co oznacza każdy szczebel, opisuje
Guidance.
curl -H "Authorization: Bearer tracio_sk_XXXX...XXXX" \ "https://api.tracio.ai/v1/visitors/X7fh2Hg9LkMn3pQr5tBvQw3xZa9mK2pL4nR8dT6y/sessions?limit=100&minRiskScore=50"| Parametr | Domyślnie | Uwagi |
|---|---|---|
limit | 50 | Ograniczony do 500; większa wartość jest przycinana, nie odrzucana |
from, to | Okres przechowywania planu | Wspólne okno czasowe opisane wyżej |
cursor | — | Nieprzezroczysty kursor paginacji z poprzedniej strony |
botResult | — | Zostaw tylko sesje z tym werdyktem bota |
minRiskScore | — | Zostaw tylko sesje o wskaźniku ryzyka nie niższym niż ten, 0..100 |
Sesje wracają od najnowszych:
{ "items": [ { "requestId": "9c1f6a2e-3b7d-4c58-a1e2-6f0d8b4a7c31", "visitorId": "X7fh2Hg9LkMn3pQr5tBvQw3xZa9mK2pL4nR8dT6y", "timestamp": "2026-07-25T08:00:00Z" } ], "nextCursor": "MTcyMTg5NDQwMDAwMDphYmMxMjM", "hasMore": true, "meta": { "plan": "business", "retentionDays": 90, "from": "2026-04-26T00:00:00Z", "to": "2026-07-25T12:00:00Z" }}Stronicowanie opiera się na kursorach. Nie ma parametru page ani offset: przekaż
otrzymany nextCursor z powrotem jako cursor i kontynuuj, dopóki hasMore jest
prawdziwe.
async function allSessions(visitorId: string, secretKey: string) { const sessions = [] let cursor: string | undefined
do { const url = new URL(`https://api.tracio.ai/v1/visitors/${visitorId}/sessions`) url.searchParams.set("limit", "500") if (cursor) url.searchParams.set("cursor", cursor)
const res = await fetch(url, { headers: { Authorization: `Bearer ${secretKey}` } }) if (!res.ok) throw new Error(`Server API: ${res.status}`)
const page = await res.json() sessions.push(...page.items) cursor = page.nextCursor } while (cursor)
return sessions}Traktuj kursor jako nieprzezroczysty — jego zawartość jest szczegółem implementacyjnym
i może się zmienić. Kursor, który został zmodyfikowany, jest odrzucany kodem
400 invalid_request i komunikatem malformed cursor.
Ta najnowsza:
curl -H "Authorization: Bearer tracio_sk_XXXX...XXXX" \ "https://api.tracio.ai/v1/visitors/X7fh2Hg9LkMn3pQr5tBvQw3xZa9mK2pL4nR8dT6y/sessions/latest"Zwracany jest goły obiekt sesji — nie tablica i nie opakowanie w kopertę.
Odwiedzający bez sesji w oknie zwraca 404 not_found z komunikatem
no sessions for this visitor in the retention window.
Albo po requestId, identyfikatorze, który pojawia się także w payloadzie webhooka:
curl -H "Authorization: Bearer tracio_sk_XXXX...XXXX" \ "https://api.tracio.ai/v1/sessions/9c1f6a2e-3b7d-4c58-a1e2-6f0d8b4a7c31?visitorId=X7fh2Hg9LkMn3pQr5tBvQw3xZa9mK2pL4nR8dT6y"visitorId jest tutaj opcjonalny, ale przekazanie go, gdy go znasz, wyraźnie
przyspiesza wyszukiwanie.
Velocity odpowiada na pytanie „ile ten odwiedzający ostatnio narobił” — tak właśnie wygląda upychanie danych logowania, testowanie kart i masowe rejestracje.
curl -H "Authorization: Bearer tracio_sk_XXXX...XXXX" \ "https://api.tracio.ai/v1/visitors/X7fh2Hg9LkMn3pQr5tBvQw3xZa9mK2pL4nR8dT6y/velocity?window=1h"window przyjmuje 1h, 24h lub 7d, a domyślnie ma 24h. Każda inna wartość jest
odrzucana kodem 400 invalid_request i komunikatem
window must be one of: 1h, 24h, 7d.
{ "window": "1h", "events": 37, "uniqueIps": 9, "uniqueCountries": 3, "uniqueAccounts": 12, "botEvents": 4, "meta": { "plan": "business", "retentionDays": 90, "from": "2026-07-25T11:00:00Z", "to": "2026-07-25T12:00:00Z" }}uniqueAccounts liczy odrębne wartości linkedId, które wysłałeś dla tego urządzenia
— zobacz Łączenie kont. botEvents jest dostępne od planu
Business wzwyż.
Brakujące pole oznacza „brak danych”, nigdy zero. Pola bez wartości są pomijane w
całości, zamiast być wysyłane jako 0, "" czy null: zupełnie nowy odwiedzający
nie ma matchConfidence, czysta wizyta nie ma antidetectScore ani suspectScore.
Jedynym celowym wyjątkiem jest bot.score, które występuje zawsze, nawet gdy wynosi
zero. Czytaj pola ostrożnie.
Payload zależy od Twojego planu. Każdy plan z dostępem do API dostaje bazową sesję
— identyfikatory, znacznik czasu, URL, IP, user agent, przeglądarkę, system
operacyjny, urządzenie, geo, sieć, bota, identyfikację i decyzję. Pro dokłada
identification.matchType, identification.matchConfidence oraz
bot.antidetectScore. Business i Enterprise dokładają geo.isp, network.asn,
decision.suspectScore, identification.driftScore, reasons, behavior,
guidance, deviceInfo oraz pola na poziomie osoby (personId, reputation,
linkedAccountsCount, linkedVisitorsCount). Brak pola z poziomu Business w planie
Pro nie jest błędem.
Wewnętrzne szczegóły na poziomie sygnałów nie są zwracane nigdy, w żadnym planie: nazwy poszczególnych sygnałów, ich wagi, progi stojące za werdyktem, surowe wartości sygnałów i rozbicia wskaźników zostają po naszej stronie. Wskaźnik, który da się odtworzyć wstecz do swoich danych wejściowych, przestaje być użyteczny jako zabezpieczenie.
Każde niepowodzenie używa jednej koperty:
{ "error": { "code": "unauthorized", "message": "missing Authorization: Bearer <secret key>", "requestId": "8f14e45fceea167a5a36dedd" }}Ten requestId nie jest identyfikatorem wizyty. Nazwę dzielą dwie różne wartości:
wewnątrz payloadu sesji requestId to UUID wizyty, ten sam, który dostarcza webhook;
wewnątrz koperty błędu jest to 24-znakowy identyfikator śledzenia, wybijany dla
każdego wywołania HTTP. Identyfikator śledzenia wraca również w nagłówku
X-Request-Id przy każdej odpowiedzi, udanej czy nie. Dołącz go, kontaktując się ze
wsparciem — właśnie po nim odnajdujemy Twoje konkretne wywołanie.
| HTTP | code | Znaczenie |
|---|---|---|
| 400 | invalid_request | Brakuje parametru albo jest źle sformułowany |
| 401 | unauthorized | Klucza brak, jest nieprawidłowy, odwołany lub wygasły |
| 402 | upgrade_required | Twój plan nie obejmuje dostępu do API |
| 404 | not_found | Wewnątrz okna przechowywania nic nie pasowało |
| 405 | method_not_allowed | Trasa istnieje, ale nie dla tej metody |
| 429 | rate_limited | Przekroczono liczbę zapytań na sekundę albo kwotę dzienną |
| 500 | internal | Coś zawiodło po naszej stronie |
| 503 | unavailable | Magazyn danych jest chwilowo nieosiągalny |
Kontrole biegną w ustalonej kolejności — klucz, potem plan, potem limity — więc żądanie ze złym kluczem zawsze zgłasza najpierw klucz, nigdy problem z kwotą.
Dwa przypadki 401 czyta się celowo inaczej: missing Authorization: Bearer <secret key>
oznacza, że nagłówek w ogóle nie dotarł, a invalid or revoked API key — że dotarł i
nie pasował. 402 niesie Data API requires the Pro plan or higher.
Każda uwierzytelniona odpowiedź niesie Twój bieżący stan:
| Nagłówek | Znaczenie |
|---|---|
X-RateLimit-Limit | Twoja kwota dzienna |
X-RateLimit-Remaining | Wywołania pozostałe na dziś |
X-RateLimit-Reset | Czas uniksowy resetu — północ UTC |
Retry-After | Sekundy oczekiwania, wysyłane tylko przy 429 |
| Plan | Zapytań na sekundę | Zapytań dziennie | Głębokość historii |
|---|---|---|---|
| Free | Brak dostępu do API | — | 7 dni |
| Pro | 10 | 10 000 | 30 dni |
| Business | 50 | 100 000 | 90 dni |
| Enterprise | 200 | Bez limitu | 365 dni |