Mit der Server API 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 | Server 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 Server 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 Server 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 Server 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, "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" }}| 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.lastIsp | Der jüngste ISP — ab Business |
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 |
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(`Server 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.
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 und bot.antidetectScore.
Business und Enterprise ergänzen geo.isp, network.asn, decision.suspectScore,
identification.driftScore, reasons, behavior, guidance, deviceInfo sowie die
Felder auf Personenebene (personId, reputation, linkedAccountsCount,
linkedVisitorsCount). 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 |