A 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 | Server 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 Server 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 Server 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 Server 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, "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" }}| 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.lastIsp | O ISP mais recente — Business e acima |
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 |
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(`Server 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.
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 e bot.antidetectScore. Business e Enterprise
adicionam geo.isp, network.asn, decision.suspectScore,
identification.driftScore, reasons, behavior, guidance, deviceInfo e os
campos em nível de pessoa (personId, reputation, linkedAccountsCount,
linkedVisitorsCount). 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 |