Data API (wcześniej opisywane tutaj jako 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 | Data 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 Data 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"Data 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 Data 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, "proxyDetectedSeen": true, "lastIsp": "Deutsche Telekom", "lastRealIp": "203.0.113.7" }, "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", "gpu": "Apple M2", "geo": { "country": "DE", "city": "Berlin", "timezone": "Europe/Berlin", "isp": "Deutsche Telekom" }, "network": { "vpn": false, "proxy": false, "tor": false, "datacenter": false, "proxyDetected": true, "realIp": { "address": "203.0.113.7", "country": "NL", "isp": "KPN" }, "asn": 3320 }, "screen": { "width": 2560, "height": 1600, "colorDepth": 30, "pixelRatio": 2 }, "locale": { "languages": ["en-US", "de"], "timezone": "Europe/Berlin" }, "clientHints": { "architecture": "arm", "bitness": "64", "platformVersion": "14.5.0" }, "extensions": [ { "slug": "ublock-origin", "name": "uBlock Origin", "category": "adblock", "risky": false, "storeUrl": "https://chromewebstore.google.com/detail/cjpalhdlnbpafiamejdnhcphjbkeiagm" } ], "bot": { "result": "human", "score": 4.5, "antidetectScore": 2.1 }, "identification": { "confidence": 0.97, "incognito": false, "matchType": "exact", "matchConfidence": 0.99 }, "deviceInfo": { "deviceId": "d_4f9c2e", "crossBrowser": true, "confidence": 0.88, "linkedBrowsers": 2 }, "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.proxyDetectedSeen | Czy co najmniej jedna wizyta w oknie wyszła przez proxy lub VPN stojące przed przeglądarką — zob. network.proxyDetected w sekcji „Fakty o urządzeniu” |
network.lastIsp | Najnowszy ISP — Business i wyżej |
network.lastRealIp | Najnowszy adres zaobserwowany za proxy lub VPN — Business i wyżej; brak, gdy takiego adresu nie zaobserwowano |
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 |
bot.type | Obecne, gdy bot.result ma wartość bot: albo konkretne narzędzie (playwright, puppeteer, selenium, jsdom, claude_computer_use…), albo rodzina, gdy narzędzie nie zostało nazwane — automation, headless, antidetect, extension, privacy_browser, other |
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(`Data 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.
Obok przeglądarki i systemu operacyjnego wziętych z User-Agent sesja niesie to, co przeglądarka odwiedzającego zgłasza o maszynie, oczyszczone po naszej stronie. Każde pole jest nieobecne, gdy wizyta nie przyniosła takich danych, więc każde traktuj jako opcjonalne.
| Pole | Znaczenie |
|---|---|
gpu | Model karty graficznej zgłaszany przez przeglądarkę (WebGL), znormalizowany do czytelnej nazwy — Intel Iris Xe Graphics, Apple M1 Pro, Qualcomm Adreno 830; Software renderer oznacza brak prawdziwego GPU (maszyna wirtualna lub środowisko headless); Safari zgłasza Apple GPU |
network.proxyDetected | Ruch HTTP wizyty i jej surowe ścieżki sieciowe wychodzą przez różne sieci — przed przeglądarką stoi proxy lub VPN; dwa adresy tego samego dostawcy (NAT operatora, drugie wyjście tego samego VPN) nie liczą się |
network.realIp.address, .country, .isp | Publiczny adres zaobserwowany na surowej ścieżce sieciowej, czyli adres za proxy lub VPN, wraz z jego krajem i ISP — Business i wyżej; brak, gdy takiego adresu nie zaobserwowano (country i isp są nieobecne, gdy nie dało się ich ustalić) |
deviceInfo.deviceId, .crossBrowser, .confidence, .linkedBrowsers | Obecne, gdy udało się ustalić tożsamość urządzenia: stabilny identyfikator fizycznego urządzenia wspólny dla przeglądarek na nim, czy ta wizyta przyszła przez inną przeglądarkę niż poprzednio, pewność tego dopasowania oraz ilu odrębnych odwiedzających (przeglądarek) dzieli urządzenie — więcej niż jeden oznacza jedną maszynę pod kilkoma tożsamościami przeglądarek — Business i wyżej |
osEnvironment | Środowisko pulpitu zmierzone na maszynie z Linuksem (Mint 22+, Ubuntu, GNOME, KDE) — Business i wyżej; brak, gdy nie ustalono |
spoofing | Co wizyta twierdziła wobec tego, co zmierzyły niezależne kontrole (claimed, real, spoofedAxes spośród os, gpu, screen, network, browser; anonymousBrowser z nazwami produktów) — Business i wyżej; obecne tylko wtedy, gdy podmianę wykryto |
screen.width, .height, .colorDepth, .pixelRatio | Rozdzielczość ekranu, głębia koloru i device pixel ratio zgłaszane przez przeglądarkę — Business i wyżej |
locale.languages, locale.timezone | Własne preferowane języki przeglądarki i jej strefa czasowa — w odróżnieniu od geo.timezone, wyprowadzanej z adresu IP; rozbieżność między nimi to częsta oznaka podrobionej lokalizacji — Business i wyżej |
clientHints.architecture, .bitness, .model, .deviceName, .platformVersion | User-Agent Client Hints: architektura i bitowość procesora, kod modelu urządzenia (Android, np. SM-A556B) wraz z jego nazwą handlową z listy urządzeń Google Play (deviceName, np. Samsung Galaxy A55 5G) i dokładna wersja platformy; tylko przeglądarki oparte na Chromium — Business i wyżej |
environment.virtualMachine, environment.hypervisor | Obecne tylko wtedy, gdy karta graficzna sama przedstawiła się jako wirtualna; hypervisor to słownik zamknięty (vmware, virtualbox, parallels, qemu, hyperv, bochs, intel-gvt, vgpu). Brak bloku oznacza, że nie ma takich przesłanek — Business i wyżej |
extensions wymienia rozszerzenia przeglądarki wykryte podczas wizyty — Business i wyżej. Każdy wpis jest obiektem:
| Pole | Znaczenie |
|---|---|
slug | Stabilny, maszynowy identyfikator rozszerzenia — ta sama wartość, którą dostarcza webhook |
name | Nazwa czytelna dla człowieka |
category | Zgrubna klasa — adblock, privacy, automation, wallet, vpn, devtools, other i tak dalej |
risky | true dla rozszerzeń powiązanych z automatyzacją, podmianą lub kradzieżą danych uwierzytelniających |
storeUrl | Link do wpisu rozszerzenia w sklepie, gdy jest znany |
Znalezisko jest raportowane dopiero po przejściu naszych kontroli wiarygodności — środowisko, które na każdą sondę odpowiada „zainstalowane”, albo partia dłuższa niż dwanaście nazw, jest odrzucane jako niewiarygodne. Pusta lub brakująca lista oznacza więc „nic, co udało nam się potwierdzić”, a nie „nie zainstalowano żadnych rozszerzeń”. Czytaj ją jako dowód, a nie jako inwentarz.
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, bot.type, bot.antidetectScore, gpu oraz network.proxyDetected. Business i Enterprise dokładają extensions, geo.isp, network.asn, network.realIp, decision.suspectScore, identification.driftScore, reasons, behavior, guidance, deviceInfo, spoofing, osEnvironment, screen, locale, clientHints oraz environment. Pola na poziomie osoby (personId, reputation, linkedAccountsCount, linkedVisitorsCount) są zarezerwowane dla Business i Enterprise i pojawią się, gdy warstwa osób zostanie włączona — dziś działa ona w trybie obserwacji i pola te nie są dostarczane. 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 |