API Tester Set your auth token to use "Try it" buttons

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.

GET /api/v1/banners Admin — list banners for the tenant
POST /api/v1/banners Admin — create a banner
Request body:
{
  "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"
  }
}
PATCH /api/v1/banners/:id Admin — update (any subset of fields)
DELETE /api/v1/banners/:id Admin — delete
GET /api/v1/banners/templates Admin — list built-in + user-saved templates
POST /api/v1/banners/:id/apply-template Admin — merge a template's look & feel into the banner
{ "key": "bold" }                   // or "minimal" | "playful" | "akeeli" | "fullscreen"
POST /api/v1/banners/:id/save-as-template Admin — save the current banner state as a reusable template
GET /api/v1/public/banner/:tenant_slug/:public_key.json Public — called by /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.

GET /api/v1/ltv/cohort?days=90&dim=campaign Auth — cohort LTV grouped by campaign, source, or medium
Response:
{
  "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
    }
  ]
}
GET /api/v1/ltv/roas?days=30&dim=campaign Auth — revenue ÷ ad spend per dimension
POST /api/v1/ltv/ad-spend Admin — upload one daily spend row (UPSERT on date+network+campaign+source)
{
  "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).

POST /api/v1/referrals/code Auth — generate a referral code for a user
{ "referrer_id": "user_42", "metadata": { "tier": "gold" } }
Response (201):
{ "id": "7", "code": "aB3dE9x", "link": "https://.../r/aB3dE9x" }
GET /api/v1/referrals/code/:code Public — look up a code, returns referrer_id + claims_count
POST /api/v1/referrals/claim Auth — mark a referral as claimed on conversion (rate-limited)
{ "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).

POST /api/v1/workflows Admin — create
{
  "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).

GET /api/v1/workflows Admin — list
POST /api/v1/workflows/:id/test Admin — evaluate against a sample payload (dry-run, no side-effects)

Audience Segments

Reusable filters over clicks/events — for targeting webhooks, scheduled reports, or exports.

POST /api/v1/segments Admin — create a saved filter
{
  "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
  }
}
GET /api/v1/segments/:id/preview Admin — returns count + 50 sample fingerprints

Scheduled Reports

Daily / weekly email digests. Runner is distributed-safe (UPDATE … RETURNING atomic claim).

POST /api/v1/scheduled-reports Admin — schedule a digest
{
  "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"]
}
POST /api/v1/scheduled-reports/:id/run-now Admin — trigger immediate send (test)

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):

NetworkColumnsEndpoint 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é

Comportement

Endpoints recommandés

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.

Tools — migration & AI

POST /api/v1/tools/fdl-import Admin — bulk-import Firebase Dynamic Links

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.

Response:
{ "imported": 42, "skipped": 3, "errors": 0, "created": [] }
POST /api/v1/tools/suggest Auth — AI-suggested copy (Claude Haiku 4.5)

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