pik.li pik.li
Version 1 · stable

API pik.li

Crée, modifie et mesure tes liens courts depuis ton propre code. Une API REST qui parle JSON à l'aller comme au retour, une clé par intégration, et chaque point d'accès documenté ici avec une requête et une vraie réponse.

Obtenir une clé API OpenAPI 3.1
URL de basehttps://pik.li/api/v1

Introduction

L'API pik.li permet à tes propres logiciels de faire ce que fait le tableau de bord : créer et modifier des liens courts, lire leurs statistiques, lister tes tags et tes domaines, et gérer les webhooks. Elle fonctionne en HTTPS et chaque chemin commence par l'URL de base indiquée plus haut.

  • Envoie le corps des requêtes en JSON avec Content-Type: application/json ; les paramètres des requêtes GET vont dans la chaîne de requête. Les réponses sont toujours en JSON, encodé en UTF-8, sauf le QR code, qui est une image.
  • La version fait partie du chemin (/api/v1) et chaque réponse porte l'en-tête X-Api-Version: 1. De nouveaux champs peuvent apparaître avec le temps dans les réponses v1 : fais en sorte que ton code ignore ceux qu'il ne connaît pas.
  • Les dates et heures sont des chaînes ISO 8601 avec leur décalage horaire (2026-09-23T10:15:42.118+02:00) ; les dates seules s'écrivent 2026-09-23.
  • Les identifiants sont des entiers. Un lien peut aussi être retrouvé à partir de son URL courte avec GET /links/lookup.

Démarrage rapide

  1. Crée une clé dans le tableau de bord (voir Créer une clé) et garde-la dans une variable d'environnement.
  2. Vérifie qu'elle fonctionne : GET /me répond avec ton compte et ton offre.
  3. Crée ton premier lien court avec POST /links.
Terminal
export PIKLI_KEY="pk_live_…"

curl https://pik.li/api/v1/me \
  -H "Authorization: Bearer $PIKLI_KEY"

curl -X POST https://pik.li/api/v1/links \
  -H "Authorization: Bearer $PIKLI_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/a/very/long/page"}'

Authentification

Chaque appel, sauf GET /ping, nécessite une clé API, envoyée dans l'en-tête Authorization sous forme de jeton Bearer.

En-tête
Authorization: Bearer pk_live_3kZ9vQeX1mT0bR7yNw2aLp4sUc8dHf6J

L'en-tête X-API-Key fonctionne aussi. Le paramètre d'URL api_key est encore accepté pour les anciens clients, mais évite-le : les adresses finissent dans les journaux et dans l'historique des navigateurs.

Les clés commencent par pk_live_, suivi de 32 caractères. Une clé voit exactement ce que voit son compte — ses liens, domaines, tags, statistiques et webhooks — et toutes les clés d'un compte peuvent faire les mêmes choses : il n'existe pas de clé en lecture seule.

Traite une clé comme un mot de passe. Garde-la sur ton serveur, jamais dans du code qui s'exécute dans un navigateur ni dans une application que tu distribues. Si une clé fuite, révoque-la et crées-en une nouvelle : ça prend dix secondes.

Créer une clé

  1. Connecte-toi et ouvre API et clés dans le tableau de bord.
  2. Donne à la clé un nom qui te rappelle où elle sert (par exemple CRM production) et clique sur Générer une clé.
  3. Copie la clé tout de suite : le tableau de bord ne l'affiche qu'une seule fois.
  4. Tu peux avoir jusqu'à 10 clés actives. Pour chacune, la page indique quand elle a servi pour la dernière fois et combien de requêtes elle a faites, et te permet de la révoquer : dès l'appel suivant, une clé révoquée reçoit 401 unauthorized.

Qui peut utiliser l'API

L'API est incluse dans les offres payantes (Premium et Business). L'offre Base ne la comprend pas, sauf si l'équipe pik.li l'active pour l'offre gratuite ou pour ton compte. Dans tous les cas, le compte doit avoir une adresse e-mail confirmée et ne doit être ni bloqué ni suspendu.

Quand l'une de ces conditions n'est pas remplie, l'API répond api_disabled, email_unconfirmed ou account_blocked.

Base

Seulement si l'équipe l'active
Requêtes par minute
60
Nouveaux liens
3 par jour
Liens par appel groupé
10
Durée de conservation des statistiques
90 jours
Webhooks
Non

Premium

API incluse
Requêtes par minute
600
Nouveaux liens
2 500 par mois
Liens par appel groupé
100
Durée de conservation des statistiques
730 jours
Webhooks
Oui

Business

API incluse
Requêtes par minute
3 000
Nouveaux liens
10 000 par mois
Liens par appel groupé
100
Durée de conservation des statistiques
1 095 jours
Webhooks
Oui

Ce sont les valeurs standard de chaque offre, lues en direct dans la configuration des tarifs. Tes propres valeurs, avec les éventuels ajustements faits par l'équipe sur ton compte, sont renvoyées par GET /limits et GET /me.

Limites de débit

Chaque clé peut faire un certain nombre de requêtes par minute, qui dépend de l'offre (voir le tableau ci-dessus). Le compteur repart de zéro au début de chaque minute.

X-RateLimit-Limit
requêtes autorisées par minute pour cette clé
X-RateLimit-Remaining
requêtes restantes dans la minute en cours
X-RateLimit-Reset
moment où le compteur repart de zéro, en temps Unix (secondes)
Retry-After
seulement sur une réponse 429 : le nombre de secondes à attendre

Au-delà de la limite, l'API répond 429 avec l'erreur rate_limited et un en-tête Retry-After. Attends le temps indiqué, puis continue : réessayer tout de suite ne fait qu'entamer la minute suivante.

Réponse
HTTP/1.1 429 Too Many Requests
Content-Type: application/json; charset=utf-8
Retry-After: 18
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1790151360

{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests. Check the X-RateLimit-* headers and retry after the window resets.",
    "details": { "limit": 600, "period_seconds": 60, "resets_at": "2026-09-23T08:16:00Z" },
    "docs_url": "https://pik.li/help#error-rate_limited"
  },
  "request_id": "2b8e51d0-7a4c-4f1e-9d3b-5c6a0e8f7d21"
}
  • Les appels sans clé sont limités à 30 par minute et par adresse IP (GET /ping n'est pas compté).
  • En plus, chaque adresse IP a un plafond de 600 requêtes par minute vers l'ensemble de pik.li, quelle que soit l'offre.

Erreurs

Une requête qui échoue reçoit toujours une réponse de la même forme, quel que soit le point d'accès, avec un statut HTTP qui correspond au problème.

Réponse
{
  "error": {
    "code": "quota_exceeded",
    "message": "Link quota reached: 2500 links per month on your plan.",
    "details": { "limit": 2500, "used": 2500, "period": "month", "resets_at": "2026-09-30T22:00:00Z" },
    "docs_url": "https://pik.li/help#error-quota_exceeded"
  },
  "request_id": "6f1c2a9e-4b0d-4c55-9a51-8f2f0d7c1e3b"
}
code
Identifiant stable, destiné à ton code. Il ne change jamais et n'est jamais traduit.
message
Une phrase pour les humains, dans la langue de la requête (voir Langues).
details
Des données structurées quand elles sont utiles : les champs invalides, les valeurs autorisées, les chiffres d'un quota. Pas toujours présent.
docs_url
Le lien vers l'explication de ce code, sur cette page.
request_id
L'identifiant de la requête, aussi envoyé dans l'en-tête X-Request-Id. Indique-le quand tu écris au support.

Tous les points d'accès qui nécessitent une clé peuvent aussi répondre unauthorized account_blocked email_unconfirmed api_disabled rate_limited : ces codes ne sont pas répétés pour chaque point d'accès.

unauthorized401
La clé est absente, erronée ou révoquée. Vérifie l'en-tête Authorization et que la clé apparaît toujours comme active dans le tableau de bord.
account_blocked403
Le compte est bloqué, suspendu ou désactivé. L'API reste fermée tant que le compte n'est pas de nouveau actif.
email_unconfirmed403
L'adresse e-mail du compte n'a pas encore été confirmée. Suis le lien de l'e-mail de confirmation, puis réessaie.
api_disabled403
L'API n'est pas incluse dans l'offre de ce compte, ou elle a été désactivée pour lui. Voir Qui peut utiliser l'API.
forbidden403
La clé ne peut pas effectuer cette action. réservé
feature_required402
L'offre n'inclut pas la fonctionnalité dont ce point d'accès a besoin. details.feature la nomme et details.plan indique la première offre qui la propose.
quota_exceeded402
Le quota de liens du jour ou du mois est épuisé. details contient limit, used, period et resets_at, le moment où tu pourras de nouveau créer des liens.
not_found404
La ressource n'existe pas, a été supprimée ou appartient à un autre compte.
conflict409
La requête entre en conflit avec l'état actuel de la ressource. réservé
invalid422
Certaines données n'ont pas passé la validation : details.fields liste les problèmes champ par champ, details.messages sous forme de phrases toutes prêtes.
domain_not_allowed422
Ton compte ne peut pas utiliser ce domaine. GET /domains liste ceux qui sont disponibles.
bad_request400
Un paramètre obligatoire manque ; details.parameter le nomme.
invalid_json400
Le corps n'est pas un JSON valide. Vérifie les guillemets et l'en-tête Content-Type.
invalid_parameter400
Un paramètre a une valeur que le point d'accès ne connaît pas. Le message nomme le paramètre et details.allowed, s'il est présent, liste les valeurs acceptées.
bulk_empty400
Le tableau links d'un appel groupé est vide ou absent.
bulk_too_many400
Trop de liens dans un seul appel groupé. details.max est la limite de ton offre : découpe la liste en plusieurs appels.
idempotency_key_invalid400
L'en-tête Idempotency-Key dépasse 128 caractères.
rate_limited429
Trop de requêtes dans cette minute. Attends le nombre de secondes indiqué dans Retry-After, puis continue.

Une réponse 500 signifie que quelque chose a planté de notre côté et qu'il n'y a rien à corriger dans ta requête. Réessaie un peu plus tard ; si ça se reproduit, écris au support en indiquant la valeur de l'en-tête X-Request-Id.

Pagination

Les listes qui peuvent devenir longues sont découpées en pages : GET /links, GET /links/active et GET /links/:id/clicks.

  • page : la page voulue, à partir de 1.
  • per_page : le nombre d'éléments par page, de 1 à 100 (50 par défaut). limit est accepté comme synonyme.

La réponse contient un objet pagination, et le total figure aussi dans l'en-tête X-Total-Count. Continue à demander next_page jusqu'à ce qu'il vaille null.

Réponse
{
  "links": [],
  "pagination": { "page": 2, "per_page": 50, "total": 137, "total_pages": 3, "next_page": 3, "prev_page": 1 }
}

Idempotence

POST /links, POST /links/bulk et POST /links/generate acceptent un en-tête Idempotency-Key : un texte de ton choix, de 128 caractères au plus, un par opération — un numéro de commande, un UUID.

Si la même valeur revient avec la même clé API dans les 24 heures, pik.li ne crée rien : il renvoie la première réponse, avec l'en-tête Idempotent-Replayed: true. Une requête répétée après un délai d'attente dépassé ne crée jamais de doublon.

Même requête, même clé, dans les 24 heures
HTTP/1.1 201 Created
Idempotent-Replayed: true
Content-Type: application/json; charset=utf-8

Les réponses avec un statut 5xx ne sont pas mémorisées : répéter la requête avec la même valeur l'exécute donc de nouveau. Une valeur trop longue reçoit idempotency_key_invalid.

Langues

Les messages d'erreur existent en neuf langues : en it zh ar ru fr de es pt-BR. L'API choisit d'après le paramètre locale, puis l'en-tête Accept-Language, puis la langue de ton compte, et à défaut répond en anglais. Seul le message change : les codes et les noms de champs restent les mêmes, tout comme docs_url, qui ouvre cette page dans la même langue.

Terminal
curl "https://pik.li/api/v1/links?status=nope" \
  -H "Authorization: Bearer $PIKLI_KEY" \
  -H "Accept-Language: it"

{
  "error": {
    "code": "invalid_parameter",
    "message": "Valore non valido per il parametro status.",
    "details": { "allowed": ["all", "active", "disabled", "review", "expired"] },
    "docs_url": "https://pik.li/help?locale=it#error-invalid_parameter"
  },
  "request_id": "0d5c9b7e-1f2a-4e3b-8c6d-7a9e0f1b2c3d"
}

Fenêtres des statistiques

Les points d'accès de statistiques lisent une fenêtre de temps et la comparent à celle qui la précède, de même durée : c'est de là que viennent previous et delta_pct.

period
24h, 7d, 30d, 90d, 1y ou all (tout l'historique que conserve ton offre). Un nombre de jours fonctionne aussi : period=45, ou l'ancien days=45. Par défaut : 7d pour le compte, 30d pour un lien et all pour la liste des clics.
from, to
À la place de period : une date (2026-09-01) ou une heure ISO 8601. Par défaut, to vaut l'instant présent et from 30 jours avant to ; une date dans to inclut toute la journée.
interval
hour ou day, le pas de la série temporelle. Jusqu'à 48 heures, les fenêtres sont par heure par défaut, au-delà par jour ; une série horaire couvre 7 jours au maximum.
window.clamped
Une fenêtre ne remonte jamais plus loin que l'historique conservé par ton offre (retention_days). Quand elle a été raccourcie, window.clamped vaut true.
bots
Les clics des robots sont exclus. Ajoute bots=1 pour les compter aussi ; les statistiques d'un lien les indiquent en outre à part dans period.bots.

Points d'accès

Chaque point d'accès avec ses paramètres, une requête à coller dans un terminal et une vraie réponse, raccourcie quand une liste serait trop longue. Les exemples lisent la clé dans $PIKLI_KEY, comme dans le démarrage rapide.

Service

L'API répond-elle, et que reste-t-il de ton offre ?

GET /ping Sans clé

Vérifier que l'API répond

Sans clé : prévu pour les contrôles de disponibilité. Renvoie l'heure du serveur et les adresses de cette documentation et du fichier OpenAPI.

Requête
curl https://pik.li/api/v1/ping
Réponse · 200
{
  "ok": true,
  "service": "pik.li",
  "version": "v1",
  "time": "2026-09-23T08:15:42Z",
  "docs": "https://pik.li/help",
  "openapi": "https://pik.li/openapi.json"
}
Erreurs Aucune : ce point d'accès ne demande pas de clé.
GET /limits

Ce qui reste de tes quotas

Les liens encore disponibles aujourd'hui ou ce mois-ci, les clics comptabilisés, la fenêtre de limitation de débit en cours, les clés et les domaines personnalisés utilisés, et la taille maximale d'un appel groupé. Chaque valeur tient compte des ajustements que l'équipe a pu faire sur ton compte.

Requête
curl https://pik.li/api/v1/limits \
  -H "Authorization: Bearer $PIKLI_KEY"
Réponse · 200
{
  "plan": "premium",
  "plan_name": "Premium",
  "links": {
    "period": "month", "limit": 2500, "used": 412, "remaining": 2088,
    "resets_at": "2026-09-30T22:00:00Z",
    "today": 9, "this_month": 412, "per_day": 0, "per_month": 2500
  },
  "clicks": { "limit": 150000, "used": 38120, "remaining": 111880, "resets_at": "2026-09-30T22:00:00Z" },
  "rate_limit": { "limit": 600, "remaining": 597, "period_seconds": 60, "resets_at": "2026-09-23T08:16:00Z" },
  "api_keys": { "used": 2, "limit": 10 },
  "custom_domains": { "used": 1, "limit": 3 },
  "bulk": { "max_items": 100 },
  "retention_days": 730,
  "link_ttl_days": 0,
  "features": ["qr", "analytics_full", "tags", "export", "custom_slug", "password", "expiry", "utm", "bulk", "api", "webhooks"]
}
Erreurs Seulement celles que tous les points d'accès peuvent renvoyer.

Compte

Qui appelle : le compte à qui appartient la clé.

GET /me

Ton compte et la clé utilisée

Profil, offre et son échéance, fonctionnalités, limites et consommation, et la clé qui a fait l'appel, avec son compteur de requêtes.

Requête
curl https://pik.li/api/v1/me \
  -H "Authorization: Bearer $PIKLI_KEY"
Réponse · 200
{
  "id": 1042,
  "username": "anna",
  "email": "[email protected]",
  "name": "Anna Rossi",
  "status": "active",
  "confirmed": true,
  "locale": "it",
  "plan": "premium",
  "plan_name": "Premium",
  "interval": "monthly",
  "plan_expires_at": "2026-10-14T23:59:59.000+02:00",
  "api_enabled": true,
  "features": ["qr", "analytics_full", "tags", "export", "custom_slug", "password", "expiry", "utm", "bulk", "api", "webhooks"],
  "limits": {
    "links_per_day": 0, "links_per_month": 2500, "clicks_per_month": 150000,
    "retention_days": 730, "link_ttl_days": 0, "custom_domains": 3, "api_rpm": 600
  },
  "usage": {
    "links_today": 9, "links_this_month": 412, "clicks_this_month": 38120,
    "links_quota_used": 412, "links_quota_limit": 2500, "links_total": 3187
  },
  "api_key": {
    "id": 55, "name": "CRM production", "prefix": "pk_live_Xq3v",
    "created_at": "2026-09-02T16:40:10.311+02:00", "last_used_at": "2026-09-23T10:15:42.020+02:00", "requests_count": 18230
  },
  "created_at": "2026-03-11T09:12:47.664+01:00"
}
Erreurs Seulement celles que tous les points d'accès peuvent renvoyer.

Statistiques du compte

Les chiffres de tous tes liens réunis, avec les filtres du tableau de bord : fenêtre, domaine, tag, robots. Voir Fenêtres des statistiques pour les paramètres communs.

GET /stats

Les statistiques du compte en un seul appel

Les totaux du compte, la fenêtre comparée à la précédente, la série temporelle, les 10 premiers référents et pays, les 6 premiers appareils, navigateurs et systèmes d'exploitation, et les 10 liens les plus cliqués. Les autres points d'accès /stats renvoient chaque partie séparément, avec des listes plus longues.

Paramètres
  • period string chaîne de requête

    La fenêtre : 24h, 7d (par défaut), 30d, 90d, 1y, all. Voir Fenêtres des statistiques.

  • from string chaîne de requête

    Début de la fenêtre, à la place de period : une date ou une heure ISO 8601.

  • to string chaîne de requête

    Fin de la fenêtre (maintenant par défaut).

  • interval string chaîne de requête

    hour ou day, le pas de la série temporelle.

  • domain string chaîne de requête

    Seulement ce domaine, par nom d'hôte (par exemple lnkz.li). Il doit faire partie des tiens : voir GET /domains.

  • tag string chaîne de requête

    Seulement les liens qui ont ce tag.

  • bots string chaîne de requête

    1 pour compter aussi les clics des robots.

Requête
curl "https://pik.li/api/v1/stats?period=7d" \
  -H "Authorization: Bearer $PIKLI_KEY"
Réponse · 200
{
  "window": {
    "from": "2026-09-17T00:00:00+02:00",
    "to": "2026-09-23T10:15:42+02:00",
    "days": 7,
    "interval": "day",
    "period": "7d",
    "clamped": false,
    "retention_days": 730,
    "previous": { "from": "2026-09-10T00:00:00+02:00", "to": "2026-09-17T00:00:00+02:00" }
  },
  "filters": { "bots": false },
  "totals": {
    "links": 3187, "active_links": 2954, "clicks": 402118, "unique_clicks": 288930,
    "clicks_today": 1204, "links_active_24h": 214, "links_created_in_window": 61
  },
  "period": { "clicks": 21480, "uniques": 15522, "links_clicked": 690, "clicks_per_link": 31.1 },
  "previous": { "clicks": 19870, "uniques": 14410, "links_clicked": 655, "clicks_per_link": 30.3, "delta_pct": 8.1, "uniques_delta_pct": 7.7 },
  "timeseries": {
    "interval": "day",
    "points": [
      { "at": "2026-09-17", "clicks": 2890, "uniques": 2104 },
      { "at": "2026-09-18", "clicks": 3120, "uniques": 2240 }
    ]
  },
  "referrers": [{ "referrer": "instagram.com", "clicks": 5210, "share": 24.3 }],
  "direct": 8740,
  "countries": [{ "country": "IT", "clicks": 12877, "share": 59.9 }],
  "devices": [{ "device": "mobile", "clicks": 14002, "share": 65.2 }],
  "browsers": [{ "browser": "Chrome", "clicks": 9406, "share": 43.8 }],
  "os": [{ "os": "iOS", "clicks": 7340, "share": 34.2 }],
  "top_links": [{
    "id": 4821,
    "short_url": "https://lnkz.li/spring-sale",
    "slug": "spring-sale",
    "domain": "lnkz.li",
    "target_url": "https://example.com/shop/spring-sale",
    "target_host": "example.com",
    "title": "Spring sale",
    "description": null,
    "tags": ["promo", "newsletter"],
    "status": "active",
    "moderation": "allowed",
    "active": true,
    "expired": false,
    "password_protected": false,
    "clicks": 1284,
    "unique_clicks": 902,
    "last_clicked_at": "2026-09-23T09:58:12.431+02:00",
    "expires_at": null,
    "max_clicks": null,
    "disabled_reason": null,
    "utm": { "source": "newsletter", "medium": "email", "campaign": "spring" },
    "preview_url": "https://lnkz.li/spring-sale+",
    "qr_url": "https://pik.li/api/v1/links/4821/qr",
    "created_at": "2026-09-01T08:30:05.117+02:00",
    "updated_at": "2026-09-01T08:30:05.117+02:00",
    "clicks_in_window": 311,
    "uniques_in_window": 240
  }]
}
GET /stats/timeseries

Les clics dans le temps

Clics et visiteurs uniques par heure ou par jour, avec le total de la fenêtre précédente et la variation en pourcentage.

Paramètres
  • period string chaîne de requête

    La fenêtre : 24h, 7d (par défaut), 30d, 90d, 1y, all. Voir Fenêtres des statistiques.

  • from string chaîne de requête

    Début de la fenêtre, à la place de period : une date ou une heure ISO 8601.

  • to string chaîne de requête

    Fin de la fenêtre (maintenant par défaut).

  • interval string chaîne de requête

    hour ou day, le pas de la série temporelle.

  • domain string chaîne de requête

    Seulement ce domaine, par nom d'hôte (par exemple lnkz.li). Il doit faire partie des tiens : voir GET /domains.

  • tag string chaîne de requête

    Seulement les liens qui ont ce tag.

  • bots string chaîne de requête

    1 pour compter aussi les clics des robots.

Requête
curl "https://pik.li/api/v1/stats/timeseries?period=24h" \
  -H "Authorization: Bearer $PIKLI_KEY"
Réponse · 200
{
  "window": {
    "from": "2026-09-22T10:15:42+02:00",
    "to": "2026-09-23T10:15:42+02:00",
    "days": 1,
    "interval": "hour",
    "period": "24h",
    "clamped": false,
    "retention_days": 730,
    "previous": { "from": "2026-09-21T10:15:42+02:00", "to": "2026-09-22T10:15:42+02:00" }
  },
  "filters": { "bots": false },
  "interval": "hour",
  "clicks": 3308,
  "previous_clicks": 2977,
  "delta_pct": 11.1,
  "points": [
    { "at": "2026-09-22T08:00:00Z", "clicks": 131, "uniques": 97 },
    { "at": "2026-09-22T09:00:00Z", "clicks": 158, "uniques": 120 }
  ]
}
GET /stats/referrers

D'où viennent les clics

Les sites qui ont envoyé le plus de clics, chacun avec sa part du total, et les clics sans référent (direct).

Paramètres
  • period string chaîne de requête

    La fenêtre : 24h, 7d (par défaut), 30d, 90d, 1y, all. Voir Fenêtres des statistiques.

  • from string chaîne de requête

    Début de la fenêtre, à la place de period : une date ou une heure ISO 8601.

  • to string chaîne de requête

    Fin de la fenêtre (maintenant par défaut).

  • domain string chaîne de requête

    Seulement ce domaine, par nom d'hôte (par exemple lnkz.li). Il doit faire partie des tiens : voir GET /domains.

  • tag string chaîne de requête

    Seulement les liens qui ont ce tag.

  • bots string chaîne de requête

    1 pour compter aussi les clics des robots.

  • limit integer chaîne de requête

    Nombre de lignes par liste, de 1 à 50 (10 par défaut).

Requête
curl "https://pik.li/api/v1/stats/referrers?period=30d&limit=5" \
  -H "Authorization: Bearer $PIKLI_KEY"
Réponse · 200
{
  "window": {
    "from": "2026-09-17T00:00:00+02:00",
    "to": "2026-09-23T10:15:42+02:00",
    "days": 7,
    "interval": "day",
    "period": "7d",
    "clamped": false,
    "retention_days": 730,
    "previous": { "from": "2026-09-10T00:00:00+02:00", "to": "2026-09-17T00:00:00+02:00" }
  },
  "filters": { "bots": false },
  "clicks": 21480,
  "direct": 8740,
  "referrers": [
    { "referrer": "instagram.com", "clicks": 5210, "share": 24.3 },
    { "referrer": "google.com", "clicks": 3011, "share": 14.0 }
  ]
}
GET /stats/countries

Pays et villes

Les pays sous forme de codes ISO, avec leur part ; cities est fourni avec les offres qui incluent les statistiques complètes.

Paramètres
  • period string chaîne de requête

    La fenêtre : 24h, 7d (par défaut), 30d, 90d, 1y, all. Voir Fenêtres des statistiques.

  • from string chaîne de requête

    Début de la fenêtre, à la place de period : une date ou une heure ISO 8601.

  • to string chaîne de requête

    Fin de la fenêtre (maintenant par défaut).

  • domain string chaîne de requête

    Seulement ce domaine, par nom d'hôte (par exemple lnkz.li). Il doit faire partie des tiens : voir GET /domains.

  • tag string chaîne de requête

    Seulement les liens qui ont ce tag.

  • bots string chaîne de requête

    1 pour compter aussi les clics des robots.

  • limit integer chaîne de requête

    Nombre de lignes par liste, de 1 à 50 (10 par défaut).

Requête
curl "https://pik.li/api/v1/stats/countries?period=30d" \
  -H "Authorization: Bearer $PIKLI_KEY"
Réponse · 200
{
  "window": {
    "from": "2026-09-17T00:00:00+02:00",
    "to": "2026-09-23T10:15:42+02:00",
    "days": 7,
    "interval": "day",
    "period": "7d",
    "clamped": false,
    "retention_days": 730,
    "previous": { "from": "2026-09-10T00:00:00+02:00", "to": "2026-09-17T00:00:00+02:00" }
  },
  "filters": { "bots": false },
  "clicks": 21480,
  "countries": [
    { "country": "IT", "clicks": 12877, "share": 59.9 },
    { "country": "DE", "clicks": 2630, "share": 12.2 }
  ],
  "cities": [
    { "city": "Milano", "clicks": 3120, "share": 14.5 },
    { "city": "Roma", "clicks": 2210, "share": 10.3 }
  ]
}
GET /stats/devices

Appareils, navigateurs, systèmes et langues

Quatre listes en un seul appel, chacune avec les clics et la part du total.

Paramètres
  • period string chaîne de requête

    La fenêtre : 24h, 7d (par défaut), 30d, 90d, 1y, all. Voir Fenêtres des statistiques.

  • from string chaîne de requête

    Début de la fenêtre, à la place de period : une date ou une heure ISO 8601.

  • to string chaîne de requête

    Fin de la fenêtre (maintenant par défaut).

  • domain string chaîne de requête

    Seulement ce domaine, par nom d'hôte (par exemple lnkz.li). Il doit faire partie des tiens : voir GET /domains.

  • tag string chaîne de requête

    Seulement les liens qui ont ce tag.

  • bots string chaîne de requête

    1 pour compter aussi les clics des robots.

  • limit integer chaîne de requête

    Nombre de lignes par liste, de 1 à 50 (10 par défaut).

Requête
curl "https://pik.li/api/v1/stats/devices?period=30d" \
  -H "Authorization: Bearer $PIKLI_KEY"
Réponse · 200
{
  "window": {
    "from": "2026-09-17T00:00:00+02:00",
    "to": "2026-09-23T10:15:42+02:00",
    "days": 7,
    "interval": "day",
    "period": "7d",
    "clamped": false,
    "retention_days": 730,
    "previous": { "from": "2026-09-10T00:00:00+02:00", "to": "2026-09-17T00:00:00+02:00" }
  },
  "filters": { "bots": false },
  "clicks": 21480,
  "devices": [{ "device": "mobile", "clicks": 14002, "share": 65.2 }, { "device": "desktop", "clicks": 7100, "share": 33.1 }],
  "browsers": [{ "browser": "Chrome", "clicks": 9406, "share": 43.8 }],
  "os": [{ "os": "iOS", "clicks": 7340, "share": 34.2 }],
  "languages": [{ "language": "it", "clicks": 12690, "share": 59.1 }]
}
GET /stats/top

Les liens les plus cliqués

Les liens qui ont reçu le plus de clics dans la fenêtre, chacun avec clicks_in_window et uniques_in_window.

Paramètres
  • period string chaîne de requête

    La fenêtre : 24h, 7d (par défaut), 30d, 90d, 1y, all. Voir Fenêtres des statistiques.

  • from string chaîne de requête

    Début de la fenêtre, à la place de period : une date ou une heure ISO 8601.

  • to string chaîne de requête

    Fin de la fenêtre (maintenant par défaut).

  • domain string chaîne de requête

    Seulement ce domaine, par nom d'hôte (par exemple lnkz.li). Il doit faire partie des tiens : voir GET /domains.

  • tag string chaîne de requête

    Seulement les liens qui ont ce tag.

  • bots string chaîne de requête

    1 pour compter aussi les clics des robots.

  • limit integer chaîne de requête

    Nombre de lignes par liste, de 1 à 50 (10 par défaut).

Requête
curl "https://pik.li/api/v1/stats/top?period=7d&limit=10" \
  -H "Authorization: Bearer $PIKLI_KEY"
Réponse · 200
{
  "window": {
    "from": "2026-09-17T00:00:00+02:00",
    "to": "2026-09-23T10:15:42+02:00",
    "days": 7,
    "interval": "day",
    "period": "7d",
    "clamped": false,
    "retention_days": 730,
    "previous": { "from": "2026-09-10T00:00:00+02:00", "to": "2026-09-17T00:00:00+02:00" }
  },
  "filters": { "bots": false },
  "clicks": 21480,
  "links": [{
    "id": 4821,
    "short_url": "https://lnkz.li/spring-sale",
    "slug": "spring-sale",
    "domain": "lnkz.li",
    "target_url": "https://example.com/shop/spring-sale",
    "target_host": "example.com",
    "title": "Spring sale",
    "description": null,
    "tags": ["promo", "newsletter"],
    "status": "active",
    "moderation": "allowed",
    "active": true,
    "expired": false,
    "password_protected": false,
    "clicks": 1284,
    "unique_clicks": 902,
    "last_clicked_at": "2026-09-23T09:58:12.431+02:00",
    "expires_at": null,
    "max_clicks": null,
    "disabled_reason": null,
    "utm": { "source": "newsletter", "medium": "email", "campaign": "spring" },
    "preview_url": "https://lnkz.li/spring-sale+",
    "qr_url": "https://pik.li/api/v1/links/4821/qr",
    "created_at": "2026-09-01T08:30:05.117+02:00",
    "updated_at": "2026-09-01T08:30:05.117+02:00",
    "clicks_in_window": 311,
    "uniques_in_window": 240
  }]
}

Tags

Les étiquettes que tu poses sur tes liens.

GET /tags

Tes tags

Tous les tags utilisés sur tes liens, avec le nombre de liens qui les portent et le nombre de clics reçus, les plus utilisés d'abord.

Paramètres
  • q string chaîne de requête

    Seulement les tags qui contiennent ce texte.

Requête
curl https://pik.li/api/v1/tags \
  -H "Authorization: Bearer $PIKLI_KEY"
Réponse · 200
{
  "tags": [
    { "name": "promo", "links": 214, "clicks": 90211 },
    { "name": "newsletter", "links": 58, "clicks": 20473 }
  ],
  "total": 2
}
Erreurs Seulement celles que tous les points d'accès peuvent renvoyer.

Domaines

Là où tes liens courts peuvent vivre.

GET /domains

Les domaines que tu peux utiliser

Les domaines pik.li autorisés par ton offre et tes domaines personnalisés vérifiés, lequel est le domaine par défaut, et combien de domaines personnalisés ton offre autorise. Passe hostname comme domain quand tu crées un lien.

Requête
curl https://pik.li/api/v1/domains \
  -H "Authorization: Bearer $PIKLI_KEY"
Réponse · 200
{
  "domains": [
    { "id": 1, "hostname": "lnkz.li", "base_url": "https://lnkz.li", "kind": "system", "min_plan": "free", "status": "active", "verified": true, "default": true },
    { "id": 318, "hostname": "go.example.com", "base_url": "https://go.example.com", "kind": "custom", "min_plan": "free", "status": "active", "verified": true, "default": false }
  ],
  "default": "lnkz.li",
  "custom_domains": { "used": 1, "limit": 3 }
}
Erreurs Seulement celles que tous les points d'accès peuvent renvoyer.

WebhooksOffres : Premium · Business

Gère depuis ton code les webhooks que tu configurerais sinon sur la page Webhooks du tableau de bord. Le fonctionnement des livraisons est décrit plus bas, dans la section Webhooks.

GET /webhooks

Lister tes webhooks

Tes webhooks et les événements auxquels tu peux t'abonner. Le secret n'est plus jamais affiché après la création.

Requête
curl https://pik.li/api/v1/webhooks \
  -H "Authorization: Bearer $PIKLI_KEY"
Réponse · 200
{
  "webhooks": [{
    "id": 17,
    "url": "https://example.com/hooks/pikli",
    "events": ["link.created", "link.disabled"],
    "active": true,
    "failures_count": 0,
    "last_delivered_at": "2026-09-22T18:04:11.520+02:00",
    "created_at": "2026-09-10T11:20:33.004+02:00",
    "updated_at": "2026-09-10T11:20:33.004+02:00",
    "signature_header": "X-Pikli-Signature"
  }],
  "events": ["link.created", "link.clicked", "link.disabled", "link.deleted"]
}
GET /webhooks/:id

Lire un webhook

Le webhook avec ses 20 dernières livraisons et le statut que ton serveur a renvoyé pour chacune.

Paramètres
  • id integer dans le chemin obligatoire

    L'id du webhook.

Requête
curl https://pik.li/api/v1/webhooks/17 \
  -H "Authorization: Bearer $PIKLI_KEY"
Réponse · 200
{
  "id": 17,
  "url": "https://example.com/hooks/pikli",
  "events": ["link.created", "link.disabled"],
  "active": true,
  "failures_count": 0,
  "last_delivered_at": "2026-09-22T18:04:11.520+02:00",
  "created_at": "2026-09-10T11:20:33.004+02:00",
  "updated_at": "2026-09-10T11:20:33.004+02:00",
  "signature_header": "X-Pikli-Signature",
  "deliveries": [
    { "id": 903, "event": "ping", "response_code": 200, "attempts": 1, "delivered_at": "2026-09-22T18:04:11.520+02:00", "created_at": "2026-09-22T18:04:11.520+02:00" }
  ]
}
POST /webhooks

Créer un webhook

La réponse contient le secret qui sert à signer les livraisons, cette fois seulement : enregistre-le tout de suite.

Paramètres
  • url string corps JSON obligatoire

    L'adresse qui reçoit les livraisons, en http:// ou https://, joignable depuis Internet.

  • events array | string corps JSON

    Les événements à recevoir, sous forme de tableau ou séparés par des virgules. Sans ce paramètre : tous. Voir la liste des événements plus bas.

  • active boolean corps JSON

    false pour le créer en pause. true par défaut.

Requête
curl -X POST https://pik.li/api/v1/webhooks \
  -H "Authorization: Bearer $PIKLI_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/hooks/pikli","events":["link.created","link.disabled"]}'
Réponse · 201
{
  "id": 17,
  "url": "https://example.com/hooks/pikli",
  "events": ["link.created", "link.disabled"],
  "active": true,
  "failures_count": 0,
  "last_delivered_at": null,
  "created_at": "2026-09-10T11:20:33.004+02:00",
  "updated_at": "2026-09-10T11:20:33.004+02:00",
  "signature_header": "X-Pikli-Signature",
  "secret": "ec307f56cfac71856a10d641eee7dd58e0eb8286"
}
PATCH /webhooks/:id

Modifier un webhook

Envoie seulement ce qui change : l'adresse, les événements, ou active pour suspendre et reprendre les livraisons.

PUT est aussi accepté, avec les mêmes paramètres.

Paramètres
  • id integer dans le chemin obligatoire

    L'id du webhook.

  • url string corps JSON

    Une nouvelle adresse.

  • events array | string corps JSON

    La nouvelle liste d'événements ; elle ne peut pas être vide.

  • active boolean corps JSON

    false suspend les livraisons, true les reprend.

Requête
curl -X PATCH https://pik.li/api/v1/webhooks/17 \
  -H "Authorization: Bearer $PIKLI_KEY" \
  -H "Content-Type: application/json" \
  -d '{"active":false}'
Réponse · 200
{
  "id": 17,
  "url": "https://example.com/hooks/pikli",
  "events": ["link.created", "link.disabled"],
  "active": false,
  "failures_count": 0,
  "last_delivered_at": "2026-09-22T18:04:11.520+02:00",
  "created_at": "2026-09-10T11:20:33.004+02:00",
  "updated_at": "2026-09-23T10:15:42.118+02:00",
  "signature_header": "X-Pikli-Signature"
}
DELETE /webhooks/:id

Supprimer un webhook

Les livraisons s'arrêtent et leur historique est supprimé avec le webhook.

Paramètres
  • id integer dans le chemin obligatoire

    L'id du webhook.

Requête
curl -X DELETE https://pik.li/api/v1/webhooks/17 \
  -H "Authorization: Bearer $PIKLI_KEY"
Réponse · 204
Pas de corps : le statut suffit.
POST /webhooks/:id/test

Envoyer une livraison de test

Met en file d'attente un ping vers l'adresse du webhook, signé comme toutes les livraisons : le moyen le plus rapide de vérifier ton code de signature. Il part peu après et apparaît dans GET /webhooks/:id.

Paramètres
  • id integer dans le chemin obligatoire

    L'id du webhook.

Requête
curl -X POST https://pik.li/api/v1/webhooks/17/test \
  -H "Authorization: Bearer $PIKLI_KEY"
Réponse · 202
{
  "queued": true,
  "event": "ping",
  "webhook_id": 17
}

WebhooksOffres : Premium · Business

Un webhook est une adresse sur ton serveur que pik.li appelle avec un POST quand quelque chose se passe dans ton compte. Tu en crées un sur la page Webhooks du tableau de bord ou via les points d'accès des webhooks.

Événements

ping
La livraison de test, envoyée quand tu cliques sur Tester dans le tableau de bord ou que tu appelles POST /webhooks/:id/test.
link.created
Un lien a été créé, depuis le tableau de bord ou depuis l'API. bientôt
link.clicked
Quelqu'un a cliqué sur l'un de tes liens. bientôt
link.disabled
Un lien a été désactivé, par toi ou par la modération. bientôt
link.deleted
Un lien a été supprimé. bientôt
Les événements de lien peuvent déjà être choisis dans un webhook, mais leurs livraisons n'ont pas encore commencé : pour l'instant, pik.li n'envoie que le ping de test.

À quoi ressemble une livraison

Un POST avec un corps JSON de trois champs : event, le nom de l'événement ; created_at, le moment de l'envoi ; data, les détails de l'événement. Il est accompagné de ces en-têtes :

Ce que reçoit ton serveur
POST /hooks/pikli HTTP/1.1
Content-Type: application/json
X-Pikli-Event: ping
X-Pikli-Signature: sha256=bdaf35d42413498dab7fea8be1f292c36b690152e5ec7d66e6dd2c2cd0b2d61c

{"event":"ping","created_at":"2026-09-23T10:15:42+02:00","data":{"message":"pik.li webhook test","at":"2026-09-23T10:15:42+02:00","via":"api"}}
Content-Type
toujours application/json.
X-Pikli-Event
le nom de l'événement, le même que dans le corps : de quoi aiguiller la requête avant de la lire.
X-Pikli-Signature
sha256= suivi de la signature du corps, comme décrit ci-dessous.

Vérifier la signature

Chaque livraison est signée avec le secret de son webhook, que tu reçois une seule fois, à sa création. L'en-tête X-Pikli-Signature contient sha256= suivi du HMAC-SHA256 du corps brut, en hexadécimal. Calcule le même HMAC sur les octets reçus — avant de les convertir en JSON, ce qui les modifierait — et compare les deux chaînes en temps constant. Si elles diffèrent, réponds 401 et ignore la requête.

Pour tester ton code : avec le secret ec307f56cfac71856a10d641eee7dd58e0eb8286 de l'exemple ci-dessus, le corps de la livraison présentée ici donne exactement la signature de son en-tête.

Node.js
import crypto from "node:crypto";
import express from "express";

const app = express();

app.post("/hooks/pikli", express.raw({ type: "application/json" }), (req, res) => {
  const expected = "sha256=" + crypto
    .createHmac("sha256", process.env.PIKLI_WEBHOOK_SECRET)
    .update(req.body)
    .digest("hex");
  const received = req.get("X-Pikli-Signature") || "";
  const valid = received.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected));
  if (!valid) return res.sendStatus(401);

  const event = JSON.parse(req.body);
  res.sendStatus(204);
});
Python
import hashlib, hmac, os
from flask import Flask, abort, request

app = Flask(__name__)
SECRET = os.environ["PIKLI_WEBHOOK_SECRET"].encode()

@app.post("/hooks/pikli")
def pikli_hook():
    body = request.get_data()
    expected = "sha256=" + hmac.new(SECRET, body, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(expected, request.headers.get("X-Pikli-Signature", "")):
        abort(401)
    event = request.get_json()
    return "", 204
Ruby
class PikliHooksController < ActionController::API
  def create
    body = request.raw_post
    expected = "sha256=" + OpenSSL::HMAC.hexdigest("SHA256", ENV.fetch("PIKLI_WEBHOOK_SECRET"), body)
    received = request.headers["X-Pikli-Signature"].to_s
    return head(:unauthorized) unless ActiveSupport::SecurityUtils.secure_compare(expected, received)

    event = JSON.parse(body)
    head :no_content
  end
end
PHP
<?php
$body = file_get_contents('php://input');
$expected = 'sha256=' . hash_hmac('sha256', $body, getenv('PIKLI_WEBHOOK_SECRET'));
$received = $_SERVER['HTTP_X_PIKLI_SIGNATURE'] ?? '';
if (!hash_equals($expected, $received)) {
    http_response_code(401);
    exit;
}
$event = json_decode($body, true);
http_response_code(204);

Réponses, nouvelles tentatives et échecs

  • Réponds avec n'importe quel statut 2xx le plus vite possible, et fais le travail long ensuite.
  • Si pik.li n'arrive pas du tout à joindre ton adresse — erreur réseau, connexion refusée, adresse non publique —, la livraison est tentée jusqu'à 5 fois, avec des pauses de plus en plus longues : la dernière tentative a lieu environ six minutes après la première.
  • Une réponse avec un statut 400 ou supérieur est enregistrée et n'est pas renvoyée. Elle ajoute un au failures_count du webhook, qui revient à zéro à la première livraison réussie.
  • Les 20 dernières livraisons, avec le statut renvoyé par ton serveur, se trouvent dans GET /webhooks/:id et sur la page Webhooks du tableau de bord.
  • L'adresse doit être publique : pik.li n'appelle pas les réseaux privés ou locaux. Utilise https://, pour que les livraisons circulent chiffrées.

OpenAPI

La même référence, sous une forme lisible par les machines, est publiée à l'adresse /openapi.json (OpenAPI 3.1). Importe-la dans Postman, Insomnia ou Bruno, ou génère un client avec openapi-generator.

Il manque quelque chose, ou ça ne fonctionne pas comme l'indique cette page ? Écris à [email protected].