REST · JSON · Server-Keys

Lotse, aufgerufen von deinem Server.Aus PHP, Node, Python oder mit curl.

Dein Backend legt Links an, sendet Kampagnen und Pushes an einzelne Nutzer, meldet Events und löscht Nutzer. REST mit JSON über HTTPS, mit einem Server-Key, der nur kann, was seine Scopes erlauben.

Als NächstesGebaut und getestet. Die Abnahme auf der Produktionsinstanz steht noch aus.

Was dein Backend kann.Jedes mit eigenem Scope.

Links

Einen Deep Link je Produkt, Artikel oder Einladung anlegen, mit seiner URL lesen und wieder löschen. Wie viele Links ein Projekt haben darf, hängt vom Tarif ab, in Free 5.000.

Kampagnen

Eine Kampagne für ein gespeichertes Segment anlegen, sofort senden oder planen und ihren Report lesen. Dieselben Kampagnen, Quoten und Reports wie in der Console.

Pushes an einen Nutzer

Ein Push an einen Nutzer oder eine Installation, etwa „Deine Bestellung ist unterwegs“, mit dem Screen deiner App, den er öffnet.

Server-Events

Melden, was dein Backend zuerst weiß, etwa einen Kauf oder eine Verlängerung. Funnels, Segmente und Journeys zählen es wie ein Event aus der App.

Nutzer löschen

Wenn jemand sein Konto bei dir schließt, löschst du ihn auch in Lotse, mit seinen Installationen und allem, was sie geschickt haben.

Bewusst nicht

  • Bearbeiten. Links und Nutzer lassen sich löschen, ändern lässt sich nichts.
  • Ein Push an viele Nutzer-IDs in einem Aufruf. Dafür gibt es Kampagnen.
  • Journeys und ihre A/B-Varianten. Sie leben in der Console.
  • Aufrufe aus dem Browser. Ein Key in einer Webseite wäre ein Key für alle.
  • Generierte Client-Bibliotheken. curl und die OpenAPI-Beschreibung reichen.

Die andere Richtung: Webhooks

Server-Keys

Ein Server-Key gehört zu einem System, nicht zu einer Person. Er funktioniert weiter, wenn die Kollegin, die ihn angelegt hat, das Projekt verlässt. Nur der Besitzer eines Projekts legt Keys an, rollt und widerruft sie; Mitglieder sehen sie.

  1. 1

    Developers öffnen

    In der Console dein Projekt öffnen, dann Developers und Server keys, und Create key wählen. Gib ihm einen Namen, den du wiedererkennst, etwa „Shop-Backend“. Ein Projekt hat bis zu zehn aktive Keys.

  2. 2

    Scopes und Laufzeit wählen

    Mit einer Vorlage beginnen (Links, Campaigns, Transactional pushes, Server events, User deletion) oder die Scopes selbst wählen. Sie stehen fest, sobald der Key existiert. Der Key läuft nach 90 oder 365 Tagen ab oder nie. Die Console zeigt, wann jeder Key zuletzt benutzt wurde.

  3. 3

    Einmal kopieren

    Der Key beginnt mit lt_srv_ und wird genau einmal angezeigt. Lotse behält nur einen Hash davon. Er gehört auf deinen Server, nie in eine App oder eine Webseite.

  4. 4

    Rollen oder widerrufen

    Roll erzeugt einen neuen Key mit denselben Scopes. Der alte gilt noch 24 Stunden, während du den neuen ausrollst, oder endet sofort, falls er geleakt sein könnte. Revoke beendet einen Key endgültig.

Scopes

links:read
Links lesen.
links:write
Links anlegen und löschen.
campaigns:read
Gespeicherte Segmente lesen, und Kampagnen mit ihren Reports.
campaigns:write
Kampagnen-Entwürfe anlegen.
campaigns:send
Kampagnen senden und planen.
messages:send
Einen Push an einen Nutzer oder eine Installation senden und wieder lesen.
events:write
Events vom Server melden.
users:delete
Einen Nutzer mit seinen Geräten und Daten löschen.

Anfragen

Jede Route liegt unter https://api.lotse.dev/v1/. Allein der Key bestimmt das Projekt: Kein Pfad, keine Query und kein Body nennen eines.

Ein Bearer-Token

Den Key im Header Authorization: Bearer lt_srv_… schicken. Ohne gültigen Key lautet die Antwort 401; der Public Key deiner App und Agent-Tokens sind keiner.

JSON in beide Richtungen

Bodies sind JSON-Objekte in UTF-8. Zeiten sind ISO 8601 mit Zeitzone, etwa 2026-10-20T09:00:00Z, und kommen in UTC zurück. Ein Feld, das die Route nicht kennt, wird abgelehnt, damit ein Tippfehler auffällt.

Nur von Servern

Browser schicken einen Origin-Header, und eine Anfrage damit bekommt 403, egal mit welchem Key.

Listen in Seiten

Listen kommen neueste zuerst als {"data": [...], "nextCursor": "..."}. limit setzt die Seitengröße, ohne Angabe 100, und der nextCursor einer Seite, als cursor übergeben, liefert die nächste. null heißt, es war die letzte.

Terminal
curl "https://api.lotse.dev/v1/links?limit=20" \
  -H "Authorization: Bearer $LOTSE_SERVER_KEY"

Routen

Jede Route braucht einen Scope. Jedes Feld und jede Antwort steht in der OpenAPI-3.1-Beschreibung der API.

Links

  • GET /v1/linkslinks:read

    Links, neueste zuerst.

  • GET /v1/links/{id}links:read

    Ein Link mit seiner URL.

  • POST /v1/linkslinks:write

    Legt einen Link an: slug und targetPath, optional campaign, source, medium und ein Fallback je Plattform.

  • DELETE /v1/links/{id}links:write

    Löscht einen Link.

Segmente und Kampagnen

  • GET /v1/segmentscampaigns:read

    Die gespeicherten Segmente des Projekts, um eine Kampagne zu adressieren.

  • GET /v1/campaignscampaigns:read

    Kampagnen, neueste zuerst, ohne die Varianten von Journeys.

  • GET /v1/campaigns/{id}campaigns:read

    Eine Kampagne mit Report. ?goalEvent= ergänzt, wer dieses Event binnen 24 Stunden erreicht hat.

  • POST /v1/campaignscampaigns:write

    Legt einen Entwurf an: title, body, optional linkId, und die Zielgruppe als segmentId, segment oder "audience": "all".

  • POST /v1/campaigns/{id}/sendcampaigns:send

    Sendet einen Entwurf sofort, innerhalb der 20 Sendungen je 24 Stunden, die sich API und Console teilen.

  • POST /v1/campaigns/{id}/schedulecampaigns:send

    Plant den Versand für sendAt, mindestens eine Minute und höchstens 30 Tage voraus.

Messages

  • POST /v1/messagesmessages:send

    Ein Push an eine userId oder installId: title, body, optional targetPath.

  • GET /v1/messages/{id}messages:send

    Eine Message mit dem Stand ihrer Zustellungen, und ob sie geöffnet wurde.

Events

  • POST /v1/eventsevents:write

    1 bis 100 Events deiner Nutzer, über die Nutzer-ID.

Nutzer

  • DELETE /v1/users/{userId}users:delete

    Löscht einen Nutzer mit seinen Installationen und Daten.

Beschreibung

  • GET /v1/openapi.jsonohne Key

    Die OpenAPI-3.1-Beschreibung aller Routen. Öffentlich, auch für einen Browser.

OpenAPI-3.1-Beschreibung

Beispiele

Mit curl, der Key in der Variablen LOTSE_SERVER_KEY. Jedes POST darf einen Idempotency-Key tragen, damit ein Retry nach einem Timeout nichts doppelt tut.

Kampagne senden

Das gespeicherte Segment nachschlagen, einen Entwurf dafür anlegen und senden. Ohne segmentId, segment oder "audience": "all" wird eine Kampagne abgelehnt, damit niemand aus Versehen an alle Geräte sendet.

Anfrage · curl
# The saved segments of the project, with their ids
curl https://api.lotse.dev/v1/segments \
  -H "Authorization: Bearer $LOTSE_SERVER_KEY"

# A draft for segment 7
curl https://api.lotse.dev/v1/campaigns \
  -H "Authorization: Bearer $LOTSE_SERVER_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: spring-sale-draft" \
  -d '{
    "title": "Spring sale ends tonight",
    "body": "Your basket is still waiting.",
    "linkId": 318,
    "segmentId": 7
  }'

# Send it now, or plan it with POST .../schedule and {"sendAt": "..."}
curl -X POST https://api.lotse.dev/v1/campaigns/57/send \
  -H "Authorization: Bearer $LOTSE_SERVER_KEY" \
  -H "Idempotency-Key: spring-sale-send"
Antwort · 200 OK
{
  "id": 57,
  "title": "Spring sale ends tonight",
  "body": "Your basket is still waiting.",
  "status": "sending",
  "linkId": 318,
  "segment": {
    "all": [{ "field": "platform", "op": "eq", "value": "android" }]
  },
  "createdAt": "2026-10-21T09:14:03.512Z",
  "scheduledAt": "2026-10-21T09:15:40.077Z"
}

Push an einen Nutzer

Erreicht die Installationen von u_42, die sich mit gültigem User-Hash identifiziert haben. Angetippt öffnet der Push /orders/1042.

Anfrage · curl
curl https://api.lotse.dev/v1/messages \
  -H "Authorization: Bearer $LOTSE_SERVER_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-1042-shipped" \
  -d '{
    "userId": "u_42",
    "title": "Your order has shipped",
    "body": "Order 1042 is on its way.",
    "targetPath": "/orders/1042"
  }'
Antwort · 201 Created
{
  "id": 905,
  "status": "queued",
  "userId": "u_42",
  "title": "Your order has shipped",
  "body": "Order 1042 is on its way.",
  "targetPath": "/orders/1042",
  "createdAt": "2026-10-21T09:14:03.512Z",
  "deliveries": {
    "pending": 2, "sending": 0, "sent": 0, "failed": 0, "invalidToken": 0
  },
  "opened": 0
}

Event melden

Ein Kauf, den dein Backend bestätigt hat. Mit id speichert ein zweiter Aufruf mit demselben Event nichts und zählt es unter duplicates.

Anfrage · curl
curl https://api.lotse.dev/v1/events \
  -H "Authorization: Bearer $LOTSE_SERVER_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "events": [
      {
        "name": "purchase",
        "userId": "u_42",
        "id": "order-1042",
        "ts": "2026-10-21T09:12:00Z",
        "props": { "amount": "89.90", "currency": "EUR" }
      }
    ]
  }'
Antwort · 200 OK
{ "accepted": 1, "duplicates": 0 }

Nutzer löschen

Die Antwort ist 204 ohne Body, oder 404 für einen Nutzer, den das Projekt nicht kennt.

Anfrage · curl
# The user id as the app passes it to identify, URL-encoded
curl -X DELETE https://api.lotse.dev/v1/users/u_42 \
  -H "Authorization: Bearer $LOTSE_SERVER_KEY"
Antwort · 204 No Content
HTTP/1.1 204 No Content

Pushes an einen Nutzer

Eine Message geht an genau ein Ziel: userId, die ID, die deine App an Lotse.identify übergibt, oder installId, die ID einer Installation.

userId erreicht nur geprüfte Installationen
Ohne diese Regel könnte jeder in seiner Kopie deiner App identify("u_42") aufrufen und die Bestell-Pushes von u_42 bekommen. Deshalb geht ein Push an eine userId nur an Installationen, deren identify einen gültigen User-Hash trug. Hat der Nutzer keine, lautet die Antwort 422 identity_verification_required.
installId geht immer
Eine Install-ID kennt nur dein Backend, weil deine App sie ihm mit Lotse.instance.installId gemeldet hat. Nimm sie, wenn dein Projekt Identitäten nicht prüft.
Kein Gerät ist kein Fehler
Ist keine Installation des Ziels erreichbar, lautet die Antwort trotzdem 201, mit "status": "no_devices", und der Webhook message.failed folgt. GET /v1/messages/{id} zeigt die Zustellungen und ob der Push geöffnet wurde.
SDK 0.2.0 oder neuer
Ab 0.2.0 öffnet das SDK den targetPath einer angetippten Message und meldet push_open mit ihrer messageId. Mit einem älteren SDK erscheint die Benachrichtigung trotzdem, ein Tippen öffnet nur die App.
Sichtbar wie Kampagnen
Messages stehen in der Console unter Campaigns, Tab Messages, und push_sent und push_open landen im Event-Stream. Titel und Text werden nach 30 Tagen gelöscht, die Message nach 90.

Der User-Hash

Das Identitäts-Secret (lt_is_…) erzeugst du in der Console unter Settings, Identity verification. Dein Backend berechnet den Hash für den angemeldeten Nutzer und gibt ihn der App, die ihn an identify übergibt. Das Secret kommt nie in die App.

JavaScript · Node.js
import crypto from 'node:crypto';

// HMAC-SHA256 of the user id, trimmed as Lotse stores it, keyed with the
// whole identity secret (lt_is_…), as lower-case hex.
const userHash = crypto
  .createHmac('sha256', process.env.LOTSE_IDENTITY_SECRET)
  .update(userId.trim())
  .digest('hex');
Dart · in der App
await Lotse.instance.identify(userId, userHash: userHash);

Server-Events

POST /v1/events nimmt 1 bis 100 Events je Aufruf, jedes mit name und userId, optional mit ts, id und props. Ein fehlerhaftes Event lehnt den ganzen Aufruf ab; die Meldung nennt seinen Index.

Auf der zuletzt gesehenen Installation
Lotse löst den Nutzer beim Eingang auf: Das Event landet auf seiner zuletzt gesehenen Installation, unter denen, die es zur Zeit des Events schon gab. Funnels, Segmente, Auslöser und Ziele von Journeys und Kampagnen-Conversions zählen es wie ein Event aus der App.
Nutzer ohne Installation
Eine unbekannte userId legt einen Nutzer ohne Installation an, bis zu 100.000 je Projekt. Solche Events zählen im Event-Explorer, nicht in Segmenten oder Journeys, und eine später hinzukommende Installation übernimmt sie nicht.
Zeit
ts ist optional, ohne Angabe gilt jetzt. Es darf bis zu 30 Tage zurück und 5 Minuten vor der Serveruhr liegen. Außerhalb wird der Aufruf abgelehnt, damit eine falsche Uhr sofort auffällt.
Duplikate
Gib jedem Event eine id aus bis zu 128 sichtbaren ASCII-Zeichen, etwa order-1042. Eine id, die das Projekt schon gespeichert hat, wird übersprungen und kostet nichts; die Antwort zählt sie unter duplicates.
Namen und Props
Namen haben bis zu 100 Zeichen, props sind bis zu 20 Paare aus Strings mit bis zu 200 Zeichen, wie im SDK. Server-Events zählen gegen dieselben 500.000 Events je Projekt und Tag.

Namen, die Lotse und sein SDK selbst schreiben, sind reserviert:

  • session_start
  • identify
  • push_sent
  • push_open
  • link_click
  • attribution_match
  • survey_answered
  • feedback_submitted
  • feature_voted
  • feature_unvoted
  • rage_tap

Einen Nutzer löschen

DELETE /v1/users/{userId} tut dasselbe wie der Löschen-Knopf in der Console, in einer Transaktion. Auch ein gesperrtes Projekt kann löschen.

Was gelöscht wird

  • der Nutzer mit seinen Attributen, den Events, die dein Backend für ihn gemeldet hat, und seinen Messages
  • jede Installation, die als dieser Nutzer identifiziert ist, mit ihren Events, Feedback und Antworten, Screenshots und Replays, Stimmen, Umfrageantworten, Heatmap-Taps, dem ihr zugeordneten Link-Klick, Push-Tokens, Zustellungen und Journey-Einträgen
  • der Inhalt der Webhook-Ereignisse der letzten 7 Tage, die den Nutzer oder eine dieser Installationen nennen, gesendet oder nicht

Was bleibt

Eine Installation, die seitdem als jemand anderes identifiziert ist, bleibt mit allem, was sie geschickt hat. Ideen auf dem Feature-Board bleiben, ohne diese Stimmen. Der Webhook user.deleted sagt es deinen anderen Systemen, mit Nutzer-ID und Zeit und sonst nichts.

Retries ohne Doppel

Schick zu jedem POST den Header Idempotency-Key mit: 1 bis 255 druckbare ASCII-Zeichen, einer je Arbeitsschritt, etwa order-1042-shipped. Lotse merkt ihn sich 24 Stunden je Server-Key.

Ein Retry bekommt die erste Antwort

Derselbe Key mit derselben Anfrage liefert die gespeicherte Antwort mit dem Header Idempotent-Replayed: true, und nichts passiert doppelt.

Eine andere Anfrage bekommt 409

Derselbe Key mit anderem Pfad oder Body ist ein Konflikt. Läuft die erste Anfrage noch, kommt die 409 mit Retry-After.

429 und 503 geben den Key frei

Ein Retry mit demselben Key läuft erneut; nach einer 503 erledigt er, was fehlt, und nichts doppelt. Nach einer 500 oder einem Timeout läuft nichts ein zweites Mal: Der Retry bekommt die gespeicherte Antwort oder 409, mit Retry-After, solange die erste Anfrage noch laufen kann, danach mit dem Hinweis, dass ihr Ausgang unbekannt ist. Dann die Ressource per GET prüfen und mit neuem Key senden.

Fehler

Jede Antwort außerhalb von 2xx ist JSON mit einem Code und einem englischen Satz für dich, nie mit einem Stacktrace.

JSON · 403 Forbidden
{
  "error": "forbidden",
  "message": "This server key lacks the scope messages:send. Scopes are fixed when a key is created: create a key with it in the Lotse console."
}
400invalid_input
Ein Feld fehlt, ist unbekannt oder hat einen falschen Typ oder Wert. Die Meldung nennt das Feld.
401unauthorized
Kein Key, oder einer, der unbekannt, widerrufen oder abgelaufen ist.
403forbidden
Dem Key fehlt der Scope der Route, und die Meldung nennt ihn. Oder die Anfrage kam aus einem Browser.
403project_suspended
Das Projekt ist gesperrt. Lesen und Löschen gehen weiter.
404not_found
Diesen Pfad gibt es nicht, oder nichts mit dieser ID in deinem Projekt.
405method_not_allowed
Der Pfad nimmt andere Methoden; der Header Allow nennt sie.
409conflict
Im aktuellen Zustand nicht möglich, etwa ein vergebener Slug, oder ein Idempotency-Key, der für eine andere Anfrage benutzt wurde.
413invalid_input
Der Body ist größer als 64 KiB, bei /v1/events größer als 256 KiB.
422identity_verification_required
Ein Push an eine userId ohne geprüfte Installation.
429quota_exceeded
Eine Quote des Projekts, dieselbe wie in der Console, etwa die Links seines Tarifs, in Free 5.000. Kommt ohne Retry-After.
429rate_limited
Ein Budget an Aufrufen oder Schreibaufrufen ist aufgebraucht. Retry-After nennt die Sekunden bis zum nächsten Versuch.
500internal_error
Ein Fehler auf dem Lotse-Server. Später erneut versuchen.
503unavailable
Arbeit, die gerade nicht fertig wurde, etwa der Start eines Versands. Dieselbe Anfrage nach Retry-After wiederholen: Sie erledigt, was fehlt, und nichts doppelt.

Jeder Aufruf mit gültigem Key ohne 2xx bleibt 7 Tage in der Console, unter Developers, Request log: Zeit, Key, Methode, Pfad, Status, Code und Dauer, nie Body oder Header. Ablehnungen durch ein Budget nur stichprobenartig. Eine Nutzer-ID im Pfad wird nicht gespeichert.

Grenzen

Großzügig für ein Backend, eng genug, dass eines in einer Schleife gebremst wird. Über einer Grenze lautet die Antwort 429.

600 Aufrufe je Stunde

Je Key. Abgelehnte und gescheiterte Aufrufe zählen mit.

60 Schreibaufrufe je Stunde

Je Key, innerhalb der 600: jedes POST und DELETE, das seine Route erreicht. Ein Retry, den sein Idempotency-Key beantwortet, kostet keinen.

6.000 Aufrufe je Stunde

Je Projekt, geteilt von allen seinen Keys.

64 KiB je Anfrage

Und 256 KiB für /v1/events, Platz für 100 typische Events.

Die Quoten des Projekts

Links je Tarif, in Free 5.000. 20 Kampagnen-Sendungen in 24 Stunden, gemeinsam mit der Console. 10 Messages je Nutzer und Stunde, 10.000 je Projekt und Tag. 100.000 Nutzer ohne Installation.

Gesperrt heißt lesen und löschen

Solange ein Projekt gesperrt ist, lesen und löschen seine Keys weiter. Alles, was anlegt oder sendet, bekommt 403 project_suspended.

Kurs setzen.Das erste Projekt in zehn Minuten.

Kostenlos während der Beta. Registrieren, Projekt anlegen, SDK einbinden.