La Data API (in precedenza documentata qui come Server API) consente al suo backend di leggere i dati di identificazione che TRACIO ha già raccolto per il suo workspace: la cronologia di un visitatore, le singole sessioni e i contatori di velocity su finestre brevi.
Integra i Webhook invece di sostituirli:
| Webhook | Data API | |
|---|---|---|
| Direzione | TRACIO invia al suo endpoint | Il suo backend interroga su richiesta |
| Tempistica | Nel momento di ogni identificazione | In qualsiasi momento, entro la sua finestra di conservazione |
| Ideale per | Reagire a un evento | Consultare i dati durante una decisione, backfill, indagini |
Entrambe le superfici sono disponibili dal piano Pro in su.
https://api.tracio.ai/v1Si tratta di un host diverso da quello dell'endpoint del browser (edge.tracio.ai) e
dalla dashboard (app.tracio.ai). Tutti e tre sono separati: il browser parla con
l'edge usando la sua chiave pubblica, il suo backend parla con la Data API usando
la sua chiave segreta.
Ogni richiesta porta la sua chiave segreta come bearer token:
curl -H "Authorization: Bearer tracio_sk_XXXX...XXXX" \ "https://api.tracio.ai/v1/visitors/X7fh2Hg9LkMn3pQr5tBvQw3xZa9mK2pL4nR8dT6y"La Data API è solo server-to-server. Gli header CORS non vengono deliberatamente restituiti, quindi un browser non può chiamarla — è questo che tiene la sua chiave segreta fuori dal codice lato client. Non spedisca mai la chiave segreta al browser.
La crei nella dashboard, sotto API Keys, scegliendo il tipo secret.
tracio_sk_ seguito da 43 caratteri, 53 in totale. La
dashboard la elenca con i suoi primi caratteri, così può distinguere le chiavi fra
loro.La rotazione emette una nuova chiave e mantiene funzionante la vecchia per 7 giorni, così può distribuirla senza interruzioni di servizio. Distribuisca la nuova chiave, verifichi che il traffico si sia spostato e lasci scadere la vecchia. Le chiavi pubbliche non sono ruotabili — non sono segreti e sono visibili nel sorgente della sua pagina per scelta progettuale.
Ogni rotta è un GET. Nella Data API non esistono operazioni di scrittura: legge i
dati, mentre la sua configurazione risiede nella dashboard.
| Metodo | Percorso | Restituisce |
|---|---|---|
GET | /v1/visitors/{visitorId} | Cronologia aggregata di un visitatore, più la sua ultima sessione |
GET | /v1/visitors/{visitorId}/sessions | Elenco paginato delle sessioni di quel visitatore |
GET | /v1/visitors/{visitorId}/sessions/latest | La singola sessione più recente |
GET | /v1/visitors/{visitorId}/velocity | Contatori di attività su una finestra breve |
GET | /v1/sessions/{requestId} | Una sessione tramite il suo identificatore di richiesta |
GET | /.well-known/webhook-keys | Chiavi pubbliche per la firma di piattaforma dei webhook (senza autenticazione) |
Una barra finale viene accettata e ignorata. Un percorso sconosciuto o un metodo errato restituisce lo stesso envelope JSON di errore di tutto il resto, mai una pagina HTML o di testo semplice.
Ogni lettura è delimitata da una finestra temporale, controllata da due parametri di query facoltativi:
| Parametro | Accetta |
|---|---|
from | YYYY-MM-DD oppure un timestamp RFC 3339 completo |
to | YYYY-MM-DD oppure un timestamp RFC 3339 completo |
to include l'intera giornata.400 invalid_request e il messaggio
time must be YYYY-MM-DD or RFC3339.meta, quindi controlli meta.from e meta.to invece di dare per
scontato che la sua richiesta sia stata rispettata alla lettera.curl -H "Authorization: Bearer tracio_sk_XXXX...XXXX" \ "https://api.tracio.ai/v1/visitors/X7fh2Hg9LkMn3pQr5tBvQw3xZa9mK2pL4nR8dT6y"La risposta porta la cronologia aggregata e incorpora l'ultima sessione, così il caso comune richiede una richiesta invece di due:
{ "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" }}| Campo | Significato |
|---|---|
visits, incognitoVisits | Visite totali nella finestra e quante di esse in una finestra privata |
uniqueIps, uniqueCountries | Indirizzi e paesi distinti visti nella finestra |
browsers, os, devices | Gli ambienti distinti in cui questo visitatore è comparso |
risk.maxRiskScore | Il punteggio di rischio più alto registrato nella finestra, 0..100 |
risk.lastDecision | La decisione registrata per la visita più recente |
risk.avgBotScore, risk.botSessions | Media del punteggio di bot e numero di sessioni bot — da Business in su |
network.*Seen | Se per questo visitatore siano mai stati visti una VPN, un proxy, un nodo di uscita Tor o un indirizzo di datacenter |
network.proxyDetectedSeen | Se almeno una visita nella finestra è uscita attraverso un proxy o una VPN davanti al browser — veda network.proxyDetected in Dati del dispositivo |
network.lastIsp | L'ISP più recente — da Business in su |
network.lastRealIp | L'indirizzo più recente osservato dietro un proxy o una VPN — da Business in su; assente quando non ne è stato osservato alcuno |
lastSession | L'oggetto sessione completo della visita più recente |
meta | Il piano, la sua conservazione in giorni e la finestra effettivamente applicata |
Un visitatore senza dati all'interno della finestra di conservazione restituisce
404 not_found con il messaggio visitor not found in the retention window — non è un
errore della sua integrazione, significa che il visitatore è nuovo oppure è uscito dalla
finestra per anzianità.
La sessione porta due verdetti, che rispondono a domande diverse — se il client fosse automatizzato e che cosa abbia concluso complessivamente il motore di rischio:
| Campo | Valori |
|---|---|
bot.result | human, bot, uncertain |
bot.type | Presente quando bot.result è bot: o uno strumento specifico (playwright, puppeteer, selenium, jsdom, claude_computer_use…) oppure una famiglia quando lo strumento non è identificato — automation, headless, antidetect, extension, privacy_browser, other |
decision.action | real, fake, suspicious |
bot.score e decision.riskScore vanno entrambi da 0..100. Da Business in su,
guidance li trasforma in indicazioni per singolo scenario sulla scala
allow → challenge → review → deny — veda
Guidance per il significato di ciascun
gradino.
curl -H "Authorization: Bearer tracio_sk_XXXX...XXXX" \ "https://api.tracio.ai/v1/visitors/X7fh2Hg9LkMn3pQr5tBvQw3xZa9mK2pL4nR8dT6y/sessions?limit=100&minRiskScore=50"| Parametro | Predefinito | Note |
|---|---|---|
limit | 50 | Limitato a 500; un valore maggiore viene ridotto, non rifiutato |
from, to | Conservazione del piano | La finestra temporale condivisa descritta sopra |
cursor | — | Cursore di paginazione opaco della pagina precedente |
botResult | — | Mantiene solo le sessioni con questo verdetto di bot |
minRiskScore | — | Mantiene solo le sessioni con punteggio di rischio pari o superiore, 0..100 |
Le sessioni tornano dalla più recente:
{ "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" }}La paginazione è basata su cursore. Non esiste un parametro page o offset: rimandi
il nextCursor ricevuto come cursor e prosegua finché hasMore è true.
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}Tratti il cursore come opaco — il suo contenuto è un dettaglio implementativo e può
cambiare. Un cursore che è stato modificato viene rifiutato con 400 invalid_request e
il messaggio malformed cursor.
Accanto al browser e al sistema operativo ricavati dallo User-Agent, una sessione porta ciò che il browser del visitatore riporta sulla macchina, ripulito dalla nostra parte. Ogni campo è assente quando la visita non portava tali dati, quindi consideri ciascuno facoltativo.
| Campo | Significato |
|---|---|
gpu | Modello della scheda video così come lo riporta il browser (WebGL), normalizzato in un nome leggibile — Intel Iris Xe Graphics, Apple M1 Pro, Qualcomm Adreno 830; Software renderer significa nessuna GPU reale (una macchina virtuale o un ambiente headless); Safari riporta Apple GPU |
network.proxyDetected | Il traffico HTTP della visita e i suoi percorsi di rete grezzi escono da reti diverse — un proxy o una VPN davanti al browser; due indirizzi dello stesso operatore (NAT dell'operatore, una seconda uscita della stessa VPN) non contano |
network.realIp.address, .country, .isp | L'indirizzo pubblico osservato sul percorso di rete grezzo, ossia l'indirizzo dietro il proxy o la VPN, con il suo paese e il suo ISP — da Business in su; assente quando non è stato osservato alcun indirizzo del genere (country e isp sono assenti quando non è stato possibile risolverli) |
deviceInfo.deviceId, .crossBrowser, .confidence, .linkedBrowsers | Presente quando è stata risolta un'identità del dispositivo: un id stabile del dispositivo fisico comune ai browser presenti su di esso, se questa visita sia arrivata da un browser diverso da prima, la confidenza di tale corrispondenza e quanti visitatori (browser) distinti condividono il dispositivo — più di uno significa una sola macchina sotto diverse identità di browser — da Business in su |
osEnvironment | L'ambiente desktop misurato su una macchina Linux (Mint 22+, Ubuntu, GNOME, KDE) — da Business in su; assente quando non è determinato |
spoofing | Ciò che la visita ha dichiarato rispetto a ciò che hanno misurato controlli indipendenti (claimed, real, spoofedAxes fra os, gpu, screen, network, browser; anonymousBrowser con i nomi dei prodotti) — da Business in su; presente solo quando è stata rilevata una contraffazione |
screen.width, .height, .colorDepth, .pixelRatio | Risoluzione dello schermo, profondità di colore e device pixel ratio così come li riporta il browser — da Business in su |
locale.languages, locale.timezone | Le lingue preferite e il fuso orario del browser stesso — a differenza di geo.timezone, derivato dall'indirizzo IP; una discordanza fra i due è un segno frequente di posizione falsificata — da Business in su |
clientHints.architecture, .bitness, .model, .deviceName, .platformVersion | User-Agent Client Hints: architettura e bitness della CPU, codice del modello del dispositivo (Android, ad es. SM-A556B) con il suo nome commerciale dall'elenco dei dispositivi Google Play (deviceName, ad es. Samsung Galaxy A55 5G) e la versione esatta della piattaforma; solo browser basati su Chromium — da Business in su |
environment.virtualMachine, environment.hypervisor | Presente solo quando la scheda video si è dichiarata virtuale; hypervisor è un dizionario chiuso (vmware, virtualbox, parallels, qemu, hyperv, bochs, intel-gvt, vgpu). Un blocco assente significa che non c'è alcun indizio del genere — da Business in su |
extensions elenca le estensioni del browser rilevate durante la visita — da Business in su. Ogni voce è un oggetto:
| Campo | Significato |
|---|---|
slug | Identificatore stabile e leggibile dalla macchina dell'estensione, lo stesso valore che consegna il webhook |
name | Nome leggibile dalle persone |
category | Classe generale — adblock, privacy, automation, wallet, vpn, devtools, other, e così via |
risky | true per le estensioni associate ad automazione, contraffazione o furto di credenziali |
storeUrl | Link alla scheda dell'estensione nello store, quando è noto |
Un rilevamento viene riportato solo dopo aver superato i nostri controlli di affidabilità — un ambiente che risponde «installata» a ogni sonda, oppure un lotto di più di dodici nomi, viene scartato come inattendibile. Una lista vuota o assente significa quindi «nulla che abbiamo potuto confermare», non «nessuna estensione installata». La legga come indizio, non come inventario.
La più recente:
curl -H "Authorization: Bearer tracio_sk_XXXX...XXXX" \ "https://api.tracio.ai/v1/visitors/X7fh2Hg9LkMn3pQr5tBvQw3xZa9mK2pL4nR8dT6y/sessions/latest"Restituisce un oggetto sessione nudo — non un array e non avvolto in un envelope. Un
visitatore senza sessioni nella finestra restituisce 404 not_found con
no sessions for this visitor in the retention window.
Oppure tramite requestId, l'identificatore che compare anche nel payload del webhook:
curl -H "Authorization: Bearer tracio_sk_XXXX...XXXX" \ "https://api.tracio.ai/v1/sessions/9c1f6a2e-3b7d-4c58-a1e2-6f0d8b4a7c31?visitorId=X7fh2Hg9LkMn3pQr5tBvQw3xZa9mK2pL4nR8dT6y"Qui visitorId è facoltativo, ma passarlo quando lo conosce rende la ricerca nettamente
più veloce.
Velocity risponde alla domanda «quanto ha fatto ultimamente questo visitatore» — la forma del credential stuffing, del card testing e delle registrazioni di massa.
curl -H "Authorization: Bearer tracio_sk_XXXX...XXXX" \ "https://api.tracio.ai/v1/visitors/X7fh2Hg9LkMn3pQr5tBvQw3xZa9mK2pL4nR8dT6y/velocity?window=1h"window accetta 1h, 24h o 7d e il valore predefinito è 24h. Qualsiasi altro
valore viene rifiutato con 400 invalid_request e
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 conta i valori distinti di linkedId che ha inviato per questo
dispositivo — veda Collegamento degli account. botEvents è da
Business in su.
Un campo assente significa «nessun dato», mai zero. I campi senza valore vengono
omessi del tutto invece di essere inviati come 0, "" o null: un visitatore
nuovissimo non ha matchConfidence, una visita pulita non ha antidetectScore né
suspectScore. L'unica eccezione deliberata è bot.score, sempre presente anche quando
vale zero. Legga i campi in modo difensivo.
Il payload dipende dal suo piano. Ogni piano con accesso all'API riceve la sessione di base — identificatori, timestamp, URL, IP, user agent, browser, sistema operativo, dispositivo, geo, rete, bot, identificazione e decisione. Pro aggiunge identification.matchType, identification.matchConfidence, bot.type, bot.antidetectScore, gpu e network.proxyDetected. Business ed Enterprise aggiungono extensions, geo.isp, network.asn, network.realIp, decision.suspectScore, identification.driftScore, reasons, behavior, guidance, deviceInfo, spoofing, osEnvironment, screen, locale, clientHints ed environment. I campi a livello di persona (personId, reputation, linkedAccountsCount, linkedVisitorsCount) sono riservati a Business ed Enterprise e compariranno una volta attivato il livello persona — oggi funziona in modalità di osservazione e questi campi non vengono consegnati. L'assenza di un campo Business su un piano Pro non è un errore.
Gli interni a livello di segnale non vengono mai restituiti, su nessun piano: i nomi dei singoli segnali, i loro pesi, le soglie dietro un verdetto, i valori grezzi dei segnali e la scomposizione dei punteggi restano dalla nostra parte. Un punteggio che può essere sottoposto a reverse engineering fino ai suoi input smette di essere utile come difesa.
Ogni errore usa un unico envelope:
{ "error": { "code": "unauthorized", "message": "missing Authorization: Bearer <secret key>", "requestId": "8f14e45fceea167a5a36dedd" }}Questo requestId non è l'identificatore della visita. Due valori diversi
condividono il nome: dentro il payload di una sessione requestId è l'UUID della
visita, lo stesso che recapita il webhook; dentro un envelope di errore è un
identificatore di traccia di 24 caratteri, generato per ogni chiamata HTTP.
L'identificatore di traccia torna anche nell'header X-Request-Id di ogni risposta,
riuscita o meno. Lo includa quando contatta il supporto — è così che troviamo la sua
chiamata esatta.
| HTTP | code | Significato |
|---|---|---|
| 400 | invalid_request | Un parametro manca o è malformato |
| 401 | unauthorized | La chiave è assente, non valida, revocata o scaduta |
| 402 | upgrade_required | Il suo piano non include l'accesso all'API |
| 404 | not_found | Nulla ha trovato corrispondenza dentro la finestra di conservazione |
| 405 | method_not_allowed | La rotta esiste, ma non per quel metodo |
| 429 | rate_limited | Superate le richieste al secondo o la quota giornaliera |
| 500 | internal | Qualcosa è fallito dalla nostra parte |
| 503 | unavailable | Uno store di supporto è temporaneamente irraggiungibile |
I controlli avvengono in un ordine fisso — chiave, poi piano, poi limiti — così una richiesta con una chiave errata segnala sempre prima la chiave, mai un problema di quota.
Due casi di 401 si leggono in modo diverso di proposito:
missing Authorization: Bearer <secret key> significa che l'header non è mai arrivato,
mentre invalid or revoked API key significa che è arrivato e non ha trovato
corrispondenza. 402 porta Data API requires the Pro plan or higher.
Ogni risposta autenticata porta la sua situazione corrente:
| Header | Significato |
|---|---|
X-RateLimit-Limit | La sua quota giornaliera |
X-RateLimit-Remaining | Chiamate rimaste oggi |
X-RateLimit-Reset | Orario Unix del reset — mezzanotte UTC |
Retry-After | Secondi da attendere, inviato solo con un 429 |
| Piano | Richieste al secondo | Richieste al giorno | Profondità della cronologia |
|---|---|---|---|
| Free | Nessun accesso API | — | 7 giorni |
| Pro | 10 | 10.000 | 30 giorni |
| Business | 50 | 100.000 | 90 giorni |
| Enterprise | 200 | Illimitate | 365 giorni |