API — Platform
All the endpoints shipped in waves 2 through 5: smart banners, LTV & ROAS, referral links, workflow engine, audience segments, ad-network Conversions APIs, SCIM provisioning, and migration tools.
Smart Banners
Embeddable JavaScript banners with three variants (bar,
interstitial, floating), positions, A/B groups
with weighted pick, and a reusable templates library (5 built-in + unlimited
user-saved presets).
The embed snippet uses data-banner-id=<public_key> (an
unguessable token, never the admin numeric id) or data-banner-group=<name>
to pick an A/B variant.
{
"name": "Homepage bar",
"variant": "bar", // bar | interstitial | floating
"position": "top", // top | bottom | bottom-right | bottom-left | top-right | top-left
"link_slug": "summer-sale",
"text": "Continue in our app",
"button_text": "Open",
"icon_url": "https://…/icon.png",
"image_url": "https://…/hero.jpg",
"accent_color": "#6366f1",
"ab_group": "homepage-test",
"ab_weight": 100,
"layout": {
"border_radius": 12, "padding_v": 14, "padding_h": 18,
"font_size": 14, "font_weight": 600,
"background": "#ffffff", "text_color": "#1a1a1d",
"shadow": true, "animation": "slide"
}
}
{ "key": "bold" } // or "minimal" | "playful" | "akeeli" | "fullscreen"
/banner.js at render time
Pass group:onboarding instead of a public_key to fetch a random
A/B variant from the group, weighted by ab_weight.
LTV & ROAS
Per-cohort lifetime value (D1/D7/D30/D90 cumulative revenue) + return on ad spend.
Feed ad spend via POST /ltv/ad-spend — or through the Node / Python / Go SDKs.
campaign, source, or medium
{
"period_days": 90,
"dimension": "campaign",
"cohorts": [
{
"cohort_date": "2026-03-01",
"dim_value": "spring-launch",
"users": 1243,
"rev_d1": 450,
"rev_d7": 2100,
"rev_d30": 8900,
"rev_d90": 15200,
"ltv_d30": 7.16
}
]
}
{
"date": "2026-04-14",
"network": "facebook",
"campaign": "spring-launch",
"spend": 325.50,
"currency": "EUR"
}
Referrals
Generate unique codes tied to a referrer, track claims on conversion.
/claim is rate-limited to 20 req/min/IP (fraud protection).
{ "referrer_id": "user_42", "metadata": { "tier": "gold" } }
Response (201):
{ "id": "7", "code": "aB3dE9x", "link": "https://.../r/aB3dE9x" }
{ "code": "aB3dE9x", "referred_id": "new_user_99", "fingerprint": "sha256-hex" }
Workflows (trigger engine)
"If this then that" automations. Each workflow fires on a specific trigger_event
when its conditions match the event payload, then runs an ordered list of
actions. Global 10 s timeout, depth capped at 3
(protection against workflow → webhook → event → workflow loops).
{
"name": "Alert Slack on iOS France purchases",
"trigger_event": "event.purchase",
"conditions": {
"platform": "ios",
"country": { "$in": ["FR", "BE", "CH"] },
"revenue": { "$gt": 50 }
},
"actions": [
{ "type": "slack", "message": "\ud83d\udcb0 {{revenue}} purchase on {{link_slug}}" },
{ "type": "tag_link", "slug": "{{link_slug}}", "tag": "vip-hit" }
]
}
Condition operators: equality ("platform": "ios"), $in,
$gt, $lt, $ne, $regex (regex source ≤ 200 chars,
nested quantifiers rejected to prevent ReDoS, input capped at 4 KB).
Action types: webhook (url, headers?, body?),
slack (message, supports {{payload.path}} templates),
tag_link (slug, tag).
Audience Segments
Reusable filters over clicks/events — for targeting webhooks, scheduled reports, or exports.
{
"name": "iOS FR high-intent",
"description": "Users who resolved at least once from France on iOS",
"filter": {
"country": "FR",
"platform": "ios",
"source_type": "resolve",
"in_last_days": 30
}
}
Scheduled Reports
Daily / weekly email digests. Runner is distributed-safe (UPDATE … RETURNING atomic claim).
{
"name": "Daily ops digest",
"report_type": "daily_digest", // or "weekly_digest"
"schedule_cron": "0 9 * * *", // only hour+minute interpreted — daily/weekly derived from type
"recipients": ["ops@acme.com", "growth@acme.com"]
}
Ad-Network Conversions APIs
Every event dispatched via POST /api/v1/events is automatically
forwarded to Meta / Google / TikTok if their credentials are configured on
the tenant. PII (email, phone) is hashed server-side
per platform requirements.
Credentials are stored in tenant columns and encrypted at rest
(AES-256-GCM via src/crypto.js):
| Network | Columns | Endpoint hit |
|---|---|---|
| Meta | fb_pixel_id, fb_access_token |
graph.facebook.com/v19.0/:pixel/events |
| Google Ads | google_ads_customer_id, google_ads_conv_action, google_ads_developer_token, google_ads_sa_key |
googleads.googleapis.com/v17/customers/:id:uploadClickConversions |
| TikTok | tiktok_pixel_id, tiktok_access_token |
business-api.tiktok.com/open_api/v1.3/event/track/ |
Configure via PATCH /api/v1/tenants/current or the dashboard:
Settings → Integrations. Responses redact secrets — only flags
fb_access_token_set, google_ads_sa_key_set,
tiktok_access_token_set are returned.
For click-to-conversion attribution, the SDK captures fbclid /
gclid / ttclid from inbound URLs and adds them as
click_id on every trackEvent(). The server validates
the format (fbclid|gclid|ttclid:<value>, ≤200 chars)
before forwarding.
Analytics destinations (Segment / Amplitude / Mixpanel)
Same fan-out model: every event is forwarded. Credentials set via
segment_write_key, amplitude_api_key,
mixpanel_project_token tenant columns — or through the dashboard
(Settings → Integrations).
Idempotency
Pour éviter les doublons sur retry réseau (timeout, 503, déconnexion), envoyez
le header Idempotency-Key: <UUID> sur les requêtes
POST / PUT / PATCH sensibles. Le serveur met en cache la
réponse 24h ; toute requête ultérieure avec la même clé renvoie la réponse
cached sans ré-exécuter le handler.
Format de la clé
- String, ≤255 caractères, idéalement UUID v4.
- Unique par opération côté client (générée juste avant l'envoi).
- Propre au tenant si vous opérez plusieurs tenants.
Comportement
- Méthodes éligibles : POST, PUT, PATCH. GET/DELETE ignorent le header.
- TTL cache : 24h glissantes. Au-delà, la clé peut être réutilisée.
- Cache hit : header
X-Idempotency-Replayed: true+ même status/body que la première réponse. - Capacité : 20 000 entrées par instance Cloud Run (eviction LRU via
TtlCache). - Application : middleware globalement appliqué (
server.js:84viasrc/middleware/idempotency.js) — actif sur toutes les routes éligibles.
Endpoints recommandés
POST /api/v1/links— création de lien (évite doublons sur retry CI/CD).POST /api/v1/links/bulk— import bulk (cf. DOC-DEV-08).POST /events/track— tracking SDK (déjà dédupliqué viaevent_id, mais Idempotency-Key reste utile pour les opérations admin).POST /api/v1/referrals/claim— réclamation parrainage (idempotence renforce l'anti-fraude).POST /api/v1/tools/fdl-import— migration FDL (gros payload, retry probable).
Exemple cURL
# Génère une UUID v4
KEY=$(uuidgen)
curl -X POST https://api.deeplink.example.com/api/v1/links \
-H "Authorization: Bearer dlk_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $KEY" \
-d '{"slug":"promo-q4","web_url":"https://shop.example.com/promo"}'
# Si la requête timeout, retry avec la MÊME clé :
curl -X POST https://api.deeplink.example.com/api/v1/links \
-H "Authorization: Bearer dlk_..." \
-H "Idempotency-Key: $KEY" \
...
# → réponse identique, header X-Idempotency-Replayed: true
Exemple Node
import { randomUUID } from 'node:crypto';
async function createLinkIdempotent(payload) {
const key = randomUUID(); // 1 UUID par tentative LOGIQUE, pas par retry
return fetch('/api/v1/links', {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': key,
},
body: JSON.stringify(payload),
});
}
Limites — multi-instance
⚠️ Cache in-memory par instance Cloud Run. Avec plusieurs instances, deux requêtes parallèles avec la même clé sur des instances différentes ne se voient pas → exécutent le handler deux fois. PROD-002 (Redis wiring) résout ce problème en mode strict.
Mitigation v1 : Idempotency-Key reste utile pour
retry séquentiel (le cas le plus fréquent) ; pour les workloads
parallèles, ajouter une contrainte UNIQUE applicative côté DB
(ex. events.event_id SDK déjà UNIQUE).
SCIM v2 — auto-provisioning
Mount path: /scim/v2. Bearer auth via a tenant-scoped API key
(dlk_*). Rate-limited to 100 req/min/tenant.
Compatible with Okta, Microsoft Entra, Google Workspace.
GET /scim/v2/Users?filter=userName+eq+"alice@acme.com"&startIndex=1&count=100GET /scim/v2/Users/:idPOST /scim/v2/UsersPATCH /scim/v2/Users/:id— supports op=replace on active / name / userNamePUT /scim/v2/Users/:idDELETE /scim/v2/Users/:idGET /scim/v2/ServiceProviderConfig·/ResourceTypes·/Schemas
Tools — migration & AI
Accepts the raw FDL export JSON ({shortLinks: [...]}) or a flat
array. Maps shortLink/longDynamicLink/iosInfo/
androidInfo/socialMetaTagInfo → DeepLink fields. Imported
links are auto-tagged fdl-imported.
{ "imported": 42, "skipped": 3, "errors": 0, "created": […] }
Requires ANTHROPIC_API_KEY env var. Returns a JSON object with
title, og_title, og_description, tags.
20 s timeout, 504 on overrun.
{
"web_url": "https://shop.acme.com/spring-sale",
"title": "Spring sale",
"description": "30% off everything",
"goal": "conversion to app"
}
Playground & CLI
- /playground.html — zero-auth sandbox to exercise
/resolve,/match,/eventsagainst your deployment. - Postman collection — 20+ endpoints, pre-configured variables.
- Go SDK at docs/sdks.html · Node & Python server SDKs each gained
createReferralCode(),uploadAdSpend(),listBanners(), LTV helpers. - CLI:
cli/deeplink.js—deeplink links list,links create --url … --tags a,b,banners list,whoami,links export --format csv.