API-Referenz

REST API
Bau auf deinen Links auf.

Authentifiziere dich mit einem API-Token, liste Links auf und erstelle sie, lies Besuche und abonniere Webhooks.

Basis-URL https://letl.ink/api/v1 Nur HTTPS

Tarife Growth Ultimate
Auf dieser Seite

Erste Schritte

Die REST-API ist bei Growth und Ultimate verfügbar. Anfragen gehen an https://letl.ink/api/v1 über HTTPS. Ein Workspace mit Free oder Solo erhält von /api/v1 die Antwort 403 plan_required; der MCP-Endpunkt ist separat an Ultimate gebunden.

  1. Öffne Integrationen → REST API (nur für Workspace-Inhaber und Admins).
  2. Generiere ein Token. Es läuft nicht ab.
  3. Bewahre es wie ein Passwort auf. Ein Token gilt für die REST-API, den MCP-Server und jede mit diesem Workspace verbundene Automatisierungsplattform – wenn du es neu erzeugst, werden alle gleichzeitig ungültig.

Mitglieder können ein vorhandenes Token kopieren, wenn eines generiert wurde; nur Inhaber und Admins können es rotieren.

Authentifizierung

Sende das Token als X-API-KEY oder als Authorization: Bearer. Beides wird auf /api/v1 und auf /mcp akzeptiert. Eine Anfrage ganz ohne Token gibt 401 unauthenticated zurück.

GET Wer bin ich?
curl -H "X-API-KEY: YOUR_TOKEN" \
  https://letl.ink/api/v1/auth/me

Entsprechender Header: Authorization: Bearer YOUR_TOKEN

Konventionen

Diese Punkte gelten für alle Endpunkte unten.

Antwort-Envelope

Jede erfolgreiche Antwort ist ein JSON-Objekt mit einem data-Schlüssel und einem meta-Schlüssel, wenn es etwas zum Ergebnis zu sagen gibt – Paginierung, der aufgelöste Datumszeitraum, ob Geodaten enthalten sind. Jeder Fehler ist ein Objekt mit einem error-Schlüssel, der einen stabilen code, eine lesbare message und manchmal details enthält. Verzweige anhand von code, nie anhand des Nachrichtentexts.

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

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

Paginierung

Listen-Endpunkte akzeptieren limit (1–200, Standard 50) und offset. Der meta-Block meldet total, has_more und next_offset, sodass du ohne Rechnen paginieren kannst: Ruf weiter mit offset = meta.next_offset auf, bis has_more false ist.

Alle Links durchblättern
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

Sortieren, Filtern und Suchen

GET /links akzeptiert sort (created_at, clicks, short_code, title, updated_at), direction (asc/desc), search (durchsucht Kurzcode, Titel und Ziel), type, archived, favorite und domain. Unbekannte Werte werden nicht stillschweigend ignoriert, sondern mit invalid_parameter und einer Liste der erlaubten Werte abgelehnt.

Datumszeiträume

start_date und end_date verwenden YYYY-MM-DD und schließen beide Tage ein. Ein Zeitraum wird auf die Aufbewahrungsgrenze deines Tarifs begrenzt — fragt ein Tarif mit 30 Tagen Aufbewahrung ein Jahr Verlauf ab, erhält er die vorhandenen 30 Tage, und meta.date_range nennt den tatsächlich verwendeten Zeitraum.

Rate Limits

600 Anfragen pro Minute und Konto, im gleitenden Zeitfenster. Jede Antwort enthält X-RateLimit-Limit, X-RateLimit-Remaining und X-RateLimit-Reset (ein Unix-Zeitstempel). Wird das Limit überschritten, kommt 429 rate_limited mit einem Retry-After-Header in Sekunden zurück. Warte gemäß Retry-After, statt sofort erneut zu versuchen.

Header in jeder Antwort
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 586
X-RateLimit-Reset: 1789000816

Versionierung

Der Pfad enthält die Version. Ein neues Feld in einer Antwort oder ein neuer optionaler Parameter ist keine Breaking Change und kann jederzeit kommen – parse also defensiv und ignoriere Schlüssel, die du nicht kennst. Alles, was ein bestehendes Feld ändern oder entfernen würde, erscheint unter einem neuen Versionspräfix.

Endpoints

Konto

GET /me Verbindungstest

Identität, Workspace, Tarif und alle Berechtigungen, die dieser Tarif mitbringt. Der einfachste Weg, ein Token zu prüfen und herauszufinden, was das Konto darf, bevor du es versuchst.

GET cURL
curl -H "X-API-KEY: YOUR_TOKEN" \
  https://letl.ink/api/v1/me
Antwort 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

Wie viel von jedem Kontingent in diesem Abrechnungsmonat verbraucht ist. Nutze das, um Kunden zu warnen, bevor sie die Grenze für erfasste Klicks erreichen — danach funktioniert die Weiterleitung weiter, aber Klicks werden nicht mehr erfasst.

Antwort 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

Eigene Domains im Workspace und ob jede davon die DNS-Verifizierung abgeschlossen hat. Nur verifizierte Domains dürfen beim Erstellen eines Links als Feld domain verwendet werden.

GET /integrations

Alle Integrationen, die das Produkt bietet, ob dein Tarif sie enthält, ob dieser Workspace sie verbunden hat und wie die Zustellung läuft – letzter Status, letzter Fehler und Anzahl aufeinanderfolgender Fehlschläge. Nützlich zur Überwachung vieler Workspaces.

Links

Analysen

GET /analytics/summary

Die wichtigsten Kennzahlen für den gesamten Workspace: Klicks insgesamt und im Zeitraum, eindeutige Besucher, QR-Scans, stärkster Tag sowie Verteilungen nach Tageszeit und Wochentag. Akzeptiert days (Standard 30, begrenzt auf deine Aufbewahrungsdauer).

Antwort 200 (gekürzt)
{
  "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

Tägliche Klickzahlen, bereit fürs Diagramm. Übergib link_id, um auf einen Link einzugrenzen, und days, um den Zeitraum festzulegen. Tage ohne Traffic werden als null zurückgegeben statt ausgelassen, sodass die Reihe immer lückenlos ist.

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

Klicks gruppiert nach einer Dimension, mit dem Anteil jeder Zeile am Gesamtwert.

Query-Parameter

  • dimension string

    channel, referrer, device, browser, os, brand, variation oder country. browser, os, brand und variation erfordern eine link_id.

    Erforderlich
  • link_id integer

    Auf einen Link beschränken. Weglassen für den ganzen Workspace.

    Optional
  • limit integer

    1–100. Standard ist 10.

    Optional
Antwort 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 Paginiert

Der Roh-Feed aller Besuche über alle Links im Workspace, neueste zuerst. Das ist der moderne Ersatz für GET /visits: dieselben Daten, Standardhülle, Unterstützung für limit und offset.

Antwort 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

In diesem Konto registrierte Webhook-Endpunkte.

POST /hooks

Registriere einen HTTPS-Endpunkt, der bei jedem erfassten Klick einen JSON-Payload erhält. Bis zu 25 pro Konto. Fehlgeschlagene Zustellungen werden automatisch wiederholt.

JSON-Body

  • target_url

    Muss mit https:// beginnen.

    Erforderlich
  • event

    Standardmäßig link_visited, derzeit das einzige Event.

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

Einen Webhook-Endpunkt entfernen. POST /hooks/subscribe und DELETE /hooks/unsubscribe/{id} bleiben für den REST-Hook-Ablauf von Zapier verfügbar.

Veraltet

GET /auth/me Veraltet

Der ursprüngliche Verbindungstest: liefert deine Nutzer-ID, E-Mail-Adresse sowie Workspace-ID und -Name als flaches Objekt. GET /me liefert dieselbe Identität plus deine Tariflimits und Feature-Flags und ist für neuen Code die bessere Wahl.

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

Die 50 neuesten Besuche im gesamten Workspace als reines JSON-Array – ohne Hülle – mit den Feldnamen visit_id, visit_time, link_shortcode und visitor_*. Diesen Schritt „perform list“ liest Zapiers REST Hook, daher ist das Format eingefroren. Nutze für neue Projekte GET /events.

POST /hooks/subscribe Zapier

REST-Hook-Abonnement. Nimmt target_url und optional event (Standard link_visited) entgegen und gibt das erstellte Abonnement flach ohne Envelope zurück. POST /hooks ist dieselbe Operation im Standardformat.

DELETE /hooks/unsubscribe/{id} Zapier

REST-Hook-Abmeldung, gibt {"success": true} zurück. DELETE /hooks/{id} ist das Gegenstück im Standardformat.

Fehler

  • 400 invalid_request

    Der Body war kein JSON-Objekt, oder ein Pflichtfeld fehlte.

  • 400 invalid_parameter

    Ein Query-Parameter war fehlerhaft oder außerhalb des erlaubten Bereichs. details.parameter nennt den Verursacher.

  • 401 unauthenticated

    Es wurde kein Token gesendet. Füge X-API-KEY oder Authorization: Bearer hinzu.

  • 401 invalid_token

    Das Token passt zu keinem Konto. Möglicherweise wurde es neu erzeugt.

  • 403 plan_required

    Der Tarif des Kontos enthält diesen Endpunkt oder diese Funktion nicht. details.upgrade_url verweist auf die Abrechnung.

  • 403 forbidden

    Authentifiziert, aber nicht berechtigt — zum Beispiel, wenn ein Mitglied eine Aktion versucht, die nur dem Inhaber vorbehalten ist.

  • 404 not_found

    Diese Ressource gibt es in diesem Workspace nicht. IDs aus einem anderen Workspace werden als nicht gefunden statt als verboten gemeldet.

  • 409 conflict

    Die Anfrage kollidiert mit dem aktuellen Zustand – das Webhook-Limit ist erreicht oder das Konto hat noch keinen Workspace.

  • 422

    Wird von POST /links/bulk zurückgegeben, wenn jede Zeile im Batch fehlgeschlagen ist. Prüfe meta.errors.

  • 429 rate_limited

    Über 600 Anfragen in der letzten Minute. Warte Retry-After Sekunden.

Die Legacy-Endpunkte GET /visits und GET /links/{id}/visits liefern ein reines JSON-Array mit eigenen Feldnamen und sind für die bereits darauf aufbauenden Zapier-Integrationen eingefroren. Neuer Code sollte GET /events und GET /links/{id}/events verwenden – sie nutzen die Standardhülle und unterstützen Paginierung.

Noch Fragen?

Wir sind für dich da. Melde dich, und wir antworten so schnell wie möglich.