pik.li pik.li
Version 1 · stabil

pik.li API

Erstelle, bearbeite und miss deine Kurzlinks aus deinem eigenen Code. Eine REST-API mit JSON in beide Richtungen, ein Schlüssel pro Integration, und jeder Endpunkt ist hier mit einer Anfrage und einer echten Antwort dokumentiert.

API-Schlüssel erstellen OpenAPI 3.1
Basis-URLhttps://pik.li/api/v1

Einführung

Mit der pik.li API erledigt deine eigene Software, was sonst das Dashboard tut: Kurzlinks erstellen und bearbeiten, ihre Statistiken lesen, deine Tags und Domains auflisten und Webhooks verwalten. Sie läuft über HTTPS, und jeder Pfad beginnt mit der oben gezeigten Basis-URL.

  • Sende den Body von Anfragen als JSON mit Content-Type: application/json; die Parameter von GET-Anfragen gehören in den Query-String. Antworten sind immer JSON in UTF-8, außer beim QR-Code, der ein Bild ist.
  • Die Version steht im Pfad (/api/v1), und jede Antwort trägt den Header X-Api-Version: 1. In Antworten von v1 können mit der Zeit neue Felder auftauchen: Lass deinen Code die ignorieren, die er nicht kennt.
  • Datum und Uhrzeit sind Strings im Format ISO 8601 mit ihrem Offset (2026-09-23T10:15:42.118+02:00); ein reines Datum sieht so aus: 2026-09-23.
  • IDs sind Ganzzahlen. Einen Link findest du auch über seine Kurz-URL, mit GET /links/lookup.

Schnellstart

  1. Erstelle im Dashboard einen Schlüssel (siehe Einen Schlüssel erstellen) und lege ihn in einer Umgebungsvariablen ab.
  2. Prüfe, ob er funktioniert: GET /me antwortet mit deinem Konto und deinem Tarif.
  3. Erstelle deinen ersten Kurzlink mit 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"}'

Authentifizierung

Jeder Aufruf außer GET /ping braucht einen API-Schlüssel, der im Header Authorization als Bearer-Token mitgeschickt wird.

Header
Authorization: Bearer pk_live_3kZ9vQeX1mT0bR7yNw2aLp4sUc8dHf6J

Der Header X-API-Key funktioniert ebenfalls. Der Query-Parameter api_key wird für ältere Clients noch akzeptiert, aber verzichte besser darauf: Adressen landen in Logs und im Browserverlauf.

Schlüssel beginnen mit pk_live_, gefolgt von 32 Zeichen. Ein Schlüssel sieht genau das, was sein Konto sieht – dessen Links, Domains, Tags, Statistiken und Webhooks –, und alle Schlüssel eines Kontos können dasselbe: Es gibt keine Nur-Lese-Schlüssel.

Behandle einen Schlüssel wie ein Passwort. Bewahre ihn auf deinem Server auf, niemals in Code, der im Browser läuft, oder in einer App, die du weitergibst. Gerät einer nach außen, widerrufe ihn und erstelle einen neuen: Das dauert zehn Sekunden.

Einen Schlüssel erstellen

  1. Melde dich an und öffne im Dashboard API & Schlüssel.
  2. Gib dem Schlüssel einen Namen, an dem du erkennst, wo er verwendet wird (zum Beispiel CRM Produktion), und klicke auf Schlüssel erzeugen.
  3. Kopiere den Schlüssel sofort: Das Dashboard zeigt ihn nur ein einziges Mal an.
  4. Du kannst bis zu 10 aktive Schlüssel haben. Für jeden zeigt die Seite, wann er zuletzt verwendet wurde und wie viele Anfragen er gestellt hat, und dort kannst du ihn auch widerrufen: Ab dem nächsten Aufruf erhält ein widerrufener Schlüssel 401 unauthorized.

Wer die API nutzen kann

Die API ist in den Bezahltarifen enthalten (Premium und Business). Der Tarif Base enthält sie nicht, es sei denn, das pik.li-Team schaltet sie für den kostenlosen Tarif oder für dein Konto frei. In jedem Fall braucht das Konto eine bestätigte E-Mail-Adresse und darf weder gesperrt noch ausgesetzt sein.

Fehlt eine dieser Voraussetzungen, antwortet die API mit api_disabled, email_unconfirmed oder account_blocked.

Base

Nur wenn das Team sie freischaltet
Anfragen pro Minute
60
Neue Links
3 pro Tag
Links pro Bulk-Aufruf
10
Aufbewahrung der Statistiken
90 Tage
Webhooks
Nein

Premium

API enthalten
Anfragen pro Minute
600
Neue Links
2.500 pro Monat
Links pro Bulk-Aufruf
100
Aufbewahrung der Statistiken
730 Tage
Webhooks
Ja

Business

API enthalten
Anfragen pro Minute
3.000
Neue Links
10.000 pro Monat
Links pro Bulk-Aufruf
100
Aufbewahrung der Statistiken
1.095 Tage
Webhooks
Ja

Das sind die Standardwerte jedes Tarifs, live aus den Preiseinstellungen gelesen. Deine eigenen Werte, einschließlich aller Änderungen, die das Team an deinem Konto vorgenommen hat, liefern GET /limits und GET /me.

Rate-Limits

Jeder Schlüssel darf pro Minute so viele Anfragen stellen, wie sein Tarif vorsieht (siehe Tabelle oben). Zu Beginn jeder Minute fängt die Zählung von vorn an.

X-RateLimit-Limit
erlaubte Anfragen pro Minute für diesen Schlüssel
X-RateLimit-Remaining
verbleibende Anfragen in der laufenden Minute
X-RateLimit-Reset
wann die Zählung neu beginnt, als Unix-Zeit in Sekunden
Retry-After
nur bei einer 429-Antwort: wie viele Sekunden du warten sollst

Ist das Limit überschritten, antwortet die API mit 429, dem Fehler rate_limited und einem Header Retry-After. Warte so lange und mach dann weiter: Schickst du sofort erneut, zehrst du nur am Kontingent der nächsten Minute.

Antwort
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"
}
  • Aufrufe ohne Schlüssel sind auf 30 pro Minute und IP-Adresse begrenzt (GET /ping zählt nicht mit).
  • Zusätzlich hat jede IP-Adresse unabhängig vom Tarif eine Obergrenze von 600 Anfragen pro Minute an pik.li insgesamt.

Fehler

Eine fehlgeschlagene Anfrage erhält unabhängig vom Endpunkt immer eine Antwort in derselben Form, mit einem HTTP-Status, der zum Problem passt.

Antwort
{
  "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
Stabile Kennung für deinen Code. Sie ändert sich nie und wird nie übersetzt.
message
Ein Satz für Menschen, in der Sprache der Anfrage (siehe „Sprachen“).
details
Strukturierte Daten, wo sie helfen: die ungültigen Felder, die erlaubten Werte, die Zahlen eines Kontingents. Nicht immer vorhanden.
docs_url
Der Link zur Erklärung dieses Codes auf dieser Seite.
request_id
Die Kennung der Anfrage, auch im Header X-Request-Id enthalten. Gib sie an, wenn du dem Support schreibst.

Jeder Endpunkt, der einen Schlüssel braucht, kann auch mit unauthorized account_blocked email_unconfirmed api_disabled rate_limited antworten: Sie werden nicht bei jedem Endpunkt wiederholt.

unauthorized401
Der Schlüssel fehlt, ist falsch oder wurde widerrufen. Prüfe den Header Authorization und ob der Schlüssel im Dashboard noch als aktiv angezeigt wird.
account_blocked403
Das Konto ist gesperrt, ausgesetzt oder deaktiviert. Die API bleibt verschlossen, bis das Konto wieder aktiv ist.
email_unconfirmed403
Die E-Mail-Adresse des Kontos ist noch nicht bestätigt. Folge dem Link in der Bestätigungs-E-Mail und versuche es dann erneut.
api_disabled403
Die API ist im Tarif dieses Kontos nicht enthalten oder wurde dafür abgeschaltet. Siehe „Wer die API nutzen kann“.
forbidden403
Der Schlüssel darf diese Aktion nicht ausführen. reserviert
feature_required402
Der Tarif enthält die Funktion nicht, die dieser Endpunkt braucht. details.feature nennt sie, details.plan den ersten Tarif, der sie enthält.
quota_exceeded402
Das Link-Kontingent des Tages oder Monats ist aufgebraucht. details enthält limit, used, period und resets_at, den Zeitpunkt, ab dem wieder neue Links möglich sind.
not_found404
Die Ressource existiert nicht, wurde gelöscht oder gehört zu einem anderen Konto.
conflict409
Die Anfrage steht im Konflikt mit dem aktuellen Zustand der Ressource. reserviert
invalid422
Einige Daten haben die Validierung nicht bestanden: details.fields listet die Probleme Feld für Feld auf, details.messages als fertige Sätze.
domain_not_allowed422
Dein Konto kann diese Domain nicht verwenden. GET /domains listet die Domains auf, die es verwenden kann.
bad_request400
Ein Pflichtparameter fehlt; details.parameter nennt ihn.
invalid_json400
Der Body ist kein gültiges JSON. Prüfe die Anführungszeichen und den Header Content-Type.
invalid_parameter400
Ein Parameter hat einen Wert, den der Endpunkt nicht kennt. Die Meldung nennt den Parameter, und details.allowed listet, falls vorhanden, die akzeptierten Werte auf.
bulk_empty400
Das Array links eines Bulk-Aufrufs ist leer oder fehlt.
bulk_too_many400
Zu viele Links in einem Bulk-Aufruf. details.max ist das Limit deines Tarifs: Teile die Liste auf mehrere Aufrufe auf.
idempotency_key_invalid400
Der Header Idempotency-Key ist länger als 128 Zeichen.
rate_limited429
Zu viele Anfragen in dieser Minute. Warte die in Retry-After angegebenen Sekunden ab und mach dann weiter.

Eine 500-Antwort bedeutet, dass bei uns etwas schiefgelaufen ist und an deiner Anfrage nichts zu korrigieren ist. Versuche es etwas später erneut; passiert es wieder, schreib dem Support und gib den Wert des Headers X-Request-Id an.

Paginierung

Listen, die lang werden können, sind in Seiten aufgeteilt: GET /links, GET /links/active und GET /links/:id/clicks.

  • page: die gewünschte Seite, beginnend bei 1.
  • per_page: wie viele Einträge pro Seite, von 1 bis 100 (Standard 50). limit wird als Synonym akzeptiert.

Die Antwort enthält ein Objekt pagination, und die Gesamtzahl steht zusätzlich im Header X-Total-Count. Frag so lange next_page ab, bis es null ist.

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

Idempotenz

POST /links, POST /links/bulk und POST /links/generate akzeptieren einen Header Idempotency-Key: einen beliebigen Text deiner Wahl mit bis zu 128 Zeichen, einen pro Vorgang – etwa eine Bestellnummer oder eine UUID.

Kommt derselbe Idempotenz-Schlüssel innerhalb von 24 Stunden erneut mit demselben API-Schlüssel an, erstellt pik.li nichts: Es schickt die erste Antwort zurück, mit dem Header Idempotent-Replayed: true. Eine nach einem Timeout wiederholte Anfrage erzeugt so nie ein Duplikat.

Gleiche Anfrage, gleicher Schlüssel, innerhalb von 24 Stunden
HTTP/1.1 201 Created
Idempotent-Replayed: true
Content-Type: application/json; charset=utf-8

Antworten mit einem 5xx-Status werden nicht gespeichert; wiederholst du die Anfrage mit demselben Idempotenz-Schlüssel, wird sie also erneut ausgeführt. Ein zu langer Schlüssel erhält idempotency_key_invalid.

Sprachen

Fehlermeldungen gibt es in neun Sprachen: en it zh ar ru fr de es pt-BR. Die API richtet sich nach dem Parameter locale, dann nach dem Header Accept-Language, dann nach der Sprache deines Kontos und fällt zuletzt auf Englisch zurück. Nur die Meldung ändert sich: Codes und Feldnamen bleiben gleich, ebenso docs_url, das diese Seite in derselben Sprache öffnet.

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"
}

Statistik-Zeitfenster

Die Statistik-Endpunkte lesen ein Zeitfenster und vergleichen es mit dem unmittelbar vorangehenden Fenster gleicher Länge: Daher stammen previous und delta_pct.

period
24h, 7d, 30d, 90d, 1y oder all (der gesamte Verlauf, den dein Tarif aufbewahrt). Eine Anzahl von Tagen geht auch: period=45 oder das ältere days=45. Standard ist 7d für das Konto, 30d für einen einzelnen Link und all für die Liste der Klicks.
from, to
Statt period: ein Datum (2026-09-01) oder ein Zeitpunkt nach ISO 8601. to ist standardmäßig jetzt, from 30 Tage vor to; ein Datum in to zählt bis zum Ende dieses Tages.
interval
hour oder day, die Schrittweite der Zeitreihe. Fenster bis 48 Stunden sind standardmäßig stündlich, längere täglich; eine stündliche Reihe umfasst höchstens 7 Tage.
window.clamped
Ein Fenster reicht nie weiter zurück als der Verlauf, den dein Tarif aufbewahrt (retention_days). Wurde es gekürzt, ist window.clamped true.
bots
Klicks von Bots werden nicht gezählt. Füge bots=1 hinzu, um sie mitzuzählen; die Statistiken eines einzelnen Links weisen sie außerdem gesondert in period.bots aus.

Endpunkte

Jeder Endpunkt mit seinen Parametern, einer Anfrage, die du direkt ins Terminal einfügen kannst, und einer echten Antwort, gekürzt, wo eine Liste lang würde. Die Beispiele lesen den Schlüssel aus $PIKLI_KEY, wie im Schnellstart.

Dienst

Ob die API läuft und wie viel von deinem Tarif noch übrig ist.

GET /ping Kein Schlüssel nötig

Prüfen, ob die API erreichbar ist

Braucht keinen Schlüssel: gedacht für Health-Checks. Liefert die Uhrzeit des Servers und die Adressen dieser Dokumentation und der OpenAPI-Datei.

Anfrage
curl https://pik.li/api/v1/ping
Antwort · 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"
}
Fehler Keine: Dieser Endpunkt braucht keinen Schlüssel.
GET /limits

Was von deinen Kontingenten übrig ist

Heute oder in diesem Monat noch verfügbare Links, erfasste Klicks, das laufende Rate-Limit-Fenster, genutzte Schlüssel und eigene Domains sowie die maximale Größe eines Bulk-Aufrufs. Jede Zahl berücksichtigt die Änderungen, die das Team eventuell an deinem Konto vorgenommen hat.

Anfrage
curl https://pik.li/api/v1/limits \
  -H "Authorization: Bearer $PIKLI_KEY"
Antwort · 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"]
}
Fehler Nur die, die jeder Endpunkt zurückgeben kann.

Konto

Wer aufruft: das Konto, dem der Schlüssel gehört.

GET /me

Dein Konto und der verwendete Schlüssel

Profil, Tarif und dessen Ablauf, Funktionen, Limits und Verbrauch sowie der Schlüssel, der den Aufruf gemacht hat, mit seinem Anfragezähler.

Anfrage
curl https://pik.li/api/v1/me \
  -H "Authorization: Bearer $PIKLI_KEY"
Antwort · 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"
}
Fehler Nur die, die jeder Endpunkt zurückgeben kann.

Kontostatistiken

Die Zahlen all deiner Links zusammen, mit den Filtern des Dashboards: Zeitfenster, Domain, Tag, Bots. Die gemeinsamen Parameter findest du unter „Statistik-Zeitfenster“.

GET /stats

Kontostatistiken in einem Aufruf

Summen des Kontos, das Zeitfenster im Vergleich zum vorherigen, die Zeitreihe, die 10 wichtigsten Referrer und Länder, die 6 wichtigsten Geräte, Browser und Betriebssysteme sowie die 10 meistgeklickten Links. Die übrigen /stats-Endpunkte liefern jeden Teil einzeln, mit längeren Listen.

Parameter
  • period string Query-String

    Das Zeitfenster: 24h, 7d (Standard), 30d, 90d, 1y, all. Siehe „Statistik-Zeitfenster“.

  • from string Query-String

    Beginn des Zeitfensters, statt period: ein Datum oder ein Zeitpunkt nach ISO 8601.

  • to string Query-String

    Ende des Zeitfensters (standardmäßig jetzt).

  • interval string Query-String

    hour oder day, die Schrittweite der Zeitreihe.

  • domain string Query-String

    Nur diese Domain, als Hostname (zum Beispiel lnkz.li). Es muss eine deiner Domains sein: siehe GET /domains.

  • tag string Query-String

    Nur die Links mit diesem Tag.

  • bots string Query-String

    1, um auch Klicks von Bots zu zählen.

Anfrage
curl "https://pik.li/api/v1/stats?period=7d" \
  -H "Authorization: Bearer $PIKLI_KEY"
Antwort · 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

Klicks im Zeitverlauf

Klicks und eindeutige Besucher pro Stunde oder pro Tag, mit der Summe des vorherigen Fensters und der Veränderung in Prozent.

Parameter
  • period string Query-String

    Das Zeitfenster: 24h, 7d (Standard), 30d, 90d, 1y, all. Siehe „Statistik-Zeitfenster“.

  • from string Query-String

    Beginn des Zeitfensters, statt period: ein Datum oder ein Zeitpunkt nach ISO 8601.

  • to string Query-String

    Ende des Zeitfensters (standardmäßig jetzt).

  • interval string Query-String

    hour oder day, die Schrittweite der Zeitreihe.

  • domain string Query-String

    Nur diese Domain, als Hostname (zum Beispiel lnkz.li). Es muss eine deiner Domains sein: siehe GET /domains.

  • tag string Query-String

    Nur die Links mit diesem Tag.

  • bots string Query-String

    1, um auch Klicks von Bots zu zählen.

Anfrage
curl "https://pik.li/api/v1/stats/timeseries?period=24h" \
  -H "Authorization: Bearer $PIKLI_KEY"
Antwort · 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

Woher die Klicks kommen

Die Websites, die die meisten Klicks geschickt haben, jeweils mit ihrem Anteil an der Gesamtzahl, und die Klicks ohne Referrer (direct).

Parameter
  • period string Query-String

    Das Zeitfenster: 24h, 7d (Standard), 30d, 90d, 1y, all. Siehe „Statistik-Zeitfenster“.

  • from string Query-String

    Beginn des Zeitfensters, statt period: ein Datum oder ein Zeitpunkt nach ISO 8601.

  • to string Query-String

    Ende des Zeitfensters (standardmäßig jetzt).

  • domain string Query-String

    Nur diese Domain, als Hostname (zum Beispiel lnkz.li). Es muss eine deiner Domains sein: siehe GET /domains.

  • tag string Query-String

    Nur die Links mit diesem Tag.

  • bots string Query-String

    1, um auch Klicks von Bots zu zählen.

  • limit integer Query-String

    Wie viele Zeilen pro Liste, von 1 bis 50 (Standard 10).

Anfrage
curl "https://pik.li/api/v1/stats/referrers?period=30d&limit=5" \
  -H "Authorization: Bearer $PIKLI_KEY"
Antwort · 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

Länder und Städte

Länder als ISO-Codes, mit ihrem Anteil; cities gibt es in den Tarifen mit vollständigen Statistiken.

Parameter
  • period string Query-String

    Das Zeitfenster: 24h, 7d (Standard), 30d, 90d, 1y, all. Siehe „Statistik-Zeitfenster“.

  • from string Query-String

    Beginn des Zeitfensters, statt period: ein Datum oder ein Zeitpunkt nach ISO 8601.

  • to string Query-String

    Ende des Zeitfensters (standardmäßig jetzt).

  • domain string Query-String

    Nur diese Domain, als Hostname (zum Beispiel lnkz.li). Es muss eine deiner Domains sein: siehe GET /domains.

  • tag string Query-String

    Nur die Links mit diesem Tag.

  • bots string Query-String

    1, um auch Klicks von Bots zu zählen.

  • limit integer Query-String

    Wie viele Zeilen pro Liste, von 1 bis 50 (Standard 10).

Anfrage
curl "https://pik.li/api/v1/stats/countries?period=30d" \
  -H "Authorization: Bearer $PIKLI_KEY"
Antwort · 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

Geräte, Browser, Systeme und Sprachen

Vier Listen in einem Aufruf, jeweils mit den Klicks und dem Anteil an der Gesamtzahl.

Parameter
  • period string Query-String

    Das Zeitfenster: 24h, 7d (Standard), 30d, 90d, 1y, all. Siehe „Statistik-Zeitfenster“.

  • from string Query-String

    Beginn des Zeitfensters, statt period: ein Datum oder ein Zeitpunkt nach ISO 8601.

  • to string Query-String

    Ende des Zeitfensters (standardmäßig jetzt).

  • domain string Query-String

    Nur diese Domain, als Hostname (zum Beispiel lnkz.li). Es muss eine deiner Domains sein: siehe GET /domains.

  • tag string Query-String

    Nur die Links mit diesem Tag.

  • bots string Query-String

    1, um auch Klicks von Bots zu zählen.

  • limit integer Query-String

    Wie viele Zeilen pro Liste, von 1 bis 50 (Standard 10).

Anfrage
curl "https://pik.li/api/v1/stats/devices?period=30d" \
  -H "Authorization: Bearer $PIKLI_KEY"
Antwort · 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

Die meistgeklickten Links

Die Links mit den meisten Klicks im Zeitfenster, jeweils mit clicks_in_window und uniques_in_window.

Parameter
  • period string Query-String

    Das Zeitfenster: 24h, 7d (Standard), 30d, 90d, 1y, all. Siehe „Statistik-Zeitfenster“.

  • from string Query-String

    Beginn des Zeitfensters, statt period: ein Datum oder ein Zeitpunkt nach ISO 8601.

  • to string Query-String

    Ende des Zeitfensters (standardmäßig jetzt).

  • domain string Query-String

    Nur diese Domain, als Hostname (zum Beispiel lnkz.li). Es muss eine deiner Domains sein: siehe GET /domains.

  • tag string Query-String

    Nur die Links mit diesem Tag.

  • bots string Query-String

    1, um auch Klicks von Bots zu zählen.

  • limit integer Query-String

    Wie viele Zeilen pro Liste, von 1 bis 50 (Standard 10).

Anfrage
curl "https://pik.li/api/v1/stats/top?period=7d&limit=10" \
  -H "Authorization: Bearer $PIKLI_KEY"
Antwort · 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

Die Etiketten, die du deinen Links gibst.

GET /tags

Deine Tags

Alle Tags, die an deinen Links vorkommen, mit der Anzahl der Links, die sie tragen, und den Klicks, die diese gesammelt haben, die meistverwendeten zuerst.

Parameter
  • q string Query-String

    Nur die Tags, die diesen Text enthalten.

Anfrage
curl https://pik.li/api/v1/tags \
  -H "Authorization: Bearer $PIKLI_KEY"
Antwort · 200
{
  "tags": [
    { "name": "promo", "links": 214, "clicks": 90211 },
    { "name": "newsletter", "links": 58, "clicks": 20473 }
  ],
  "total": 2
}
Fehler Nur die, die jeder Endpunkt zurückgeben kann.

Domains

Wo deine Kurzlinks zu Hause sein können.

GET /domains

Die Domains, die du verwenden kannst

Die pik.li-Domains, die dein Tarif erlaubt, und deine verifizierten eigenen Domains, welche davon die Standard-Domain ist und wie viele eigene Domains dein Tarif erlaubt. Übergib hostname als domain, wenn du einen Link erstellst.

Anfrage
curl https://pik.li/api/v1/domains \
  -H "Authorization: Bearer $PIKLI_KEY"
Antwort · 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 }
}
Fehler Nur die, die jeder Endpunkt zurückgeben kann.

WebhooksTarife: Premium · Business

Verwalte aus deinem Code die Webhooks, die du sonst im Dashboard auf der Seite „Webhooks“ einrichten würdest. Wie die Zustellungen funktionieren, steht weiter unten im Abschnitt „Webhooks“.

GET /webhooks

Webhooks auflisten

Deine Webhooks und die Ereignisse, die du abonnieren kannst. Das Secret wird nach der Erstellung nie wieder angezeigt.

Anfrage
curl https://pik.li/api/v1/webhooks \
  -H "Authorization: Bearer $PIKLI_KEY"
Antwort · 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

Einen Webhook abrufen

Der Webhook mit seinen letzten 20 Zustellungen und dem Status, mit dem dein Server jeweils geantwortet hat.

Parameter
  • id integer im Pfad Pflicht

    Die id des Webhooks.

Anfrage
curl https://pik.li/api/v1/webhooks/17 \
  -H "Authorization: Bearer $PIKLI_KEY"
Antwort · 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

Einen Webhook erstellen

Die Antwort enthält das secret, mit dem die Zustellungen signiert werden, und zwar nur dieses eine Mal: Speichere es sofort.

Parameter
  • url string JSON-Body Pflicht

    Die Adresse, die die Zustellungen empfängt, http:// oder https://, aus dem Internet erreichbar.

  • events array | string JSON-Body

    Die Ereignisse, die du empfangen willst, als Array oder kommagetrennt. Ohne Angabe: alle. Siehe die Liste der Ereignisse weiter unten.

  • active boolean JSON-Body

    false, um ihn pausiert anzulegen. Standard true.

Anfrage
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"]}'
Antwort · 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

Einen Webhook ändern

Sende nur, was sich ändert: Adresse, Ereignisse oder active, um Zustellungen zu pausieren und fortzusetzen.

PUT wird ebenfalls akzeptiert, mit denselben Parametern.

Parameter
  • id integer im Pfad Pflicht

    Die id des Webhooks.

  • url string JSON-Body

    Eine neue Adresse.

  • events array | string JSON-Body

    Die neue Liste der Ereignisse; sie darf nicht leer sein.

  • active boolean JSON-Body

    false pausiert die Zustellungen, true setzt sie fort.

Anfrage
curl -X PATCH https://pik.li/api/v1/webhooks/17 \
  -H "Authorization: Bearer $PIKLI_KEY" \
  -H "Content-Type: application/json" \
  -d '{"active":false}'
Antwort · 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

Einen Webhook löschen

Die Zustellungen hören auf, und ihr Verlauf wird mit gelöscht.

Parameter
  • id integer im Pfad Pflicht

    Die id des Webhooks.

Anfrage
curl -X DELETE https://pik.li/api/v1/webhooks/17 \
  -H "Authorization: Bearer $PIKLI_KEY"
Antwort · 204
Kein Body: Der Status sagt alles.
POST /webhooks/:id/test

Eine Test-Zustellung senden

Stellt einen ping an die Webhook-Adresse in die Warteschlange, signiert wie jede Zustellung: der schnellste Weg, deinen Code zur Signaturprüfung zu testen. Er geht kurz darauf raus und erscheint in GET /webhooks/:id.

Parameter
  • id integer im Pfad Pflicht

    Die id des Webhooks.

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

WebhooksTarife: Premium · Business

Ein Webhook ist eine Adresse auf deinem Server, die pik.li mit einem POST aufruft, wenn in deinem Konto etwas passiert. Du legst ihn im Dashboard auf der Seite „Webhooks“ an oder über die Webhook-Endpunkte.

Ereignisse

ping
Die Test-Zustellung, die gesendet wird, wenn du im Dashboard auf „Testen“ klickst oder POST /webhooks/:id/test aufrufst.
link.created
Ein Link wurde erstellt, im Dashboard oder über die API. demnächst
link.clicked
Jemand hat einen deiner Links angeklickt. demnächst
link.disabled
Ein Link wurde deaktiviert, von dir oder von der Moderation. demnächst
link.deleted
Ein Link wurde gelöscht. demnächst
Die Link-Ereignisse lassen sich in einem Webhook bereits auswählen, ihre Zustellung hat aber noch nicht begonnen: Derzeit sendet pik.li nur den Test-Ping.

So sieht eine Zustellung aus

Ein POST mit einem JSON-Body aus drei Feldern: event, der Name des Ereignisses; created_at, wann die Zustellung gesendet wurde; data, die Details des Ereignisses. Dazu kommen diese Header:

Was dein Server empfängt
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
immer application/json.
X-Pikli-Event
der Name des Ereignisses, wie im Body: So kannst du die Anfrage zuordnen, bevor du den Body liest.
X-Pikli-Signature
sha256=, gefolgt von der Signatur des Bodys, wie unten beschrieben.

Die Signatur prüfen

Jede Zustellung ist mit dem Secret ihres Webhooks signiert, das du ein einziges Mal erhältst, wenn du ihn erstellst. Der Header X-Pikli-Signature enthält sha256=, gefolgt vom HMAC-SHA256 des unveränderten Bodys in Hexadezimalschreibweise. Berechne denselben HMAC über die empfangenen Bytes – bevor du sie als JSON parst, denn das würde sie verändern – und vergleiche die beiden Strings in konstanter Zeit. Stimmen sie nicht überein, antworte mit 401 und ignoriere die Anfrage.

Zum Testen deines Codes: Mit dem Secret ec307f56cfac71856a10d641eee7dd58e0eb8286 aus dem Beispiel oben ergibt der Body der hier gezeigten Zustellung genau die Signatur in ihrem Header.

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

Antworten, Wiederholungen und Fehlschläge

  • Antworte so schnell wie möglich mit einem beliebigen 2xx-Status und erledige die langsame Arbeit danach.
  • Kann pik.li deine Adresse überhaupt nicht erreichen – Netzwerkfehler, abgelehnte Verbindung, eine nicht öffentliche Adresse –, wird die Zustellung bis zu 5-mal versucht, mit immer längeren Pausen: Der letzte Versuch erfolgt etwa sechs Minuten nach dem ersten.
  • Eine Antwort mit einem Status ab 400 wird protokolliert und nicht wiederholt. Sie erhöht den failures_count des Webhooks um eins; der Zähler springt mit der ersten erfolgreichen Zustellung wieder auf null.
  • Die letzten 20 Zustellungen mit dem Status, mit dem dein Server geantwortet hat, findest du in GET /webhooks/:id und im Dashboard auf der Seite „Webhooks“.
  • Die Adresse muss öffentlich sein: pik.li ruft keine privaten oder lokalen Netzwerke auf. Verwende https://, damit die Zustellungen verschlüsselt übertragen werden.

OpenAPI

Dieselbe Referenz in maschinenlesbarer Form ist unter /openapi.json veröffentlicht (OpenAPI 3.1). Importiere sie in Postman, Insomnia oder Bruno oder erzeuge mit openapi-generator einen Client.

Fehlt etwas, oder funktioniert etwas anders, als diese Seite sagt? Schreib an [email protected].