A Data API (antes documentada aqui como Server API) permite que o seu backend leia os dados de identificação que a TRACIO já coletou para o seu workspace: o histórico de um visitante, sessões individuais e contadores de velocity de janela curta.
Ela complementa os webhooks em vez de substituí-los:
| Webhooks | Data API | |
|---|---|---|
| Direção | A TRACIO envia para o seu endpoint | O seu backend consulta sob demanda |
| Momento | Conforme cada identificação acontece | A qualquer momento, dentro da sua janela de retenção |
| Ideal para | Reagir a um evento | Consultar dados durante uma decisão, preencher históricos, investigações |
Ambas as superfícies estão disponíveis a partir do plano Pro.
https://api.tracio.ai/v1Este é um host diferente do endpoint do navegador (edge.tracio.ai) e do painel
(app.tracio.ai). Os três são separados: o navegador fala com o edge usando a sua
chave pública, e o seu backend fala com a Data API usando a sua chave
secreta.
Toda requisição carrega a sua chave secreta como bearer token:
curl -H "Authorization: Bearer tracio_sk_XXXX...XXXX" \ "https://api.tracio.ai/v1/visitors/X7fh2Hg9LkMn3pQr5tBvQw3xZa9mK2pL4nR8dT6y"A Data API é apenas de servidor para servidor. Os cabeçalhos CORS deliberadamente não são retornados, então um navegador não consegue chamá-la — é isso que mantém a sua chave secreta fora do código do lado do cliente. Nunca envie a chave secreta para o navegador.
Crie-a no painel, em API Keys, escolhendo o tipo secret.
tracio_sk_ seguido de 43 caracteres, 53 no total.
O painel a lista pelos primeiros caracteres, para que você consiga diferenciar as
chaves.A rotação emite uma chave nova e mantém a antiga funcionando por 7 dias, para que você possa implantá-la sem indisponibilidade. Implante a chave nova, confirme que o tráfego migrou e deixe a antiga expirar. Chaves públicas não são rotacionáveis — elas não são segredos e ficam visíveis no código-fonte das suas páginas por design.
Toda rota é um GET. Não há operações de escrita na Data API: ela lê dados, e a sua
configuração vive no painel.
| Método | Caminho | Retorna |
|---|---|---|
GET | /v1/visitors/{visitorId} | Histórico agregado de um visitante, mais a sessão mais recente dele |
GET | /v1/visitors/{visitorId}/sessions | Lista paginada das sessões desse visitante |
GET | /v1/visitors/{visitorId}/sessions/latest | A única sessão mais recente |
GET | /v1/visitors/{visitorId}/velocity | Contadores de atividade em uma janela curta |
GET | /v1/sessions/{requestId} | Uma sessão, pelo identificador da requisição |
GET | /.well-known/webhook-keys | Chaves públicas da assinatura de plataforma dos webhooks (sem auth) |
Uma barra final é aceita e ignorada. Um caminho desconhecido ou um método errado retorna o mesmo envelope de erro JSON que todo o resto, nunca uma página HTML ou de texto puro.
Toda leitura é limitada por uma janela de tempo, controlada por dois parâmetros de query opcionais:
| Parâmetro | Aceita |
|---|---|
from | YYYY-MM-DD ou um timestamp RFC 3339 completo |
to | YYYY-MM-DD ou um timestamp RFC 3339 completo |
to inclui aquele dia inteiro.400 invalid_request e a mensagem
time must be YYYY-MM-DD or RFC3339.meta, então verifique meta.from e meta.to em vez de supor que a sua
requisição foi atendida ao pé da letra.curl -H "Authorization: Bearer tracio_sk_XXXX...XXXX" \ "https://api.tracio.ai/v1/visitors/X7fh2Hg9LkMn3pQr5tBvQw3xZa9mK2pL4nR8dT6y"A resposta carrega o histórico agregado e embute a sessão mais recente, então o caso comum precisa de uma requisição em vez de duas:
{ "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 | Significado |
|---|---|
visits, incognitoVisits | Total de visitas na janela, e quantas foram em uma janela privada |
uniqueIps, uniqueCountries | Endereços e países distintos observados na janela |
browsers, os, devices | Os ambientes distintos em que este visitante apareceu |
risk.maxRiskScore | A maior pontuação de risco registrada na janela, 0..100 |
risk.lastDecision | A decisão registrada para a visita mais recente |
risk.avgBotScore, risk.botSessions | Média da pontuação de bot e o número de sessões de bot — Business e acima |
network.*Seen | Se um VPN, proxy, nó de saída Tor ou endereço de datacenter já foi visto para este visitante |
network.proxyDetectedSeen | Se pelo menos uma visita na janela saiu por um proxy ou VPN à frente do navegador — veja network.proxyDetected em Fatos do dispositivo |
network.lastIsp | O ISP mais recente — Business e acima |
network.lastRealIp | O endereço mais recente observado atrás de um proxy ou VPN — Business e acima; ausente quando nenhum foi observado |
lastSession | O objeto de sessão completo da visita mais recente |
meta | O plano, a retenção dele em dias e a janela efetivamente aplicada |
Um visitante sem dados dentro da janela de retenção retorna 404 not_found com a
mensagem visitor not found in the retention window — isso não é um erro da sua
integração, significa que o visitante é novo ou que os dados dele expiraram.
A sessão carrega dois vereditos, e eles respondem a perguntas diferentes — se o cliente era automatizado, e o que o motor de risco concluiu no geral:
| Campo | Valores |
|---|---|
bot.result | human, bot, uncertain |
bot.type | Presente quando bot.result é bot: ou uma ferramenta específica (playwright, puppeteer, selenium, jsdom, claude_computer_use…) ou uma família quando a ferramenta não é nomeada — automation, headless, antidetect, extension, privacy_browser, other |
decision.action | real, fake, suspicious |
bot.score e decision.riskScore vão ambos de 0..100. No Business e acima,
guidance os transforma em recomendações por cenário na escada
allow → challenge → review → deny — veja
Guidance para o que cada degrau
significa.
curl -H "Authorization: Bearer tracio_sk_XXXX...XXXX" \ "https://api.tracio.ai/v1/visitors/X7fh2Hg9LkMn3pQr5tBvQw3xZa9mK2pL4nR8dT6y/sessions?limit=100&minRiskScore=50"| Parâmetro | Padrão | Observações |
|---|---|---|
limit | 50 | Limitado a 500; um valor maior é ajustado ao limite, não rejeitado |
from, to | Retenção do plano | A janela de tempo compartilhada descrita acima |
cursor | — | Cursor de paginação opaco vindo da página anterior |
botResult | — | Manter apenas as sessões com este veredito de bot |
minRiskScore | — | Manter apenas as sessões com esta pontuação de risco ou acima, 0..100 |
As sessões voltam da mais recente para a mais antiga:
{ "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" }}A paginação é baseada em cursor. Não há parâmetro page nem offset: devolva o
nextCursor que você recebeu como cursor e siga enquanto hasMore for 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}Trate o cursor como opaco — o conteúdo dele é um detalhe de implementação e pode
mudar. Um cursor que tenha sido editado é rejeitado com 400 invalid_request e a
mensagem malformed cursor.
Além do navegador e do sistema operacional obtidos do User-Agent, uma sessão carrega o que o navegador do visitante informa sobre a máquina, higienizado do nosso lado. Cada campo fica ausente quando a visita não trouxe esses dados, então trate cada um como opcional.
| Campo | Significado |
|---|---|
gpu | Modelo do adaptador de vídeo conforme informado pelo navegador (WebGL), normalizado para um nome legível — Intel Iris Xe Graphics, Apple M1 Pro, Qualcomm Adreno 830; Software renderer significa que não há GPU real (uma máquina virtual ou um ambiente headless); o Safari informa Apple GPU |
network.proxyDetected | O tráfego HTTP da visita e seus caminhos de rede brutos saem por redes diferentes — um proxy ou VPN à frente do navegador; dois endereços do mesmo provedor (NAT da operadora, uma segunda saída da mesma VPN) não contam |
network.realIp.address, .country, .isp | O endereço público observado no caminho de rede bruto, ou seja, o endereço atrás do proxy ou da VPN, com o país e o ISP dele — Business e acima; ausente quando nenhum endereço desse tipo foi observado (country e isp ficam ausentes quando não puderam ser resolvidos) |
deviceInfo.deviceId, .crossBrowser, .confidence, .linkedBrowsers | Presente quando uma identidade de dispositivo foi resolvida: um id estável do dispositivo físico comum aos navegadores que há nele, se esta visita chegou por um navegador diferente do anterior, a confiança dessa correspondência e quantos visitantes (navegadores) distintos compartilham o dispositivo — mais de um significa uma máquina sob várias identidades de navegador — Business e acima |
osEnvironment | O ambiente de desktop medido em uma máquina Linux (Mint 22+, Ubuntu, GNOME, KDE) — Business e acima; ausente quando não determinado |
spoofing | O que a visita afirmou em comparação com o que verificações independentes mediram (claimed, real, spoofedAxes de os, gpu, screen, network, browser; anonymousBrowser com nomes de produto) — Business e acima; presente apenas quando uma falsificação foi detectada |
screen.width, .height, .colorDepth, .pixelRatio | Resolução de tela, profundidade de cor e device pixel ratio conforme informados pelo navegador — Business e acima |
locale.languages, locale.timezone | Os idiomas preferidos e o fuso horário do próprio navegador — ao contrário de geo.timezone, derivado do endereço IP; uma divergência entre os dois é um sinal comum de localização falsificada — Business e acima |
clientHints.architecture, .bitness, .model, .deviceName, .platformVersion | User-Agent Client Hints: arquitetura e bitness da CPU, código do modelo do dispositivo (Android, p. ex. SM-A556B) com seu nome comercial da lista de dispositivos do Google Play (deviceName, p. ex. Samsung Galaxy A55 5G) e a versão exata da plataforma; apenas navegadores baseados em Chromium — Business e acima |
environment.virtualMachine, environment.hypervisor | Presente apenas quando o adaptador de vídeo se identificou como virtual; hypervisor é um dicionário fechado (vmware, virtualbox, parallels, qemu, hyperv, bochs, intel-gvt, vgpu). Um bloco ausente significa que não há tal indício — Business e acima |
extensions lista as extensões do navegador detectadas durante a visita — Business e acima. Cada entrada é um objeto:
| Campo | Significado |
|---|---|
slug | Identificador estável e legível por máquina da extensão, o mesmo valor que o webhook entrega |
name | Nome legível por humanos |
category | Classe geral — adblock, privacy, automation, wallet, vpn, devtools, other, e assim por diante |
risky | true para extensões associadas a automação, falsificação ou roubo de credenciais |
storeUrl | Link para a página da extensão na loja, quando conhecido |
Um achado só é reportado depois de passar pelas nossas verificações de confiabilidade — um ambiente que responde "instalada" a cada sondagem, ou um lote com mais de doze nomes, é descartado como não confiável. Portanto, uma lista vazia ou ausente significa "nada que pudemos confirmar", não "nenhuma extensão instalada". Leia-a como indício, não como inventário.
A mais recente:
curl -H "Authorization: Bearer tracio_sk_XXXX...XXXX" \ "https://api.tracio.ai/v1/visitors/X7fh2Hg9LkMn3pQr5tBvQw3xZa9mK2pL4nR8dT6y/sessions/latest"Isso retorna um objeto de sessão puro — não um array, e não embrulhado em um envelope.
Um visitante sem sessões na janela retorna 404 not_found com
no sessions for this visitor in the retention window.
Ou pelo requestId, o identificador que também aparece no payload do webhook:
curl -H "Authorization: Bearer tracio_sk_XXXX...XXXX" \ "https://api.tracio.ai/v1/sessions/9c1f6a2e-3b7d-4c58-a1e2-6f0d8b4a7c31?visitorId=X7fh2Hg9LkMn3pQr5tBvQw3xZa9mK2pL4nR8dT6y"visitorId é opcional aqui, mas passá-lo quando você o conhece torna a busca
sensivelmente mais rápida.
Velocity responde "quanto este visitante andou fazendo ultimamente" — o formato de credential stuffing, teste de cartões e cadastros em massa.
curl -H "Authorization: Bearer tracio_sk_XXXX...XXXX" \ "https://api.tracio.ai/v1/visitors/X7fh2Hg9LkMn3pQr5tBvQw3xZa9mK2pL4nR8dT6y/velocity?window=1h"window aceita 1h, 24h ou 7d e o padrão é 24h. Qualquer outro valor é
rejeitado com 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 os valores linkedId distintos que você enviou para este
dispositivo — veja Vinculação de contas. botEvents é do
Business e acima.
Um campo ausente significa "sem dados", nunca zero. Campos sem valor são omitidos
por completo em vez de enviados como 0, "" ou null: um visitante novinho em
folha não tem matchConfidence, e uma visita limpa não tem antidetectScore nem
suspectScore. A única exceção deliberada é bot.score, que está sempre presente
mesmo quando vale zero. Leia os campos de forma defensiva.
O payload depende do seu plano. Todo plano com acesso à API recebe a sessão base — identificadores, timestamp, URL, IP, user agent, navegador, SO, dispositivo, geo, network, bot, identification e decision. O Pro adiciona identification.matchType, identification.matchConfidence, bot.type, bot.antidetectScore, gpu e network.proxyDetected. Business e Enterprise adicionam extensions, geo.isp, network.asn, network.realIp, decision.suspectScore, identification.driftScore, reasons, behavior, guidance, deviceInfo, spoofing, osEnvironment, screen, locale, clientHints e environment. Os campos em nível de pessoa (personId, reputation, linkedAccountsCount, linkedVisitorsCount) são reservados a Business e Enterprise e aparecerão assim que a camada de pessoa for ativada — hoje ela funciona em modo de observação e esses campos não são entregues. A ausência de um campo de Business em um plano Pro não é um erro.
Detalhes internos em nível de sinal nunca são retornados, em nenhum plano: nomes de sinais individuais, seus pesos, os limiares por trás de um veredito, valores brutos de sinais e a decomposição das pontuações ficam do nosso lado. Uma pontuação que pode ser submetida a engenharia reversa até as suas entradas deixa de ser útil como defesa.
Toda falha usa um único envelope:
{ "error": { "code": "unauthorized", "message": "missing Authorization: Bearer <secret key>", "requestId": "8f14e45fceea167a5a36dedd" }}Este requestId não é o identificador da visita. Dois valores diferentes dividem o
mesmo nome: dentro de um payload de sessão, requestId é o UUID da visita, o mesmo que
o webhook entrega; dentro de um envelope de erro, é um identificador de trace de 24
caracteres, cunhado a cada chamada HTTP. O identificador de trace também volta no
cabeçalho X-Request-Id em toda resposta, bem-sucedida ou não. Inclua-o quando entrar
em contato com o suporte — é assim que encontramos a sua chamada exata.
| HTTP | code | Significado |
|---|---|---|
| 400 | invalid_request | Um parâmetro está ausente ou malformado |
| 401 | unauthorized | A chave está ausente, inválida, revogada ou expirada |
| 402 | upgrade_required | O seu plano não inclui acesso à API |
| 404 | not_found | Nada correspondeu dentro da janela de retenção |
| 405 | method_not_allowed | A rota existe, mas não para esse método |
| 429 | rate_limited | Requisições por segundo, ou a cota diária, excedidas |
| 500 | internal | Algo falhou do nosso lado |
| 503 | unavailable | Um armazenamento de apoio está temporariamente inacessível |
As verificações rodam em uma ordem fixa — chave, depois plano, depois limites —, então uma requisição com uma chave ruim sempre reporta a chave primeiro, nunca um problema de cota.
Dois casos de 401 são redigidos de forma diferente de propósito:
missing Authorization: Bearer <secret key> significa que o cabeçalho nunca chegou,
enquanto invalid or revoked API key significa que ele chegou e não correspondeu. O
402 carrega Data API requires the Pro plan or higher.
Toda resposta autenticada carrega a sua situação atual:
| Cabeçalho | Significado |
|---|---|
X-RateLimit-Limit | A sua cota diária |
X-RateLimit-Remaining | Chamadas restantes hoje |
X-RateLimit-Reset | Horário Unix do reset — meia-noite UTC |
Retry-After | Segundos de espera, enviado apenas com um 429 |
| Plano | Requisições por segundo | Requisições por dia | Profundidade do histórico |
|---|---|---|---|
| Free | Sem acesso à API | — | 7 dias |
| Pro | 10 | 10.000 | 30 dias |
| Business | 50 | 100.000 | 90 dias |
| Enterprise | 200 | Sem limite | 365 dias |