API Tester Set your auth token to use "Try it" buttons
🔑
All /api/v1/* routes require the header X-Api-Key: <API_SECRET> (or Authorization: Bearer <jwt>). If API_SECRET is not set in .env, auth is disabled.

Create a Link

POST /api/v1/links Create a single link
Request body:
{
  "slug":               "summer-sale",        // optional — auto-generated if absent
  "title":              "Summer Sale",
  "description":        "20% off everything", // optional
  "image_url":          "https://example.com/og.jpg", // optional, OG image
  "ios_uri_scheme":     "myapp://product/42",
  "ios_store_url":      "https://apps.apple.com/app/id123",
  "android_uri_scheme": "myapp://product/42",
  "android_store_url":  "https://play.google.com/store/apps/details?id=com.ex",
  "web_url":            "https://example.com/product/42",
  "campaign":           "summer_2025",
  "source":             "instagram",
  "medium":             "story",
  "custom_data":        { "product_id": 42, "screen": "tab" },
  "expires_at":         "2025-09-01T00:00:00Z",  // optional — ISO 8601
  "max_clicks":         1000,                   // optional — click cap
  "targeting_rules":    {
    "allow_devices":   ["ios", "android"],
    "allow_countries": ["FR", "BE"],
    "block_countries": ["CN"]
  }
}
Response (201):
{
  "id": 42,
  "slug": "summer-sale",
  "short_url": "https://links.yourapp.com/summer-sale",
  "title": "Summer Sale",
  // ... all link fields
}

At least one destination (ios_uri_scheme, android_uri_scheme, or web_url) is required.

Bulk Create

POST /api/v1/links/bulk Create up to 500 links in one call
Request body:
{
  "links": [
    { "title": "Link 1", "web_url": "https://example.com/1" },
    { "title": "Link 2", "web_url": "https://example.com/2" }
  ]
}
Response (207):
{
  "total": 100, "created": 97, "failed": 3,
  "links": [
    { "index": 0, "slug": "abc123", "short_url": "https://links.yourapp.com/abc123" }
  ],
  "errors": [
    { "index": 12, "error": "Slug already taken" }
  ]
}

Partial success is possible. Each item has its own index for correlation. Maximum 500 links per call.

Snippets — bulk create

Recommandé : ajouter Idempotency-Key sur les requêtes bulk pour rendre le retry safe (cf. idempotency).

cURL
# 100 liens depuis un fichier JSON local
curl -X POST "$BASE/api/v1/links/bulk" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  --data @bulk-payload.json
Node / JS
import { randomUUID } from 'node:crypto';

const r = await fetch(`${BASE}/api/v1/links/bulk`, {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${API_KEY}`,
    'Content-Type': 'application/json',
    'Idempotency-Key': randomUUID(),
  },
  body: JSON.stringify({ links }),  // links: array of ≤500 objects
});

const { total, created, failed, errors } = await r.json();
if (failed) console.warn('errors', errors);
Python
import requests, uuid

resp = requests.post(
    f"{BASE}/api/v1/links/bulk",
    headers={
        "Authorization": f"Bearer {API_KEY}",
        "Idempotency-Key": str(uuid.uuid4()),
    },
    json={"links": links},  # list of dicts, ≤500
    timeout=30,
)
data = resp.json()
if data["failed"]:
    for e in data["errors"]:
        print(f"link[{e['index']}]: {e['error']}")

FDL importer (Firebase Dynamic Links migration)

Endpoint dédié POST /api/v1/tools/fdl-import pour migrer les liens depuis un export Firebase Dynamic Links (FDL est deprecated par Google en 2025). Accepte un array brut ou un objet enveloppe {shortLinks: [...]} ou {links: [...]}.

# cURL — depuis un export FDL
curl -X POST "$BASE/api/v1/tools/fdl-import" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  --data @firebase-export.json

# Réponse :
# { "imported": 247, "skipped": 12, "errors": [{...}] }

L'import dédupliue par slug. Les liens FDL utilisant des paramètres analytics Firebase (utm_source, etc.) sont mappés sur links.source/medium/campaign. Cf. tools §FDL importer pour l'endpoint complet.

List Links

GET /api/v1/links Paginated list — ?page=1&limit=50&search=…
ParameterDefaultDescription
page1Page number
limit50Items per page (max 200)
searchFilter by slug, title, or campaign
active1 or 0 to filter by active status

Get a Link

GET /api/v1/links/:slug Get a single link by slug

Returns the full link object including analytics counters (total_clicks, total_installs).

Update a Link

PATCH /api/v1/links/:slug Update one or more fields

All fields are optional — only send what you want to update.

{
  "title": "Updated Title",
  "active": 0,
  "max_clicks": 5000
}

Delete a Link

DELETE /api/v1/links/:slug Delete link and all associated clicks (CASCADE)

Returns 204 No Content on success. This operation is irreversible — all analytics data for the link is also deleted.

QR Code

GET /api/v1/links/:slug/qr Generate a QR code for the link

Generates on-the-fly (no storage). The QR code encodes the short URL.

ParameterDefaultDescription
formatsvgsvg (vector) or png (raster)
size400Size in pixels (64–2048)
GET /api/v1/links/summer-sale/qr?format=svg&size=400
GET /api/v1/links/summer-sale/qr?format=png&size=800  ← for print

Response: binary image with Content-Type: image/svg+xml or image/png.

Link Controls

Expiration (expires_at)

ISO 8601 date after which the link is inactive. Returns HTTP 410 Gone. Useful for limited-time offers.

"expires_at": "2025-09-01T00:00:00Z"

Click Cap (max_clicks)

Maximum number of allowed clicks. Returns HTTP 410 Gone once the cap is reached. Ideal for limited-use promo codes.

"max_clicks": 1000

Targeting Rules

The targeting_rules field restricts link access by platform and country. Geolocation uses geoip-lite (local MaxMind database — no external network calls).

"targeting_rules": {
  "allow_devices":   ["ios", "android"],  // empty = all devices
  "allow_countries": ["FR", "BE", "CH"], // whitelist — empty = all countries
  "block_countries": ["CN", "RU"]        // blacklist
}
RuleBehavior when violated
allow_devicesHTTP 403 if device is not in the list
allow_countriesHTTP 403 if country is not in the whitelist
block_countriesHTTP 403 if country is in the blacklist

Public Redirect — Response Codes

GET /:slug Public endpoint — no auth required
CodeCause
200Redirect HTML page served
403Country or device targeting rule violated
404Slug does not exist or link is disabled
410Link expired or click cap reached

Postman collection

Une collection Postman officielle est versionnée dans le repo et servie publiquement pour démarrer rapidement avec l'API. Tous les endpoints principaux sont pré-configurés avec variables d'environnement.

Téléchargement

Run in Postman

Importez la collection dans Postman :

  1. Ouvrir Postman → Import → coller l'URL https://<votre-domaine>/postman-collection.json OU drag-drop le fichier téléchargé.
  2. Postman crée automatiquement l'environnement avec les variables prédéfinies.
  3. Configurer les variables (cf. ci-dessous) puis lancer Auth → Login pour récupérer un accessToken JWT, ou utiliser une apiKey tenant dlk_*.

Variables d'environnement

VariableDefaultDescription
baseUrlhttps://deeplink-server-...run.appURL racine de votre déploiement, sans / final. À remplacer par votre URL self-hosted.
accessToken(vide)JWT obtenu via Auth → Login. Remplit automatiquement après login si vous utilisez les Tests Postman.
apiKey(vide)Clé tenant dlk_*. Alternative au JWT pour scripts/CI.
tenantSlugmainSlug du tenant cible. Visible dans l'URL dashboard.
linkSlugdemo-linkSlug du lien utilisé dans les requêtes Get / Update / Delete.

L'auth Bearer est configurée au niveau collection avec {{accessToken}} par défaut. Pour utiliser apiKey, surcharger l'auth au niveau du dossier ou de la requête.

Tutorial — premier appel en 3 étapes

  1. Importer la collection (cf. ci-dessus). Vérifier que l'environnement DeepLink Server apparaît en haut à droite.
  2. Obtenir un token : ouvrir Auth → Login (local), ajuster le body avec votre email/password/tenantSlug, lancer la requête. Copier le accessToken de la réponse dans la variable d'environnement accessToken.
  3. Premier appel : ouvrir Links → List Links, lancer. Vous devriez voir vos liens en JSON. Si 401, le token est expiré → relancer Auth → Refresh (ou Login).
⚠️
Mise à jour — la collection est versionnée à la main avec le code (pas de génération auto OpenAPI v1, cf. DT-76). Vérifier collection.info.version et la date du fichier par rapport à votre version de serveur. Si décalage, ouvrir une issue label:doc.

Link Templates

Templates let you save preset link configurations to quickly create new links with shared settings.

GET /api/v1/templates List all templates for the tenant
POST /api/v1/templates Create a new template
Request body:
{
  "name": "iOS Campaign Template",
  "description": "Standard iOS campaign link",
  "preset": {
    "ios_uri_scheme": "myapp://campaign",
    "ios_store_url":  "https://apps.apple.com/app/id123",
    "campaign":       "ios_2025"
  }
}

The preset object contains the default values applied when creating a link from this template.

DELETE /api/v1/templates/:id Delete a template

Returns 204 No Content. Existing links created from this template are not affected.