API HTTP
Trackily expose deux APIs distinctes : MCP (recommandé pour les agents LLM, cf. /docs/mcp) et HTTP REST classique (recommandé pour les intégrations machine-to-machine, webhooks externes, et postbacks affiliate). Cette section documente la deuxième.
Concept
L'API HTTP de Trackily a quatre familles d'endpoints :
- Admin API (
/admin/api/*) — pilote toute l'instance. Auth par cookie de session admin (HTTP-only, signé HMAC) + permission check par section. Préfère le MCP pour piloter : moins de boilerplate, scopes plus fins, audit log natif. - Public tracking (
/c/:slug,/lp/:slug,/r/:slug) — sert les redirects de tracking et les landings. C'est le hot path de l'app, optimisé pour la perf. - Postback (
/postback) — endpoint universel de réception des conversions affiliate. Cf. Postback. - Webhooks ENTRANTS (
/webhook/*) — endpoints qui RECEIVENT des events de Shopify, Stripe, PayPal, Mailgun, etc. Cf. Webhooks.
À noter : Trackily n'émet PAS de webhooks sortants (du moins, pas via un système de souscription générique). Toute la propagation d'événement vers l'extérieur passe par d'autres chemins : conversion triggers email, postbacks vers les ad networks, script tags Shopify injectés.
Auth des endpoints /admin/api/*
L'admin API est protégée par authMiddleware qui :
- Lit le cookie
tly_admin_session(signé HMAC avecSECRETS_MASTER_KEY) - Vérifie la signature + la fraîcheur (TTL configurable, par défaut 7 jours)
- Lit
users.iddepuis le cookie, charge le user row - Pour les endpoints
requirePermission(section, action), vérifie que le user a la permission (tableuser_permissionsJSONB)
Pour s'authentifier en POST initial :
POST /admin/api/login HTTP/1.1
Content-Type: application/json
{ "email": "admin@example.com", "password": "..." }
Réponse : Set-Cookie: tly_admin_session=...; HttpOnly; SameSite=Lax (Secure en HTTPS).
Les calls suivants passent le cookie automatiquement. Pour un script externe : extraire le cookie après login, le rejouer dans le header Cookie:.
Pour les agents LLM, préfère MCP qui a un système de bearer + scopes + rate limiting + audit dédié.
Clés API Trackily (tly_…)
Pour un script ou une intégration (Zapier, Make…), crée une clé durable dans Réglages → Clés API et envoie-la sur chaque appel : Authorization: Bearer tly_…. Elle porte les droits de son propriétaire (l'utilisateur à qui elle appartient — celui qui l'a créée, ou celui pour qui un admin l'a créée ; une régénération garde le même propriétaire), pas ceux de la personne qui l'a créée ou régénérée.
Une clé n'est affichée qu'une seule fois : dans la réponse de sa création, puis plus jamais.
| Route | Ce qu'elle rend |
|---|---|
POST /admin/api/api-keys { "name": "Zapier" } |
{ id, name, key_masked, …, "key": "tly_…" } — la seule fois où key (la clé entière) apparaît |
GET /admin/api/api-keys |
la liste, avec key_masked seulement (tly_0123...abcd : le début et les 4 derniers caractères) ; un admin voit toutes les clés de l'instance, avec email |
POST /admin/api/api-keys/:id/regenerate |
une nouvelle clé, rendue une fois dans key, et remplace (l'id révoqué) ; l'ancienne est supprimée dans le même geste — elle cesse de marcher tout de suite. Même nom, mêmes droits. Un non-admin ne régénère que les siennes |
PUT /admin/api/api-keys/:id/toggle · DELETE /admin/api/api-keys/:id |
désactiver / réactiver, supprimer (bornés à ses propres clés pour un non-admin) |
Clé perdue ? Elle ne se relit pas : régénère-la (une clé désactivée le reste après régénération). Une fois le nouveau format activé par l'opérateur (SECRETS_CHIFFRER_AU_REPOS=1, voir OPERATIONS.md §3), Trackily n'en garde qu'une empreinte sha256 (colonne api_keys.key = sha256$<hex>) et sa forme abrégée (key_hint, migration v139) ; l'authentification hache le jeton reçu et compare les empreintes. Avant cette activation, la clé est encore stockée en clair (pour que l'ancienne version du serveur, pendant une mise à jour ou un retour arrière, l'accepte), mais elle n'est jamais relue par l'écran ni par l'API : seule la forme abrégée sort.
Clés créées avant la v139 : elles étaient stockées en clair. Elles restent valables ; après l'activation, Trackily les réécrit hachées à leur premier usage, et la tâche de démarrage hache celles qui ne servent plus (sans rien changer pour l'intégration). D'ici là, la liste n'en montre déjà plus que la forme abrégée.
OpenClaw : sa clé (tly_oc_…) est générée par Trackily, chiffrée au repos ; GET /admin/api/openclaw/settings n'en rend que la forme abrégée (openclaw_api_key, openclaw_api_key_set), et POST /admin/api/openclaw/api-key/regenerate en crée une nouvelle, rendue une fois. Le PUT des réglages ne l'écrit plus.
Format des réponses
Convention :
// Success
{ "ok": true, ...payload }
// ou directement le payload (pas d'enveloppe)
// Erreur
{ "error": "description courte" }
// ou
{ "ok": false, "error": "..." }
Status HTTP standard :
200— succès400— payload invalide / validation échouée401— pas authentifié403— authentifié mais permission manquante404— ressource introuvable429— rate limited500— erreur serveur
Rate limiting
Limites importantes en place côté serveur :
| Endpoint | Limite |
|---|---|
POST /admin/api/login |
5 essais / 15 min / IP (loginRateLimit) |
GET /postback |
600 / minute / IP (postbackRateLimit) |
POST /api/account/magic-link |
~10 / minute / IP (pixelRateLimit) |
POST /api/lead |
~10 / minute / IP |
| Tracking pixels | ~30 / min / IP |
Le rate limiter est en mémoire process (pas Redis) — si tu tournes plusieurs instances derrière un LB, chaque process a son propre compteur.
CORS
Par défaut, /admin/api/* n'a pas de CORS (même-origin uniquement). Les endpoints publics (/api/account/*, /postback, /api/lead) ont une allowlist configurable dans Settings.
Endpoints publics utiles
GET /postback
Réception des conversions affiliate. Cf. Postback.
POST /api/lead
Soumission de form (landing). Body : email, name, et tout champ custom. Crée une leads row + (si la landing est bound à une email_list) enroll dans la liste.
POST /api/lead HTTP/1.1
Content-Type: application/x-www-form-urlencoded
email=marie@example.com&name=Marie&landing_id=42&city=Paris
Réponse : 200 { ok: true, redirect_url: "/r/..." }.
Consentement WhatsApp. Les clés wa_consent, wa_consent_version et wa_consent_langue ne suffisent pas : le consentement d'un lead n'est retenu que si le corps porte aussi wa_consent_jeton, le jeton signé que Trackily pose avec la case sur les landings qu'il sert (valable 12 heures, pour cette landing seulement). Un formulaire maison qui poste sa propre case vers /api/lead voit son lead enregistré, sans consentement. La réponse ne dit jamais si le consentement a été retenu. Une page hébergée hors de Trackily obtient son jeton (valable 1 heure) par la route ci-dessous, et ne le fait valoir que depuis l'origine déclarée de sa landing. Voir Déclencheurs → La case de consentement.
GET /api/wa/consentement
Le contexte de la case de consentement WhatsApp pour une page hébergée hors de Trackily (landing de type url). public/trackily.js l'appelle tout seul quand sa balise porte data-wa-landing-id ; on n'a normalement pas à l'appeler soi-même.
<script src="https://VOTRE-TRACKER/trackily.js" data-wa-landing-id="42" data-wa-lang="fr"></script>
<div data-trackily-wa-slot></div>
Paramètres : landing_id (obligatoire), lang (fr, en, ar ; l'extrait de l'admin le pose toujours). Sans lang, la langue enregistrée sur la landing s'applique s'il y en a une ; sinon, pas de case : une page dont la langue n’est pas servie n’a pas de case. Réponse 200 { texte, version, langue, jeton, expire_dans_s } — le jeton vaut 1 heure, pour cette landing seulement. La réponse n'est lisible (CORS) que depuis l'origine de l'URL déclarée de la landing ou sa variante www. / sans www. (même schéma, même port ; aucun autre sous-domaine, jamais pour une IP ou localhost), sans cookie. Tout autre cas (landing inconnue ou interne, sans liste WhatsApp active, case éteinte, langue non servie, autre origine) répond 404 { disponible: false }, sans dire pourquoi ; 120 requêtes par minute et par IP au plus (429). Le jeton se renvoie à POST /api/lead dans wa_consent_jeton, depuis la même origine. Voir Déclencheurs → La case de consentement.
POST /api/account/magic-link
Magic-link customer auth. Cf. Magic Link.
GET /pixel.js, GET /trackily.js, GET /cloak.js, GET /i18n.js
Static JS assets servis avec cache headers. Le pixel pixel.js capture les page views côté landing, trackily.js est la lib client pour les events custom, cloak.js est le pilote du cloaking.
GET /mcp et POST /mcp
L'endpoint MCP. Cf. /docs/mcp/concept.
Différences MCP vs Admin REST
| Aspect | MCP | Admin REST |
|---|---|---|
| Auth | Bearer token tly_ap_<...> |
Cookie session HTTP-only |
| Granularité perms | 42 scopes | 8 sections × {read, write} |
| Audit log | autopilot_audit_log (chaque call) |
logs/syslog seulement |
| Rate limiting | per-token, 2 buckets (default + ai) | per-IP global |
| Découvrabilité | tools/list retourne tout en JSON-Schema |
OpenAPI spec à parser séparément |
| Tier-2 (confirm) | natif | absent — chaque endpoint exécute direct |
| Idempotency | natif sur Tier-2 | absent (à toi de gérer côté client) |
| Compatibilité LLM | excellent | médiocre (le LLM compose à la main) |
Si tu écris un script qui doit appeler 5-10 endpoints, MCP est plus simple. Si tu écris un webhook handler côté ton infra qui doit pinguer Trackily, REST est plus naturel.
Voir aussi
- MCP — Concept — l'autre API
- Webhooks — Trackily comme récepteur de webhooks
- Postback — endpoint affiliate conversion
- « Connectée ? » —
?detail=1sur les listes de sources, réseaux et boutiques : verdict, usages, santé - Magic Link — endpoints customer auth