Référence de l'API

API REST
Construisez sur vos liens.

Authentifiez-vous avec un jeton API, listez et créez des liens, lisez les visites et abonnez-vous aux webhooks.

URL de base https://letl.ink/api/v1 HTTPS uniquement

Offres Growth Ultimate
Sur cette page

Premiers pas

L'API REST est disponible avec Growth et Ultimate. Les requêtes sont envoyées à https://letl.ink/api/v1 en HTTPS. Un espace de travail sur Free ou Solo reçoit 403 plan_required de la part de /api/v1 ; l'endpoint MCP est réservé séparément à Ultimate.

  1. Ouvrez Intégrations → API REST (réservé aux propriétaires et admins de l'espace de travail).
  2. Générez un jeton. Il n'expire pas.
  3. Conservez-le comme un mot de passe. Un seul jeton sert à l'API REST, au serveur MCP et à toutes les plateformes d'automatisation connectées à cet espace de travail : le régénérer les invalide donc tous à la fois.

Les membres peuvent copier un jeton existant s'il a été généré ; seuls les propriétaires et admins peuvent le renouveler.

Authentification

Envoyez le jeton en tant que X-API-KEY ou en tant que Authorization: Bearer. Les deux sont acceptés sur /api/v1 et sur /mcp. Une requête sans aucun jeton renvoie 401 unauthenticated.

GET Qui suis-je ?
curl -H "X-API-KEY: YOUR_TOKEN" \
  https://letl.ink/api/v1/auth/me

En-tête équivalent : Authorization: Bearer YOUR_TOKEN

Conventions

Ces règles s'appliquent à tous les endpoints ci-dessous.

Enveloppe de réponse

Chaque réponse réussie est un objet JSON avec une clé data, et une clé meta lorsqu'il y a quelque chose à préciser sur le résultat — pagination, période résolue, inclusion de la géographie. Chaque échec est un objet avec une clé error contenant un code stable, un message lisible et parfois des details. Basez votre logique sur code, jamais sur le texte du message.

Formes
// success
{ "data": { … }, "meta": { … } }

// failure
{ "error": { "code": "invalid_parameter",
             "message": "sort must be one of: created_at, clicks.",
             "details": { "parameter": "sort" } } }

Pagination

Les endpoints de liste acceptent limit (1–200, 50 par défaut) et offset. Le bloc meta indique total, has_more et next_offset, pour paginer sans calcul : continuez d'appeler avec offset = meta.next_offset jusqu'à ce que has_more soit false.

Parcourir tous les liens page par page
offset=0
while [ "$offset" != "null" ]; do
  page=$(curl -s -H "X-API-KEY: $TOKEN" \
    "https://letl.ink/api/v1/links?limit=200&offset=$offset")
  echo "$page" | jq -c '.data[]'
  offset=$(echo "$page" | jq -r '.meta.next_offset')
done

Tri, filtrage et recherche

GET /links accepte sort (created_at, clicks, short_code, title, updated_at), direction (asc/desc), search (recherche dans le code court, le titre et la destination), type, archived, favorite et domain. Les valeurs inconnues sont rejetées avec invalid_parameter et la liste des valeurs autorisées, au lieu d'être ignorées sans avertissement.

Périodes

start_date et end_date utilisent le format YYYY-MM-DD et incluent les deux jours. La période est limitée à la durée de conservation de votre offre — demander un an d'historique avec une offre à 30 jours de conservation renvoie les 30 jours disponibles, et meta.date_range indique la période réellement utilisée.

Limites de débit

600 requêtes par minute et par compte, sur une fenêtre glissante. Chaque réponse contient X-RateLimit-Limit, X-RateLimit-Remaining et X-RateLimit-Reset (un horodatage Unix). Au-delà de la limite, la réponse est 429 rate_limited avec un en-tête Retry-After en secondes. Attendez le délai indiqué par Retry-After au lieu de réessayer immédiatement.

En-têtes présents dans chaque réponse
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 586
X-RateLimit-Reset: 1789000816

Gestion des versions

Le chemin porte la version. L'ajout d'un champ à une réponse ou d'un nouveau paramètre facultatif n'est pas un changement cassant et peut survenir à tout moment : analysez les réponses de manière défensive et ignorez les clés que vous ne reconnaissez pas. Tout ce qui modifierait ou supprimerait un champ existant sera publié sous un nouveau préfixe de version.

Endpoints

Compte

GET /me Test de connexion

Identité, espace de travail, offre et tous les droits associés à cette offre. Le moyen le plus économique de vérifier un jeton et de découvrir ce que le compte est autorisé à faire avant de le tenter.

GET cURL
curl -H "X-API-KEY: YOUR_TOKEN" \
  https://letl.ink/api/v1/me
Réponse 200
{
  "data": {
    "user": { "id": 1, "email": "[email protected]", "name": "You" },
    "workspace": { "id": 12, "name": "My Workspace", "block_bots": true },
    "plan": {
      "id": "growth",
      "name": "Growth",
      "limits": {
        "links": 200,
        "monthly_clicks": 100000,
        "custom_domains": 3,
        "qr_codes": 30,
        "retention_days": 365
      },
      "features": {
        "api": true,
        "webhooks": true,
        "integrations": true,
        "ai_access": false,
        "geo_analytics": false,
        "smart_link_types": true
      }
    },
    "base_domain": "letl.ink"
  }
}
GET /usage

La part de chaque quota consommée ce mois de facturation. Utilisez-la pour avertir un client avant qu'il n'atteigne le plafond de clics suivis — au-delà, la redirection fonctionne toujours, mais les clics ne sont plus enregistrés.

Réponse 200
{
  "data": {
    "links": { "used": 42, "limit": 200, "remaining": 158, "percent_used": 21.0 },
    "clicks_this_month": { "used": 8140, "limit": 100000, "remaining": 91860, "percent_used": 8.1 },
    "custom_domains": { "used": 1, "limit": 3, "remaining": 2, "percent_used": 33.3 },
    "qr_codes": { "used": 4, "limit": 30, "remaining": 26, "percent_used": 13.3 }
  },
  "meta": { "plan": "growth", "period": "2026-09" }
}
GET /domains

Les domaines personnalisés de l'espace de travail et l'état de leur vérification DNS. Seuls les domaines vérifiés peuvent être utilisés dans le champ domain lors de la création d'un lien.

GET /integrations

Toutes les intégrations proposées par le produit, si votre offre les inclut, si cet espace de travail les a connectées, et l'état des envois — dernier statut, dernière erreur et nombre d'échecs consécutifs. Pratique pour surveiller un ensemble d'espaces de travail.

Liens

Statistiques

GET /analytics/summary

Les chiffres clés de tout l'espace de travail : clics depuis le début et sur la période, visiteurs uniques, scans de QR code, jour de pic, et répartition par heure de la journée et par jour de la semaine. Accepte days (30 par défaut, limité à votre durée de conservation).

Réponse 200 (abrégé)
{
  "data": {
    "window_days": 30,
    "clicks": {
      "total": 24592, "window": 8140, "last_7_days": 2103,
      "daily_average": 271,
      "peak_day": { "date": "Sep 02", "clicks": 612 }
    },
    "visitors": { "unique_total": 18204, "unique_window": 6021 },
    "qr": { "scans_total": 940, "scans_window": 310 },
    "timing": {
      "by_hour": [12, 4, 2, …],
      "by_weekday": [1420, 1610, 1585, 1490, 1310, 480, 245],
      "peak_hour": { "peakHour": 14, "peakValue": 812, "label": "14:00–14:59" },
      "peak_weekday": { "peakDay": 1, "peakValue": 1610, "label": "Tuesday" }
    },
    "links": { "active": 42, "limit": 200 }
  },
  "meta": { "plan": "growth", "geo_included": true, "retention_days": 365 }
}
GET /analytics/timeseries

Nombre de clics par jour, prêt à être représenté graphiquement. Passez link_id pour le limiter à un lien, et days pour définir la période. Les jours sans trafic sont renvoyés avec la valeur zéro au lieu d'être omis : la série est donc toujours continue.

Réponse 200
{
  "data": [
    { "date": "Sep 04", "clicks": 22 },
    { "date": "Sep 05", "clicks": 11 }
  ],
  "meta": { "window_days": 7, "total": 33, "link_id": null }
}
GET /analytics/breakdown

Clics regroupés selon une dimension, avec la part du total de chaque ligne.

Paramètres de requête

  • dimension string

    channel, referrer, device, browser, os, brand, variation ou country. browser, os, brand et variation nécessitent un link_id.

    Requis
  • link_id integer

    Limiter à un lien. Omettez-le pour tout l'espace de travail.

    Facultatif
  • limit integer

    1–100. Par défaut : 10.

    Facultatif
Réponse 200
{
  "data": [
    { "label": "Search", "value": 215, "share": 33.1 },
    { "label": "Social", "value": 201, "share": 30.9 },
    { "label": "Direct / none", "value": 142, "share": 21.8 }
  ],
  "meta": { "dimension": "channel", "link_id": 49, "total": 650 }
}
GET /events Paginée

Le flux brut des visites sur tous les liens de l'espace de travail, des plus récentes aux plus anciennes. C'est le remplaçant moderne de GET /visits : mêmes données, enveloppe standard, prise en charge de limit et offset.

Réponse 200
{
  "data": [
    {
      "id": 2691,
      "link_id": 49,
      "short_code": "spring",
      "occurred_at": "2026-09-10T22:45:33+00:00",
      "ip_address": "203.0.113.7",
      "referrer": "https://twitter.com/",
      "browser": "Chrome",
      "os": "iOS",
      "device_type": "mobile",
      "device_brand": "Apple",
      "country": "US",
      "variation": "control"
    }
  ],
  "meta": { "limit": 50, "returned": 50, "geo_included": true }
}

Webhooks

GET /hooks

Endpoints de webhook enregistrés sur ce compte.

POST /hooks

Enregistrez un endpoint HTTPS pour recevoir des données JSON à chaque clic enregistré. Jusqu'à 25 par compte. Les envois en échec sont relancés automatiquement.

Corps JSON

  • target_url

    Doit commencer par https://.

    Requis
  • event

    Par défaut : link_visited, qui est actuellement le seul événement.

    Facultatif
POST cURL
curl -X POST https://letl.ink/api/v1/hooks \
  -H "X-API-KEY: YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"target_url": "https://example.com/hooks/clicks"}'
DELETE /hooks/{id}

Supprime un endpoint de webhook. POST /hooks/subscribe et DELETE /hooks/unsubscribe/{id} restent disponibles pour le flux REST Hook de Zapier.

Ancien système

GET /auth/me Ancien système

Le test de connexion d'origine : renvoie votre identifiant utilisateur, votre e-mail et l'identifiant/nom de l'espace de travail sous forme d'objet plat. GET /me renvoie la même identité ainsi que les limites de votre offre et les indicateurs de fonctionnalités, et constitue le meilleur choix pour du nouveau code.

GET cURL
curl -H "X-API-KEY: YOUR_TOKEN" \
  https://letl.ink/api/v1/auth/me
Réponse 200
{
  "id": 1,
  "email": "[email protected]",
  "workspace_id": 12,
  "workspace_name": "My Workspace"
}
GET /visits Ancien système

Les 50 visites les plus récentes de l'espace de travail, sous forme de tableau JSON brut — sans enveloppe — avec les noms de champs visit_id, visit_time, link_shortcode et visitor_*. C'est ce que lit l'étape « perform list » du REST Hook de Zapier, c'est pourquoi son format est figé. Utilisez GET /events pour tout nouveau développement.

POST /hooks/subscribe Zapier

Abonnement REST Hook. Accepte target_url et un event facultatif (link_visited par défaut) et renvoie l'abonnement créé à plat, sans enveloppe. POST /hooks est la même opération au format standard.

DELETE /hooks/unsubscribe/{id} Zapier

Désabonnement REST Hook, qui renvoie {"success": true}. DELETE /hooks/{id} est l'équivalent au format standard.

Erreurs

  • 400 invalid_request

    Le corps n'était pas un objet JSON, ou un champ obligatoire manquait.

  • 400 invalid_parameter

    Un paramètre de requête était mal formé ou hors des valeurs autorisées. details.parameter indique lequel.

  • 401 unauthenticated

    Aucun jeton n'a été envoyé. Ajoutez X-API-KEY ou Authorization: Bearer.

  • 401 invalid_token

    Ce jeton ne correspond à aucun compte. Il a peut-être été régénéré.

  • 403 plan_required

    L'offre du compte n'inclut pas cet endpoint ou cette fonctionnalité. details.upgrade_url pointe vers la facturation.

  • 403 forbidden

    Authentifié, mais non autorisé — par exemple un membre qui tente une action réservée au propriétaire.

  • 404 not_found

    Cette ressource n'existe pas dans cet espace de travail. Les ID d'un autre espace de travail sont signalés comme introuvables plutôt qu'interdits.

  • 409 conflict

    La requête entre en conflit avec l'état actuel — la limite de webhooks est atteinte, ou le compte n'a pas encore d'espace de travail.

  • 422

    Renvoyé par POST /links/bulk lorsque toutes les lignes du lot ont échoué. Consultez meta.errors.

  • 429 rate_limited

    Plus de 600 requêtes au cours de la dernière minute. Attendez le nombre de secondes indiqué par Retry-After.

Les anciens endpoints GET /visits et GET /links/{id}/visits renvoient un tableau JSON brut avec leurs propres noms de champs et sont figés pour les intégrations Zapier déjà construites dessus. Le nouveau code doit utiliser GET /events et GET /links/{id}/events, qui utilisent l'enveloppe standard et prennent en charge la pagination.

Vous avez encore des questions ?

Nous sommes là pour vous aider. Contactez-nous et nous vous répondrons dès que possible.