Mit der Data API (hier früher als Server API dokumentiert) kann Ihr Backend die Identifikationsdaten lesen, die TRACIO für Ihren Workspace bereits erfasst hat: die Historie eines Besuchers, einzelne Sessions und Velocity-Zähler über kurze Fenster.
Sie ergänzt Webhooks, statt sie zu ersetzen:
| Webhooks | Data API | |
|---|---|---|
| Richtung | TRACIO sendet an Ihren Endpunkt | Ihr Backend ruft bei Bedarf ab |
| Zeitpunkt | Sobald eine Identifikation stattfindet | Jederzeit, innerhalb Ihres Aufbewahrungsfensters |
| Am besten für | Auf ein Ereignis reagieren | Daten während einer Entscheidung nachschlagen, Nacherfassungen, Untersuchungen |
Beide Schnittstellen sind ab dem Tarif Pro verfügbar.
https://api.tracio.ai/v1Das ist ein anderer Host als der Browser-Endpunkt (edge.tracio.ai) und als das
Dashboard (app.tracio.ai). Alle drei sind getrennt: Der Browser spricht mit dem Edge
über Ihren öffentlichen Schlüssel, Ihr Backend spricht mit der Data API über
Ihren geheimen Schlüssel.
Jede Anfrage trägt Ihren geheimen Schlüssel als Bearer-Token:
curl -H "Authorization: Bearer tracio_sk_XXXX...XXXX" \ "https://api.tracio.ai/v1/visitors/X7fh2Hg9LkMn3pQr5tBvQw3xZa9mK2pL4nR8dT6y"Die Data API ist ausschließlich Server-zu-Server. CORS-Header werden bewusst nicht zurückgegeben, sodass ein Browser sie nicht aufrufen kann — genau das hält Ihren geheimen Schlüssel aus clientseitigem Code heraus. Liefern Sie den geheimen Schlüssel niemals an den Browser aus.
Legen Sie ihn im Dashboard unter API Keys an und wählen Sie dabei den Typ secret.
tracio_sk_ gefolgt von 43 Zeichen, insgesamt 53. Das Dashboard listet ihn anhand seiner ersten Zeichen auf, damit Sie Schlüssel
auseinanderhalten können.Beim Rotieren wird ein neuer Schlüssel ausgegeben, während der alte noch 7 Tage funktioniert, sodass Sie ihn ohne Ausfallzeit ausrollen können. Rollen Sie den neuen Schlüssel aus, prüfen Sie, dass der Traffic umgezogen ist, und lassen Sie den alten ablaufen. Öffentliche Schlüssel sind nicht rotierbar — sie sind keine Geheimnisse und stehen bewusst sichtbar im Quelltext Ihrer Seite.
Jede Route ist ein GET. Es gibt keine Schreiboperationen in der Data API: Sie liest
Daten, und Ihre Konfiguration lebt im Dashboard.
| Methode | Pfad | Liefert |
|---|---|---|
GET | /v1/visitors/{visitorId} | Aggregierte Historie eines Besuchers plus dessen jüngste Session |
GET | /v1/visitors/{visitorId}/sessions | Paginierte Liste der Sessions dieses Besuchers |
GET | /v1/visitors/{visitorId}/sessions/latest | Die einzelne jüngste Session |
GET | /v1/visitors/{visitorId}/velocity | Aktivitätszähler über ein kurzes Fenster |
GET | /v1/sessions/{requestId} | Eine Session anhand ihrer Request-Kennung |
GET | /.well-known/webhook-keys | Öffentliche Schlüssel für die Plattform-Signatur der Webhooks (ohne Auth) |
Ein abschließender Schrägstrich wird akzeptiert und ignoriert. Ein unbekannter Pfad oder eine falsche Methode liefert denselben JSON-Fehler-Envelope wie alles andere, niemals eine HTML- oder Klartext-Seite.
Jeder Lesezugriff ist durch ein Zeitfenster begrenzt, gesteuert über zwei optionale Query-Parameter:
| Parameter | Akzeptiert |
|---|---|
from | YYYY-MM-DD oder einen vollständigen RFC-3339-Zeitstempel |
to | YYYY-MM-DD oder einen vollständigen RFC-3339-Zeitstempel |
to schließt den gesamten Tag ein.400 invalid_request und der Meldung
time must be YYYY-MM-DD or RFC3339 abgelehnt.meta
zurückgemeldet — prüfen Sie also meta.from und meta.to, statt anzunehmen, dass
Ihre Anfrage wortwörtlich erfüllt wurde.curl -H "Authorization: Bearer tracio_sk_XXXX...XXXX" \ "https://api.tracio.ai/v1/visitors/X7fh2Hg9LkMn3pQr5tBvQw3xZa9mK2pL4nR8dT6y"Die Antwort trägt die aggregierte Historie und bettet die jüngste Session ein, sodass der häufige Fall eine statt zwei Anfragen braucht:
{ "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" }}| Feld | Bedeutung |
|---|---|
visits, incognitoVisits | Besuche insgesamt im Fenster und wie viele davon in einem privaten Fenster stattfanden |
uniqueIps, uniqueCountries | Unterschiedliche Adressen und Länder, die im Fenster gesehen wurden |
browsers, os, devices | Die unterschiedlichen Umgebungen, in denen dieser Besucher aufgetreten ist |
risk.maxRiskScore | Der höchste im Fenster erfasste Risiko-Score, 0..100 |
risk.lastDecision | Die für den jüngsten Besuch erfasste Entscheidung |
risk.avgBotScore, risk.botSessions | Durchschnittlicher Bot-Score und die Anzahl der Bot-Sessions — ab Business |
network.*Seen | Ob für diesen Besucher jemals ein VPN, ein Proxy, ein Tor-Exit-Node oder eine Rechenzentrums-Adresse gesehen wurde |
network.proxyDetectedSeen | Ob mindestens ein Besuch im Fenster über einen Proxy oder ein VPN vor dem Browser ausgetreten ist — siehe network.proxyDetected unter Gerätefakten |
network.lastIsp | Der jüngste ISP — ab Business |
network.lastRealIp | Die jüngste Adresse, die hinter einem Proxy oder VPN beobachtet wurde — ab Business; fehlt, wenn keine beobachtet wurde |
lastSession | Das vollständige Session-Objekt des jüngsten Besuchs |
meta | Der Tarif, seine Aufbewahrung in Tagen und das tatsächlich angewandte Fenster |
Ein Besucher ohne Daten innerhalb des Aufbewahrungsfensters liefert 404 not_found mit
der Meldung visitor not found in the retention window — das ist kein Fehler in Ihrer
Integration, es bedeutet, dass der Besucher neu ist oder herausgefallen ist.
Die Session trägt zwei Verdicts, und sie beantworten unterschiedliche Fragen — ob der Client automatisiert war und was die Risiko-Engine insgesamt geschlossen hat:
| Feld | Werte |
|---|---|
bot.result | human, bot, uncertain |
bot.type | Vorhanden, wenn bot.result gleich bot ist: entweder ein konkretes Tool (playwright, puppeteer, selenium, jsdom, claude_computer_use…) oder eine Familie, wenn das Tool nicht benannt ist — automation, headless, antidetect, extension, privacy_browser, other |
decision.action | real, fake, suspicious |
bot.score und decision.riskScore laufen beide von 0..100. Ab Business macht
guidance daraus Empfehlungen je Szenario auf der Leiter
allow → challenge → review → deny — siehe
Guidance für die Bedeutung jeder Stufe.
curl -H "Authorization: Bearer tracio_sk_XXXX...XXXX" \ "https://api.tracio.ai/v1/visitors/X7fh2Hg9LkMn3pQr5tBvQw3xZa9mK2pL4nR8dT6y/sessions?limit=100&minRiskScore=50"| Parameter | Standard | Hinweise |
|---|---|---|
limit | 50 | Bei 500 gedeckelt; ein größerer Wert wird beschnitten, nicht abgelehnt |
from, to | Tarif-Aufbewahrung | Das oben beschriebene gemeinsame Zeitfenster |
cursor | — | Undurchsichtiger Paginierungs-Cursor von der vorherigen Seite |
botResult | — | Nur Sessions mit diesem Bot-Verdict behalten |
minRiskScore | — | Nur Sessions ab diesem Risiko-Score behalten, 0..100 |
Sessions kommen mit der neuesten zuerst zurück:
{ "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" }}Das Blättern ist cursorbasiert. Es gibt keinen Parameter page oder offset:
Übergeben Sie den erhaltenen nextCursor als cursor und machen Sie weiter, solange
hasMore wahr ist.
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}Behandeln Sie den Cursor als undurchsichtig — sein Inhalt ist ein
Implementierungsdetail und kann sich ändern. Ein bearbeiteter Cursor wird mit
400 invalid_request und der Meldung malformed cursor abgelehnt.
Neben dem Browser und dem Betriebssystem aus dem User-Agent trägt eine Session das, was der Browser des Besuchers über die Maschine meldet, auf unserer Seite bereinigt. Jedes Feld fehlt, wenn der Besuch keine solchen Daten mitbrachte — behandeln Sie daher jedes als optional.
| Feld | Bedeutung |
|---|---|
gpu | Modell des Grafikadapters, wie es der Browser meldet (WebGL), normalisiert auf einen lesbaren Namen — Intel Iris Xe Graphics, Apple M1 Pro, Qualcomm Adreno 830; Software renderer bedeutet keine echte GPU (eine virtuelle Maschine oder eine Headless-Umgebung); Safari meldet Apple GPU |
network.proxyDetected | Der HTTP-Verkehr des Besuchs und seine rohen Netzwerkpfade treten über verschiedene Netze aus — ein Proxy oder VPN vor dem Browser; zwei Adressen desselben Anbieters (Carrier-NAT, ein zweiter Ausgang desselben VPN) zählen nicht |
network.realIp.address, .country, .isp | Die öffentliche Adresse, die auf dem rohen Netzwerkpfad beobachtet wurde, also die Adresse hinter dem Proxy oder VPN, mit ihrem Land und ihrem ISP — ab Business; fehlt, wenn keine solche Adresse beobachtet wurde (country und isp fehlen, wenn sie nicht aufgelöst werden konnten) |
deviceInfo.deviceId, .crossBrowser, .confidence, .linkedBrowsers | Vorhanden, wenn eine Geräteidentität aufgelöst wurde: eine stabile ID des physischen Geräts über die Browser darauf hinweg, ob dieser Besuch über einen anderen Browser als zuvor kam, die Konfidenz dieser Übereinstimmung und wie viele unterschiedliche Besucher (Browser) sich das Gerät teilen — mehr als einer bedeutet eine Maschine unter mehreren Browser-Identitäten — ab Business |
osEnvironment | Die auf einem Linux-Rechner gemessene Desktop-Umgebung (Mint 22+, Ubuntu, GNOME, KDE) — ab Business; fehlt, wenn nicht bestimmt |
spoofing | Was der Besuch behauptet hat, verglichen mit dem, was unabhängige Prüfungen gemessen haben (claimed, real, spoofedAxes aus os, gpu, screen, network, browser; anonymousBrowser mit Produktnamen) — ab Business; nur vorhanden, wenn ein Spoof erkannt wurde |
screen.width, .height, .colorDepth, .pixelRatio | Bildschirmauflösung, Farbtiefe und Device Pixel Ratio, wie sie der Browser meldet — ab Business |
locale.languages, locale.timezone | Die vom Browser selbst bevorzugten Sprachen und die Zeitzone — im Unterschied zu geo.timezone, das aus der IP-Adresse abgeleitet wird; eine Abweichung zwischen beiden ist ein häufiges Anzeichen für einen vorgetäuschten Standort — ab Business |
clientHints.architecture, .bitness, .model, .deviceName, .platformVersion | User-Agent Client Hints: CPU-Architektur und Bitness, Gerätemodellcode (Android, z. B. SM-A556B) mit seinem Marketingnamen aus der Google-Play-Geräteliste (deviceName, z. B. Samsung Galaxy A55 5G) und die genaue Plattformversion; nur Chromium-basierte Browser — ab Business |
environment.virtualMachine, environment.hypervisor | Nur vorhanden, wenn sich der Grafikadapter selbst als virtuell zu erkennen gegeben hat; hypervisor ist ein geschlossenes Wörterbuch (vmware, virtualbox, parallels, qemu, hyperv, bochs, intel-gvt, vgpu). Ein fehlender Block bedeutet, dass es keinen solchen Hinweis gibt — ab Business |
extensions listet die während des Besuchs erkannten Browser-Erweiterungen auf — ab Business. Jeder Eintrag ist ein Objekt:
| Feld | Bedeutung |
|---|---|
slug | Stabile maschinenlesbare Kennung der Erweiterung, derselbe Wert, den der Webhook liefert |
name | Menschenlesbarer Name |
category | Grobe Klasse — adblock, privacy, automation, wallet, vpn, devtools, other und so weiter |
risky | true für Erweiterungen, die mit Automatisierung, Spoofing oder dem Diebstahl von Zugangsdaten in Verbindung stehen |
storeUrl | Link zum Store-Eintrag der Erweiterung, sofern bekannt |
Ein Fund wird erst gemeldet, nachdem er unsere Vertrauensprüfungen bestanden hat — eine Umgebung, die auf jede Sonde mit „installiert“ antwortet, oder ein Stapel von mehr als zwölf Namen wird als unzuverlässig verworfen. Eine leere oder fehlende Liste bedeutet daher „nichts, was wir bestätigen konnten“, nicht „keine Erweiterungen installiert“. Lesen Sie sie als Indiz, nicht als Inventar.
Die jüngste:
curl -H "Authorization: Bearer tracio_sk_XXXX...XXXX" \ "https://api.tracio.ai/v1/visitors/X7fh2Hg9LkMn3pQr5tBvQw3xZa9mK2pL4nR8dT6y/sessions/latest"Das liefert ein nacktes Session-Objekt — kein Array und nicht in einen Envelope
verpackt. Ein Besucher ohne Sessions im Fenster liefert 404 not_found mit
no sessions for this visitor in the retention window.
Oder anhand der requestId, der Kennung, die auch im Webhook-Payload auftaucht:
curl -H "Authorization: Bearer tracio_sk_XXXX...XXXX" \ "https://api.tracio.ai/v1/sessions/9c1f6a2e-3b7d-4c58-a1e2-6f0d8b4a7c31?visitorId=X7fh2Hg9LkMn3pQr5tBvQw3xZa9mK2pL4nR8dT6y"visitorId ist hier optional, aber sie mitzugeben, wenn Sie sie kennen, macht die
Suche deutlich schneller.
Velocity beantwortet die Frage „wie viel hat dieser Besucher in letzter Zeit getan“ — die Form von Credential Stuffing, Card Testing und Massenregistrierungen.
curl -H "Authorization: Bearer tracio_sk_XXXX...XXXX" \ "https://api.tracio.ai/v1/visitors/X7fh2Hg9LkMn3pQr5tBvQw3xZa9mK2pL4nR8dT6y/velocity?window=1h"window akzeptiert 1h, 24h oder 7d und ist standardmäßig 24h. Jeder andere
Wert wird mit 400 invalid_request und window must be one of: 1h, 24h, 7d abgelehnt.
{ "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 zählt die unterschiedlichen linkedId-Werte, die Sie für dieses Gerät
gesendet haben — siehe Account-Verknüpfung. botEvents gibt es
ab Business.
Ein fehlendes Feld bedeutet „keine Daten“, niemals null. Felder ohne Wert werden
vollständig weggelassen statt als 0, "" oder null gesendet: Ein brandneuer
Besucher hat keine matchConfidence, ein sauberer Besuch hat keinen antidetectScore
und keinen suspectScore. Die eine bewusste Ausnahme ist bot.score, das immer
vorhanden ist, selbst wenn es null ist. Lesen Sie Felder defensiv.
Der Payload hängt von Ihrem Tarif ab. Jeder Tarif mit API-Zugang erhält die Basis-Session — Kennungen, Zeitstempel, URL, IP, User Agent, Browser, Betriebssystem, Gerät, Geo, Netzwerk, Bot, Identifikation und Entscheidung. Pro ergänzt identification.matchType, identification.matchConfidence, bot.type, bot.antidetectScore, gpu und network.proxyDetected. Business und Enterprise ergänzen extensions, geo.isp, network.asn, network.realIp, decision.suspectScore, identification.driftScore, reasons, behavior, guidance, deviceInfo, spoofing, osEnvironment, screen, locale, clientHints und environment. Die Felder auf Personenebene (personId, reputation, linkedAccountsCount, linkedVisitorsCount) sind Business und Enterprise vorbehalten und erscheinen, sobald die Personenebene aktiviert ist — heute läuft sie im Beobachtungsmodus, und diese Felder werden nicht geliefert. Das Fehlen eines Business-Feldes im Pro-Tarif ist kein Fehler.
Interna auf Signalebene werden nie zurückgegeben, in keinem Tarif: einzelne Signalnamen, ihre Gewichte, die Schwellenwerte hinter einem Verdict, rohe Signalwerte und Score-Aufschlüsselungen bleiben auf unserer Seite. Ein Score, der sich auf seine Eingaben zurückrechnen lässt, ist als Absicherung nicht mehr brauchbar.
Jeder Fehlschlag verwendet einen einzigen Envelope:
{ "error": { "code": "unauthorized", "message": "missing Authorization: Bearer <secret key>", "requestId": "8f14e45fceea167a5a36dedd" }}Diese requestId ist nicht die Kennung des Besuchs. Zwei verschiedene Werte teilen
sich den Namen: Innerhalb eines Session-Payloads ist requestId die UUID des Besuchs —
dieselbe, die der Webhook zustellt; innerhalb eines Fehler-Envelopes ist es eine
24-stellige Trace-Kennung, die pro HTTP-Aufruf erzeugt wird. Die Trace-Kennung kommt
außerdem im Header X-Request-Id bei jeder Antwort zurück, erfolgreich oder nicht.
Geben Sie sie an, wenn Sie den Support kontaktieren — so finden wir genau Ihren Aufruf.
| HTTP | code | Bedeutung |
|---|---|---|
| 400 | invalid_request | Ein Parameter fehlt oder ist fehlerhaft |
| 401 | unauthorized | Der Schlüssel fehlt, ist ungültig, widerrufen oder abgelaufen |
| 402 | upgrade_required | Ihr Tarif enthält keinen API-Zugang |
| 404 | not_found | Innerhalb des Aufbewahrungsfensters passte nichts |
| 405 | method_not_allowed | Die Route existiert, aber nicht für diese Methode |
| 429 | rate_limited | Anfragen pro Sekunde oder das Tageskontingent überschritten |
| 500 | internal | Auf unserer Seite ist etwas fehlgeschlagen |
| 503 | unavailable | Ein zugrunde liegender Speicher ist vorübergehend nicht erreichbar |
Die Prüfungen laufen in fester Reihenfolge — Schlüssel, dann Tarif, dann Limits —, sodass eine Anfrage mit einem falschen Schlüssel immer zuerst den Schlüssel meldet, nie ein Kontingentproblem.
Zwei 401-Fälle lesen sich bewusst unterschiedlich: missing Authorization: Bearer <secret key>
bedeutet, dass der Header nie ankam, während invalid or revoked API key bedeutet, dass
er ankam und nicht passte. 402 trägt Data API requires the Pro plan or higher.
Jede authentifizierte Antwort trägt Ihren aktuellen Stand:
| Header | Bedeutung |
|---|---|
X-RateLimit-Limit | Ihr Tageskontingent |
X-RateLimit-Remaining | Heute verbleibende Aufrufe |
X-RateLimit-Reset | Unix-Zeit des Resets — Mitternacht UTC |
Retry-After | Wartezeit in Sekunden, nur mit einem 429 gesendet |
| Tarif | Anfragen pro Sekunde | Anfragen pro Tag | Historientiefe |
|---|---|---|---|
| Free | Kein API-Zugang | — | 7 Tage |
| Pro | 10 | 10.000 | 30 Tage |
| Business | 50 | 100.000 | 90 Tage |
| Enterprise | 200 | Ohne Kontingent | 365 Tage |