La Data API (auparavant documentée ici sous le nom de Server API) permet à votre backend de lire les données d'identification que TRACIO a déjà collectées pour votre espace de travail : l'historique d'un visiteur, les sessions individuelles et les compteurs de velocity sur une courte fenêtre. Quelques routes écrivent aussi — fiches de compte, champs personnalisés et entrées de listes — afin que votre backend puisse enrichir ce que TRACIO sait.
Elle complète les webhooks plutôt qu'elle ne les remplace :
| Webhooks | Data API | |
|---|---|---|
| Direction | TRACIO pousse vers votre endpoint | Votre backend interroge à la demande |
| Moment | À chaque identification | À tout moment, sur votre fenêtre de rétention |
| Idéal pour | Réagir à un événement | Consulter des données pendant une décision, des reprises, des investigations |
Les deux surfaces sont disponibles à partir de l'offre Pro.
https://api.tracio.ai/v2C'est un hôte différent de l'endpoint navigateur (edge.tracio.ai) et du tableau de
bord (app.tracio.ai). Les trois sont distincts : le navigateur s'adresse à l'edge
avec votre clé publique, votre backend s'adresse à la Data API avec votre clé
secrète.
Les routes /v1 ont été supprimées. Utilisez /v2 : les routes sont les mêmes, et chaque
clé JSON et chaque paramètre de requête est en snake_case (visitor_id, risk_score,
min_risk_score). Les clés à l'intérieur de vos propres dictionnaires fields et labels
sont vos données et conservent leur graphie d'origine. Le SDK JavaScript conserve son
interface en camelCase.
Chaque requête porte votre clé secrète sous forme de bearer token :
curl -H "Authorization: Bearer tracio_sk_XXXX...XXXX" \ "https://api.tracio.ai/v2/visitors/X7fh2Hg9LkMn3pQr5tBvQw3xZa9mK2pL4nR8dT6y"La Data API est strictement de serveur à serveur. Les en-têtes CORS ne sont délibérément pas renvoyés, si bien qu'un navigateur ne peut pas l'appeler — c'est ce qui garde votre clé secrète hors du code côté client. Ne livrez jamais la clé secrète au navigateur.
Créez-la dans le tableau de bord, sous API Keys, en choisissant le type secret.
tracio_sk_ suivi de 43 caractères, 53 au total. Le
tableau de bord la liste par ses premiers caractères, ce qui vous permet de
distinguer les clés entre elles.404, exactement comme pour des données qui n'existent pas.La rotation émet une nouvelle clé et laisse l'ancienne fonctionner pendant 7 jours, ce qui vous permet de la déployer sans interruption de service. Déployez la nouvelle clé, vérifiez que le trafic est passé dessus, puis laissez l'ancienne expirer. Les clés publiques ne sont pas rotatives — ce ne sont pas des secrets et elles sont visibles dans le source de vos pages, par conception.
| Méthode | Chemin | Renvoie |
|---|---|---|
GET | /v2/visitors/{visitor_id} | Historique agrégé d'un visiteur, plus sa session la plus récente |
GET | /v2/visitors/{visitor_id}/sessions | Liste paginée des sessions de ce visiteur |
GET | /v2/visitors/{visitor_id}/sessions/latest | L'unique session la plus récente |
GET | /v2/visitors/{visitor_id}/velocity | Compteurs d'activité sur une courte fenêtre |
GET | /v2/sessions/{request_id} | Une session, par son identifiant de requête |
POST | /v2/accounts/metadata | Rattacher des noms d'affichage et des e-mails à vos identifiants de compte |
POST | /v2/events/fields | Rattacher des champs personnalisés à une visite après le chargement de la page |
POST | /v2/events/fields/delete | Effacer des champs personnalisés d'une visite — la visite elle-même n'est pas touchée |
GET | /v2/lists | Les listes de votre espace de travail : id, nom, type (l'action), mode, nombre d'entrées |
POST | /v2/lists/{list_id}/entries | Ajouter une valeur à une liste, avec un TTL facultatif |
POST | /v2/lists/{list_id}/entries/delete | Retirer une valeur d'une liste |
GET | /.well-known/webhook-keys | Clés publiques de la signature de plateforme des webhooks (sans auth) |
La même clé et le même hôte servent aussi les API de décision, toutes en snake_case :
| Méthode | Chemin | Rôle |
|---|---|---|
POST | /v2/transactions | Une décision synchrone APPROVE / REVIEW / DECLINE sur un événement métier — Business et au-delà, sauf une conversion d'affilié (account_register ou deposit avec affiliate_id), disponible sur toute offre avec accès à l'API |
GET | /v2/transactions/{transaction_id} | Relire une décision avec son label et son état effectif — Business et au-delà, sauf une transaction attribuée à un affilié |
PUT, DELETE | /v2/transaction-labels | Définir ou retirer le label de résultat de 50 transactions au plus — Business et au-delà |
GET | /v2/transaction-labels/value-set | Les valeurs de label acceptées avec leur polarité — Business et au-delà |
POST | /v2/logins/assess | Évaluer une connexion : allow / challenge / review / deny avec les raisons |
POST, GET | /v2/accounts/{user_id}/trusted-devices | Accorder la confiance à l'appareil d'une visite pour un compte après votre step-up, ou lister les appareils de confiance du compte |
DELETE | /v2/accounts/{user_id}/trusted-devices/{device_ref} | Révoquer la confiance accordée à un appareil |
GET | /v2/affiliates/{affiliate_id} | Le rapport de qualité d'un affilié sur 90 jours, avec une recommandation hold / review |
POST, GET | /v2/self-exclusions | Poser ou prolonger une auto-exclusion sur un compte, ou les lister |
DELETE | /v2/self-exclusions/{user_id} | Lever une auto-exclusion de manière anticipée |
Leurs résultats vous parviennent aussi sous forme d'événements de webhook :
transaction_state_changed, login_risk, trusted_device_enrolled,
trusted_device_revoked et affiliate_threshold_crossed.
Une barre oblique finale est acceptée et ignorée. Un chemin inconnu ou une mauvaise méthode renvoie la même enveloppe d'erreur JSON que tout le reste, jamais une page HTML ou en texte brut.
Chaque lecture est bornée par une fenêtre temporelle, contrôlée par deux paramètres de requête optionnels :
| Paramètre | Accepte |
|---|---|
from | YYYY-MM-DD ou un horodatage RFC 3339 complet |
to | YYYY-MM-DD ou un horodatage RFC 3339 complet |
to inclut la journée entière.400 invalid_request et le message
time must be YYYY-MM-DD or RFC3339.meta : vérifiez donc meta.from et meta.to au lieu de
supposer que votre requête a été honorée telle quelle.curl -H "Authorization: Bearer tracio_sk_XXXX...XXXX" \ "https://api.tracio.ai/v2/visitors/X7fh2Hg9LkMn3pQr5tBvQw3xZa9mK2pL4nR8dT6y"La réponse porte l'historique agrégé et embarque la session la plus récente : le cas courant ne demande donc qu'une requête au lieu de deux :
{ "visitor_id": "X7fh2Hg9LkMn3pQr5tBvQw3xZa9mK2pL4nR8dT6y", "first_seen_at": "2026-05-02T10:11:12Z", "last_seen_at": "2026-07-25T08:00:00Z", "visits": 42, "incognito_visits": 3, "unique_ips": 5, "unique_countries": 2, "browsers": ["Chrome"], "os": ["macOS"], "devices": ["desktop"], "risk": { "max_risk_score": 63, "avg_bot_score": 12.5, "bot_sessions": 7, "last_decision": "real" }, "network": { "vpn_seen": false, "proxy_seen": false, "tor_seen": false, "datacenter_seen": true, "proxy_detected_seen": true, "last_isp": "Deutsche Telekom", "last_real_ip": { "address": "203.0.113.7", "country": "NL", "isp": "KPN" } }, "last_session": { "request_id": "9c1f6a2e-3b7d-4c58-a1e2-6f0d8b4a7c31", "visitor_id": "X7fh2Hg9LkMn3pQr5tBvQw3xZa9mK2pL4nR8dT6y", "account_id": "user_8842", "account": { "id": "user_8842", "email": "ann@example.com", "display_name": "Ann Lee" }, "timestamp": "2026-07-25T08:00:00Z", "tag": "checkout", "fields": { "order_ref": "A-1029" }, "url": "https://shop.example.com/checkout", "ip": "203.0.113.42", "user_agent": "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, "connection_type": "residential", "proxy_detected": true, "real_ip": { "address": "203.0.113.7", "country": "NL", "isp": "KPN" }, "asn": 3320 }, "screen": { "width": 2560, "height": 1600, "color_depth": 30, "pixel_ratio": 2 }, "locale": { "languages": ["en-US", "de"], "timezone": "Europe/Berlin" }, "client_hints": { "architecture": "arm", "bitness": "64", "platform_version": "14.5.0" }, "extensions": [ { "slug": "ublock-origin", "name": "uBlock Origin", "category": "adblock", "risky": false, "store_url": "https://chromewebstore.google.com/detail/cjpalhdlnbpafiamejdnhcphjbkeiagm" } ], "bot": { "result": "human", "score": 4.5, "antidetect_score": 2.1 }, "identification": { "confidence": 0.97, "incognito": false, "match_type": "exact", "match_confidence": 0.99 }, "device_info": { "device_id": "d_4f9c2e", "cross_browser": true, "confidence": 0.88, "linked_browsers": 2 }, "decision": { "action": "real", "risk_score": 12, "suspect_score": 3 } }, "meta": { "plan": "business", "retention_days": 90, "from": "2026-04-26T00:00:00Z", "to": "2026-07-25T12:00:00Z" }}| Champ | Signification |
|---|---|
visits, incognito_visits | Total des visites dans la fenêtre, et combien l'ont été dans une fenêtre de navigation privée |
unique_ips, unique_countries | Adresses et pays distincts observés dans la fenêtre |
browsers, os, devices | Les environnements distincts dans lesquels ce visiteur est apparu |
risk.max_risk_score | Le score de risque le plus élevé enregistré dans la fenêtre, 0..100 |
risk.last_decision | La décision enregistrée pour la visite la plus récente |
risk.avg_bot_score, risk.bot_sessions | Moyenne du score de bot et nombre de sessions de bot — Business et au-delà |
network.*_seen | Si un VPN, un proxy, un nœud de sortie Tor ou une adresse de datacenter a déjà été vu pour ce visiteur |
network.proxy_detected_seen | Si au moins une visite de la fenêtre est sortie par un proxy ou un VPN devant le navigateur — voir network.proxy_detected dans Faits sur l'appareil |
network.last_isp | L'ISP le plus récent — Business et au-delà |
network.last_real_ip | L'adresse la plus récente observée derrière un proxy ou un VPN (address, country, isp) — Business et au-delà ; absente lorsqu'aucune visite n'en a apporté |
last_session | L'objet session complet de la visite la plus récente |
meta | L'offre, sa rétention en jours et la fenêtre réellement appliquée |
Un visiteur sans aucune donnée dans la fenêtre de rétention renvoie 404 not_found
avec le message visitor not found in the retention window — ce n'est pas une erreur
dans votre intégration : cela signifie que le visiteur est nouveau ou qu'il est sorti
de la fenêtre.
La session porte deux verdicts, et ils répondent à des questions différentes — si le client était automatisé, et ce que le moteur de risque a conclu globalement :
| Champ | Valeurs |
|---|---|
bot.result | human, bot, uncertain |
bot.type | Présent lorsque bot.result vaut bot : soit un outil précis (playwright, puppeteer, selenium, jsdom, claude_computer_use…), soit un agent d'IA vérifié (ai_agent:<vendor>), soit une famille lorsque l'outil n'est pas nommé — automation, headless, antidetect, extension, privacy_browser, impersonation, other |
decision.action | real, fake, suspicious |
bot.score et decision.risk_score vont tous deux de 0..100. À partir de Business,
guidance les transforme en recommandations par scénario sur l'échelle
allow → challenge → review → deny — voir
Guidance pour la signification de chaque
barreau. Les valeurs et les échelles correspondent au corps du webhook v3 pour la même
visite, et guidance est calculé selon les mêmes règles.
curl -H "Authorization: Bearer tracio_sk_XXXX...XXXX" \ "https://api.tracio.ai/v2/visitors/X7fh2Hg9LkMn3pQr5tBvQw3xZa9mK2pL4nR8dT6y/sessions?limit=100&min_risk_score=50"| Paramètre | Défaut | Notes |
|---|---|---|
limit | 50 | Plafonné à 500 ; une valeur plus grande est ramenée à la limite, pas rejetée |
from, to | Rétention de l'offre | La fenêtre temporelle partagée décrite plus haut |
cursor | — | Curseur de pagination opaque issu de la page précédente |
bot_result | — | Ne garder que les sessions portant ce verdict de bot |
min_risk_score | — | Ne garder que les sessions dont le score de risque atteint ce seuil, 0..100 |
Les sessions reviennent de la plus récente à la plus ancienne :
{ "items": [ { "request_id": "9c1f6a2e-3b7d-4c58-a1e2-6f0d8b4a7c31", "visitor_id": "X7fh2Hg9LkMn3pQr5tBvQw3xZa9mK2pL4nR8dT6y", "timestamp": "2026-07-25T08:00:00Z" } ], "next_cursor": "MTcyMTg5NDQwMDAwMDphYmMxMjM", "has_more": true, "meta": { "plan": "business", "retention_days": 90, "from": "2026-04-26T00:00:00Z", "to": "2026-07-25T12:00:00Z" }}La pagination est fondée sur un curseur. Il n'y a ni paramètre page ni paramètre
offset : renvoyez le next_cursor reçu en tant que cursor et continuez tant que
has_more vaut true. La dernière page peut ne pas inclure next_cursor du tout : ne
rendez donc pas ce champ obligatoire.
async function allSessions(visitorId: string, secretKey: string) { const sessions = [] let cursor: string | undefined
do { const url = new URL(`https://api.tracio.ai/v2/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.has_more ? page.next_cursor : undefined } while (cursor)
return sessions}Traitez le curseur comme opaque — son contenu est un détail d'implémentation et peut
changer. Un curseur qui a été modifié est rejeté avec 400 invalid_request et le
message malformed cursor.
Outre le navigateur et le système d'exploitation tirés du User-Agent, une session porte ce que le navigateur du visiteur rapporte de la machine, assaini de notre côté. Chaque champ est absent lorsque la visite ne portait pas ces données : traitez donc chacun comme facultatif.
| Champ | Signification |
|---|---|
gpu | Modèle de la carte graphique tel que le navigateur le rapporte (WebGL), normalisé en un nom lisible — Intel Iris Xe Graphics, Apple M1 Pro, Qualcomm Adreno 830 ; Software renderer signifie qu'il n'y a pas de vrai GPU (une machine virtuelle ou un environnement headless) ; Safari rapporte Apple GPU |
network.proxy_detected | Le trafic HTTP de la visite et ses chemins réseau bruts sortent par des réseaux différents — un proxy ou un VPN devant le navigateur ; deux adresses du même opérateur (NAT opérateur, une seconde sortie du même VPN) ne comptent pas ; non levé lorsque les adresses ne peuvent pas être comparées (iCloud Private Relay, IPv4 à côté d'IPv6) |
network.real_ip.address, .country, .isp | L'adresse publique observée sur le chemin réseau brut, c'est-à-dire l'adresse derrière le proxy ou le VPN, avec son pays et son ISP — Business et au-delà ; absente lorsqu'aucune adresse de ce type n'a été observée (country et isp sont absents lorsqu'ils n'ont pas pu être résolus) |
device_info.device_id, .cross_browser, .confidence, .linked_browsers | Présent lorsqu'une identité d'appareil a été résolue : un id stable de l'appareil physique commun aux navigateurs qui s'y trouvent, si cette visite est arrivée par un navigateur différent du précédent, la confiance de cette correspondance et combien de visiteurs (navigateurs) distincts partagent l'appareil — plus d'un signifie une seule machine sous plusieurs identités de navigateur — Business et au-delà |
os_environment | L'environnement de bureau mesuré sur une machine Linux (Mint 22+, Ubuntu 25.04+, Ubuntu, GNOME, KDE) — Business et au-delà ; absent lorsqu'il n'est pas déterminé |
spoofing | Ce que la visite a prétendu face à ce que des contrôles indépendants ont mesuré (claimed, real, spoofed_axes parmi os, gpu, screen, network, browser ; anonymous_browser avec les noms de produits) — Business et au-delà ; présent uniquement lorsqu'une falsification a été détectée |
screen.width, .height, .color_depth, .pixel_ratio | Résolution de l'écran, profondeur de couleur et device pixel ratio tels que le navigateur les rapporte — Business et au-delà |
locale.languages, locale.timezone | Les langues préférées et le fuseau horaire du navigateur lui-même — par opposition à geo.timezone, dérivé de l'adresse IP ; un écart entre les deux est un signe courant de localisation falsifiée — Business et au-delà |
client_hints.architecture, .bitness, .model, .device_name, .platform_version | User-Agent Client Hints : architecture et bitness du processeur, code de modèle de l'appareil (Android, par ex. SM-A556B) avec son nom commercial issu de la liste d'appareils Google Play (device_name, par ex. Samsung Galaxy A55 5G) et la version exacte de la plateforme ; navigateurs basés sur Chromium uniquement — Business et au-delà |
environment.virtual_machine, environment.hypervisor | Présent uniquement lorsque la carte graphique s'est déclarée virtuelle ; hypervisor est un dictionnaire fermé (vmware, virtualbox, parallels, qemu, hyperv, bochs, intel-gvt, vgpu). Un bloc absent signifie qu'il n'existe aucun indice de ce type — Business et au-delà |
extensions liste les extensions du navigateur détectées pendant la visite — Pro et au-delà.
Chaque entrée est un objet :
| Champ | Signification |
|---|---|
slug | Identifiant stable et lisible par machine de l'extension, la même valeur que celle livrée par le webhook |
name | Nom lisible par un humain |
category | Classe générale — adblock, privacy, automation, wallet, vpn, devtools, other, et ainsi de suite |
risky | true pour les extensions associées à l'automatisation, à la falsification ou au vol d'identifiants |
store_url | Lien vers la fiche de l'extension sur le store, lorsqu'il est connu |
Un constat n'est rapporté qu'après avoir passé nos contrôles de fiabilité — un environnement qui répond « installée » à chaque sonde, ou un lot de plus de douze noms, est écarté comme non fiable. Une liste vide ou absente signifie donc « rien que nous ayons pu confirmer », et non « aucune extension installée ». Lisez-la comme un indice, pas comme un inventaire.
fields contient les paires clé-valeur personnalisées que votre intégration a rattachées à la
visite : une référence de commande, un nom d'offre, l'issue d'un paiement. Les clés et les
valeurs sont toujours des chaînes : un nombre ou un booléen rattaché revient sous forme de
texte. Comme le reste de la session, il est disponible sur toute offre avec accès à l'API (Pro
et au-delà) et il est absent lorsque la visite ne porte aucun champ.
fields est lu en direct à chaque requête, et non copié au moment de la visite. Les champs
rattachés après la visite (voir Rattacher des champs après la visite)
apparaissent aussi ici : pour une même visite, fields peut donc contenir davantage que ce que
le webhook a livré.
Ne placez pas de données personnelles, comme des noms, des adresses e-mail ou des numéros de téléphone, dans les champs personnalisés : rattachez plutôt une référence que vos propres systèmes savent résoudre, et transmettez l'identité du compte sous forme de fiche de compte (ci-dessous).
account est votre fiche pour le compte qui se trouve derrière la visite — ce que vous avez
envoyé en dernier pour cet account_id via POST /v2/accounts/metadata :
{ "account": { "id": "user-42", "email": "ann@example.com", "display_name": "Ann Lee" }}| Champ | Description |
|---|---|
id | Votre identifiant de compte — la même valeur que account_id |
email | L'adresse e-mail que vous avez envoyée pour le compte ; absente si vide |
display_name | Le nom d'affichage que vous avez envoyé pour le compte ; absent si vide |
La fiche est lue en direct à chaque requête : un changement de nom apparaît donc aussi sur les anciennes visites — le bloc décrit le compte tel qu'il est maintenant, et non tel qu'il était au moment de la visite. Elle est disponible sur toute offre avec accès à l'API (Pro et au-delà). Les labels envoyés avec la fiche ne sont pas renvoyés.
Le bloc est absent lorsque la visite n'a aucun compte lié, lorsqu'il n'existe aucune fiche pour ce compte (ou que vous l'avez effacée en envoyant des valeurs vides) et — rarement — lorsque la fiche n'a pas pu être lue : la session est malgré tout renvoyée en entier au lieu de faire échouer la requête. Traitez donc un bloc absent comme « inconnu », et non comme « ce compte n'a pas d'e-mail ». Les webhooks ne portent pas ce bloc.
La plus récente :
curl -H "Authorization: Bearer tracio_sk_XXXX...XXXX" \ "https://api.tracio.ai/v2/visitors/X7fh2Hg9LkMn3pQr5tBvQw3xZa9mK2pL4nR8dT6y/sessions/latest"Cela renvoie un objet session nu — pas un tableau, et sans enveloppe autour. Un
visiteur sans aucune session dans la fenêtre renvoie 404 not_found avec
no sessions for this visitor in the retention window.
Ou bien par request_id, l'identifiant qui apparaît aussi dans le payload du webhook :
curl -H "Authorization: Bearer tracio_sk_XXXX...XXXX" \ "https://api.tracio.ai/v2/sessions/9c1f6a2e-3b7d-4c58-a1e2-6f0d8b4a7c31?visitor_id=X7fh2Hg9LkMn3pQr5tBvQw3xZa9mK2pL4nR8dT6y"visitor_id est optionnel ici, mais le passer quand vous le connaissez rend la
recherche nettement plus rapide.
identification.drift_score — le score de comportement lissé sur lequel le verdict de prise
de contrôle de compte a été rendu, Business et au-delà — n'est renvoyé que par cette lecture
d'une session unique. La dérive de comportement est mesurée pendant la visite, après
l'identification initiale : les routes de liste et de session la plus récente l'omettent donc.
Une visite dont le comportement n'a jamais été évalué n'a pas de drift_score. La même valeur
vous parvient via le webhook account_takeover.
Une visite peut être livrée plusieurs fois par webhook : d'abord primary, puis late ou
correction à mesure qu'arrivent des signaux plus lents et des indices comportementaux — et
une phase ultérieure peut renverser le verdict précédent. La Data API répond à partir de
l'enregistrement primary. Les phases ultérieures n'y sont pas fusionnées : pour une visite
corrigée, le dernier webhook est donc la réponse la plus récente. Si vous agissez quelques
minutes après la visite, préférez la phase reçue en dernier à une nouvelle lecture ici.
Une chose fonctionne en sens inverse : dès que le détecteur de prise de contrôle de compte
s'est déclenché pour une visite, guidance le reflète ici, alors que les corps de webhook
identification de la même visite ne portent pas cet axe — il passe par la livraison dédiée
account_takeover.
Velocity répond à la question « quelle a été l'activité récente de ce visiteur » — la forme que prennent le credential stuffing, le test de cartes et les inscriptions en masse.
curl -H "Authorization: Bearer tracio_sk_XXXX...XXXX" \ "https://api.tracio.ai/v2/visitors/X7fh2Hg9LkMn3pQr5tBvQw3xZa9mK2pL4nR8dT6y/velocity?window=1h"window accepte 1h, 24h ou 7d et vaut 24h par défaut. Toute autre valeur est
rejetée avec 400 invalid_request et window must be one of: 1h, 24h, 7d.
{ "window": "1h", "events": 37, "unique_ips": 9, "unique_countries": 3, "unique_accounts": 12, "bot_events": 4, "meta": { "plan": "business", "retention_days": 90, "from": "2026-07-25T11:00:00Z", "to": "2026-07-25T12:00:00Z" }}unique_accounts compte les valeurs linkedId distinctes que vous avez envoyées pour
cet appareil — voir Liaison de comptes. bot_events est
réservé à Business et au-delà ; la clé est absente sur Pro.
TRACIO ne connaît vos comptes que par l'identifiant que votre site transmet comme linkedId —
une chaîne opaque comme usr_8412. Envoyez une fois les libellés lisibles, et chaque écran du
tableau de bord qui affiche ce compte montre le nom ou l'e-mail au lieu de l'identifiant brut ;
l'objet session les renvoie aussi dans son bloc account. Rien ici
n'influe sur la détection ni sur le scoring : c'est une couche d'affichage.
curl -s -X POST "https://api.tracio.ai/v2/accounts/metadata" \ -H "Authorization: Bearer tracio_sk_XXXX...XXXX" \ -H "Content-Type: application/json" \ -d '{"accounts":[ {"account_id":"usr_8412","email":"ann@example.com","display_name":"Ann Lee","labels":{"tier":"vip"}}, {"account_id":"usr_9130","display_name":"Bob Ray"} ]}'{ "updated": 2 }fields. Un e-mail
ou un nom envoyé ici ne vit que dans la fiche de compte de votre espace de travail : il n'est
pas stocké par visite, pas écrit dans la page, et un seul envoi met à jour chaque visite de
ce compte.400 invalid_request, et une seule entrée
invalide fait rejeter tout le lot — l'erreur indique sa position. Les identifiants de
compte en double dans un même lot ne posent pas de problème : le dernier l'emporte.Limites : 1 000 comptes par requête, corps de 1 MiB, account_id ≤ 256 caractères,
email ≤ 320, display_name ≤ 256, au plus 16 labels avec des clés ≤ 64 et des valeurs
≤ 256 caractères.
Le navigateur ne peut envoyer que ce que la page connaît au moment du rendu — un identifiant de commande, jamais l'issue d'un contrôle de paiement ni rien d'autre qui se résout plus tard. Cette route comble ce manque : retrouvez la visite par un champ qu'elle porte déjà, et rattachez-lui d'autres champs côté serveur.
curl -s -X POST "https://api.tracio.ai/v2/events/fields" \ -H "Authorization: Bearer tracio_sk_XXXX...XXXX" \ -H "Content-Type: application/json" \ -d '{"match":{"key":"order_ref","value":"A-1029"},"fields":{"amount":"120.00"}}'{ "updated": 1 }match retrouve la visite par un champ qu'elle possède déjà, et non par request_id,
dans l'espace de travail et l'environnement de votre clé.404 not_found — écrivez le champ peu après que l'issue est connue.updated compte les visites distinctes. Il dépasse 1 uniquement lorsque votre match
touche plus d'une visite — une paire clé/valeur qui n'est pas propre à une seule commande ou
à un seul panier. Un match qui touche plus de 100 événements est refusé avec
400 invalid_request plutôt qu'appliqué à un sous-ensemble arbitraire.server, et
priment sur une valeur de même clé écrite par le navigateur.404 not_found, et non un 200 vide.Les limites sont celles du canal navigateur : 50 champs par appel, clés ≤ 40 octets, valeurs
≤ 500 octets — des octets, pas des caractères — jeu de caractères des clés A-Za-z0-9_.:-,
corps de 1 MiB.
Efface des champs personnalisés — jamais la visite elle-même. Utilisez-la pour des valeurs que vous n'avez plus besoin de garder rattachées à une visite, comme le montant d'une commande. Les webhooks déjà envoyés et les exports déjà réalisés ne sont pas modifiés.
# by request_id — clears every field on this one visitcurl -s -X POST "https://api.tracio.ai/v2/events/fields/delete" \ -H "Authorization: Bearer tracio_sk_XXXX...XXXX" \ -H "Content-Type: application/json" \ -d '{"request_id":"9c1f6a2e-3b7d-4c58-a1e2-6f0d8b4a7c31"}'
# by match — clears "order_ref" from every visit currently holding "A-1029"curl -s -X POST "https://api.tracio.ai/v2/events/fields/delete" \ -H "Authorization: Bearer tracio_sk_XXXX...XXXX" \ -H "Content-Type: application/json" \ -d '{"match":{"key":"order_ref","value":"A-1029"}}'{ "deleted": 1 }request_id et match s'excluent mutuellement — envoyer les deux, ou aucun, donne
400 invalid_request. match utilise la même fenêtre de 24 heures et le même plafond de
100 événements que le rattachement.deleted compte les champs retirés de la visite (par request_id) ou les visites dont le
champ a été retiré (par match).404 not_found.Les mêmes listes que vos opérateurs gèrent dans le tableau de bord, accessibles depuis votre
propre back-office. Le type d'une liste est son action sur une visite correspondante —
block, manual_review, challenge, allow — et son mode décide si l'action est
appliquée : observe ne fait que compter les correspondances, enforce l'applique à
guidance (l'axe list). Les listes et leur mode se créent dans le tableau de bord ; la Data
API lit les listes et ajoute ou retire des entrées.
curl -s "https://api.tracio.ai/v2/lists" \ -H "Authorization: Bearer tracio_sk_XXXX...XXXX"[ { "id": "7f3a9d2e-4c1b-4e8f-9a6d-1b2c3d4e5f60", "name": "Chargeback accounts", "type": "block", "mode": "enforce", "entries_count": 128 }]# ban an account for 30 dayscurl -s -X POST "https://api.tracio.ai/v2/lists/7f3a9d2e-4c1b-4e8f-9a6d-1b2c3d4e5f60/entries" \ -H "Authorization: Bearer tracio_sk_XXXX...XXXX" \ -H "Content-Type: application/json" \ -d '{"value":"usr_8412","value_type":"linked_id","ttl_minutes":43200}'{ "id": "02b4c8d1-6e7f-4a90-b1c2-d3e4f5a6b7c8", "value": "usr_8412", "value_type": "linked_id", "expires_at": "2026-10-19T12:00:00Z", "propagation": "immediate"}Retirez une entrée avec le même corps via POST /v2/lists/{list_id}/entries/delete ; la
réponse est { "removed": true, "propagation": "immediate" }.
value_type indique avec quoi la valeur est comparée : linked_id, visitor_id,
device_id, ip, cidr ou country. Une valeur qui ne s'analyse pas pour son type donne
400 invalid_request.linked_id →
visitor_id → device_id → ip → cidr → country — et, à l'intérieur de celui-ci,
l'action la plus stricte l'emporte.ttl_minutes est facultatif. S'il est omis, le TTL par défaut de la liste s'applique ;
sans TTL par défaut, l'entrée n'expire jamais (expires_at vaut null). Ajouter de nouveau
une entrée la renouvelle.propagation indique quand le changement est effectif : immediate, ou ttl — en
cinq minutes environ.200 avec removed: false ; 404 est réservé à une
liste inconnue.Un champ absent signifie « pas de donnée », jamais zéro. Les champs sans valeur
sont entièrement omis plutôt qu'envoyés à 0, "" ou null : un visiteur tout
nouveau n'a pas de match_confidence, une visite propre n'a ni antidetect_score ni
suspect_score. La seule exception délibérée est bot.score, toujours présent même
lorsqu'il vaut zéro. Lisez les champs de manière défensive.
Le payload dépend de votre offre. L'accès à l'API commence à Pro, et Pro reçoit la session
de base — identifiants, horodatage, URL, IP, user agent, navigateur, OS, appareil, geo,
network, bot, identification et decision — avec identification.match_type,
identification.match_confidence, bot.score, bot.type, bot.antidetect_score, gpu,
network.proxy_detected, extensions, fields et account. Business et Enterprise ajoutent
geo.isp, network.asn, network.real_ip, decision.suspect_score,
identification.drift_score, reasons, behavior, guidance, device_info, spoofing,
os_environment, screen, locale, client_hints et environment. Les champs au niveau de
la personne (person_id, reputation, linked_accounts_count, linked_visitors_count) sont
réservés à Business et Enterprise et apparaîtront une fois la couche personne activée —
aujourd'hui elle fonctionne en mode observation et ces champs ne sont pas livrés. L'absence
d'un champ Business sur une offre Pro n'est pas une erreur.
Les éléments internes au niveau des signaux ne sont jamais renvoyés, quelle que soit l'offre : les noms des signaux individuels, leurs poids, les seuils derrière un verdict, les valeurs brutes des signaux et la décomposition des scores restent de notre côté. Un score que l'on peut rétro-analyser jusqu'à ses entrées cesse d'être utile comme défense.
Tout échec utilise une seule et même enveloppe :
{ "error": { "code": "unauthorized", "message": "missing Authorization: Bearer <secret key>", "request_id": "8f14e45fceea167a5a36dedd" }}Ce request_id n'est pas l'identifiant de la visite. Deux valeurs différentes
portent le même nom : dans un payload de session, request_id est l'UUID de la visite,
celui-là même que livre le webhook ; dans une enveloppe d'erreur, c'est un identifiant
de trace de 24 caractères, créé pour chaque appel HTTP. Cet identifiant de trace
revient aussi dans l'en-tête X-Request-Id de chaque réponse, réussie ou non.
Joignez-le lorsque vous contactez le support — c'est ainsi que nous retrouvons votre
appel exact.
| HTTP | code | Signification |
|---|---|---|
| 400 | invalid_request | Un paramètre est absent ou mal formé |
| 401 | unauthorized | La clé est absente, invalide, révoquée ou expirée |
| 402 | upgrade_required | Votre offre n'inclut pas l'accès API |
| 404 | not_found | Rien ne correspond dans la fenêtre de rétention, ou cela relève d'un autre environnement |
| 405 | method_not_allowed | La route existe, mais pas pour cette méthode |
| 429 | rate_limited | Requêtes par seconde, ou quota quotidien, dépassés |
| 500 | internal | Quelque chose a échoué de notre côté |
| 503 | unavailable | Un magasin de données est temporairement injoignable |
Les contrôles s'exécutent dans un ordre fixe — la clé, puis l'offre, puis les limites
— de sorte qu'une requête avec une mauvaise clé signale toujours la clé en premier,
jamais un problème de quota. Les données d'un autre espace de travail sont
indiscernables de données inexistantes : elles donnent 404, et non 403.
Deux cas de 401 se lisent différemment à dessein : missing Authorization: Bearer <secret key>
signifie que l'en-tête n'est jamais arrivé, tandis que invalid or revoked API key
signifie qu'il est arrivé et n'a pas correspondu. 402 porte
Data API requires the Pro plan or higher.
Relancez 429 et 503 avec une pause croissante ; relancer les autres 4xx est inutile —
elles signifient que la requête elle-même doit être corrigée.
Les réponses pour lesquelles la clé a été reconnue portent votre situation du moment (les
réponses 401 et 402 ne la portent pas, et Enterprise, sans quota quotidien, ne reçoit pas
d'en-têtes de quota) :
| En-tête | Signification |
|---|---|
X-RateLimit-Limit | Votre quota quotidien ; lors d'un refus par seconde, la limite par seconde |
X-RateLimit-Remaining | Appels restants aujourd'hui |
X-RateLimit-Reset | Heure Unix de la réinitialisation — minuit UTC |
Retry-After | Secondes à attendre, envoyé uniquement avec un 429 — fiez-vous-y : il est juste pour les deux limites |
| Offre | Requêtes par seconde | Requêtes par jour | Profondeur d'historique |
|---|---|---|---|
| Free | Pas d'accès API | — | 7 jours |
| Pro | 10 | 10 000 | 30 jours |
| Business | 50 | 100 000 | 90 jours |
| Enterprise | 200 | Non plafonné | 365 jours |