Eigene HTTP-Quellen

Ein KPI aus jeder JSON-API: URL, Header, Datums-Tokens, die JSONPath-Syntax, die Freigabe, der Testlauf und jede Fehlermeldung, die auftauchen kann.

Wenn kein Connector den Dienst abdeckt, den du messen willst, richtest du mit der Quelle HTTP selbst einen JSON-Endpunkt ein. Du gibst eine URL an, bei Bedarf ein paar Request-Header, und einen Pfad in die Antwort. Pulsaria ruft den Endpunkt ab, läuft den Pfad entlang und speichert die gefundene Zahl als heutigen Wert.

Mehr als ein GET-Request gegen eine JSON-API ist das nicht. Braucht deine Zahl ein POST, einen Login-Ablauf oder etwas Textverarbeitung, nimm Shell-Befehle.

Einen HTTP-KPI anlegen

  1. Dashboard → + KPI im Kopf einer Gruppe. Auf einem leeren Dashboard: KPI anlegen.
  2. Die Karte HTTP wählen — „Jeder HTTP-Endpunkt mit einer Zahl in der JSON-Antwort. Braucht deine Freigabe.“
  3. URL ausfüllen, bei Bedarf Header-Zeilen ergänzen, JSONPath eintragen.
  4. Test ausführen drücken und den ermittelten Wert prüfen.
  5. Freigeben drücken und den Dialog bestätigen.
  6. Optional einen Cron-Zeitplan setzen.
  7. Den Abschnitt Ziel darunter ausfüllen — Name, Zielwert, Einheit, Richtung, KPI-Typ, Zeitraum — und KPI anlegen drücken.

Später änderst du das über die Detailseite des KPI → Bearbeiten. Es ist dasselbe Formular, nur mit umschaltbarer Quelle.

KPI anlegen und Änderungen speichern sind ohne Lizenz oder aktive Testphase deaktiviert; der Button trägt dann den Tooltip „Lizenz oder aktive Testversion nötig“. Test ausführen ist davon nicht betroffen.

Alle vier Code-Felder — URL, Header-Name, Header-Wert, JSONPath — räumen die macOS-Textersetzung für dich auf. Typografische Anführungszeichen, Gedankenstriche und werden in ihre ASCII-Entsprechungen zurückverwandelt, sobald du das Feld verlässt, und noch einmal bei jedem Test, jeder Freigabe und jedem Speichern. Den bereinigten Wert siehst du direkt im Feld.

Die URL

Eine Zeile, Monospace, Pflichtfeld. Der Platzhalter zeigt die gemeinte Form:

https://api.example.com/metrics?from={{firstDayOfMonth}}&to={{lastDayOfMonth}}

Die Regeln, unter denen der Request läuft — alle fest verdrahtet:

RegelWert
Methodeimmer GET
Schematanur http:// und https://
Timeout10 Sekunden
Redirectshöchstens 5, und ein Redirect, der http/https verlässt, wird nicht gefolgt
Antwortgrößebis 64 KB werden gelesen, der Rest wird abgeschnitten
User-Agentpulsaria/<App-Version>

Nichts davon lässt sich einstellen. Es gibt keine Proxy-Einstellung, keine eigene CA und keine Möglichkeit, das Timeout zu erhöhen.

Pulsaria schickt den Request aus der nativen Schicht, nicht aus der Web-View der App. Eine API, die Browser-Origins blockt (CORS), ist deshalb kein Problem — es gibt aber auch keinen Cookie-Speicher, keinen Schlüsselbund-Zugriff und keine Browser-Session, auf der du mitreiten könntest. Was den Request authentifiziert, muss in der URL oder in einem Header stehen.

Pulsaria baut und maskiert den Query-String nicht für dich. Schreib ihn genau so, wie die API ihn erwartet, inklusive aller %-Escapes.

Optional. Der Block Header startet leer mit der Zeile „Keine Header.“ Mit + Header fügst du eine Zeile aus Name und Wert hinzu, mit dem X rechts entfernst du sie wieder. Die Platzhalter zeigen Authorization und Bearer ….

Hier kommt ein API-Token hin. Zwei übliche Formen:

Header-NameHeader-Wert
AuthorizationBearer sk_live_abc123…
X-API-Keyabc123…

Welche deine API will, steht in ihrer Dokumentation — Pulsaria schickt wörtlich das, was du einträgst.

Zeilen ohne Namen werden beim Speichern verworfen, eine halb ausgefüllte Zeile schadet also nicht. Header-Namen werden getrimmt, Header-Werte exakt so gespeichert, wie du sie tippst, Leerzeichen inklusive.

Lies Wo Header-Werte gespeichert werden, bevor du ein Token einfügst. Es landet nicht im macOS-Schlüsselbund.

Datums-Tokens

Unter dem URL-Feld liegt eine Reihe von Chips mit der Überschrift Datums-Tokens. Ein Klick fügt {{tokenName}} an der Cursorposition im URL-Feld ein. Jeder Chip zeigt, wozu er sich gerade auflöst; beim Überfahren erscheint die ausführliche Beschreibung.

Tokens werden beim Abruf aufgelöst, nicht beim Speichern. Genau darum geht es: Eine URL, die auf &from={{firstDayOfMonth}}&to={{lastDayOfMonth}} endet, gibst du einmal frei und sie bleibt freigegeben, während der Kalender weiterläuft.

TokenLöst auf zu
{{today}}heutiges Datum
{{yesterday}}gestriges Datum
{{last7DaysAgo}}das Datum vor 7 Tagen
{{last28DaysAgo}}das Datum vor 28 Tagen
{{last30DaysAgo}}das Datum vor 30 Tagen
{{firstDayOfMonth}}der 1. des aktuellen Monats
{{lastDayOfMonth}}der letzte Tag des aktuellen Monats
{{firstDayOfLastMonth}}der 1. des Vormonats
{{lastDayOfLastMonth}}der letzte Tag des Vormonats
{{firstDayOfQuarter}}der erste Tag des aktuellen Quartals
{{lastDayOfQuarter}}der letzte Tag des aktuellen Quartals
{{firstDayOfYear}}der 1. Januar des laufenden Jahres
{{lastDayOfYear}}der 31. Dezember des laufenden Jahres
{{now}}aktueller Zeitstempel als ISO 8601, z. B. 2026-07-25T07:00:00.000Z
{{nowUnix}}aktueller Unix-Zeitstempel in Sekunden

Alles oberhalb von {{now}} ergibt ein schlichtes YYYY-MM-DD, gebildet aus dem lokalen Kalendertag deines Macs.

Was du dazu wissen solltest:

  • Tokens funktionieren in der URL und in Header-Werten. Header-Namen werden nie ersetzt.
  • Die Chips schreiben ausschließlich ins URL-Feld. Für ein Token in einem Header-Wert tippst du es von Hand.
  • Leerzeichen in den Klammern sind erlaubt: {{ today }} funktioniert.
  • Ein unbekannter Name bleibt stehen. {{foo}} wird wörtlich mitgeschickt — so überlebt ein legitimes {{…}} in einem URL-Pfad.
  • Es gibt keine Rechenoperationen und kein eigenes Format. Diese 15 Tokens sind alles: kein {{today-3}}, kein TT.MM.JJJJ.

Der JSONPath

Pflichtfeld, eine Zeile, Platzhalter data.items[0].value. Der Hilfetext unter dem Feld lautet „Punkt-/Bracket-Pfad in der JSON-Antwort. Muss eine Zahl ergeben.“

Das ist ein kleiner, bewusst einfacher Pfad-Läufer, keine JSONPath-Implementierung. Er versteht:

BausteinBeispielBedeutung
Punkt-Segmenttotals.visitorsein Schlüssel in einem Objekt
Numerischer Indexitems[0]ein Element eines Arrays
Verkettetdata.rows[2][0]beliebige Mischung aus beidem
Führender Index[0].valuewenn die Antwort selbst ein Array ist
Schlüssel in Anführungszeichenmetrics["sessions.total"]ein Schlüssel, der einen Punkt oder eine Klammer enthält; '…' geht auch

Leerzeichen in Klammern werden ignoriert, items[ 0 ] ist also in Ordnung. Klammerinhalt, der keine ganze Zahl ist, gilt als Schlüssel — mit oder ohne Anführungszeichen: data[total] bedeutet dasselbe wie data.total.

Was er nicht versteht:

  • Keine Wildcards (*), keine Filter ([?(@.x>1)]), keine Slices ([0:3]).
  • Kein führendes $. $.totals.visitors sucht einen Schlüssel, der buchstäblich $ heißt, und findet nichts.
  • Keine rekursive Suche. Ein doppelter Punkt wird schlicht ignoriert, data..value verhält sich also exakt wie data.value — es wird nichts durchsucht.
  • Keine Aggregation. Du bekommst genau ein Element; Summe, Anzahl oder Mittelwert über ein Array gibt es hier nicht. (Connectoren können das intern, nach außen ist es nicht verfügbar.)
  • Negative Indizes praktisch nicht. [-1] wird zwar geparst, aber ein JavaScript-Array hat kein Element -1, also löst es nie auf. „Das letzte Element“ lässt sich nicht ausdrücken.
  • Keinen Schlüssel, der ein ] enthält — der Parser beendet die Klammer beim ersten.

Wie die Antwort aussehen muss

Vier Dinge müssen stimmen, in dieser Reihenfolge:

  1. Der Status ist 2xx. Alles andere scheitert mit HTTP <Status>.
  2. Der Body ist gültiges JSON. HTML-Fehlerseiten, XML, CSV, JSONL und Klartext scheitern mit „Antwort ist kein gültiges JSON“.
  3. Der Pfad löst auf. Ein fehlender Schlüssel, ein fehlendes Array-Element oder ein null unterwegs beendet den Lauf.
  4. Der Wert ist eine Zahl oder ein String, der sich als Zahl lesen lässt. 4821, "4821", "12.5" und "1e3" funktionieren. "1,284" nicht — ein Tausendertrennzeichen macht den String unlesbar. true, null, Objekte und Arrays werden abgelehnt.

Die Zahl wird genau so gespeichert, wie sie ankommt. Pulsaria rechnet nichts um: keine Transformation, keine Rundung, kein /100, keine Währungsumrechnung. Liefert eine API Cent, steht dein KPI in Cent, solange du den Endpunkt nicht änderst.

Zwei Folgen, mit denen du planen solltest:

  • Eine Antwort über 64 KB wird bei 64 KB abgeschnitten, was das JSON praktisch immer zerlegt. Liefert dein Endpunkt eine lange Liste, grenze sie über Query-Parameter ein.
  • Ein leeres Ergebnis ist ein Fehler, keine Null. Antwortet die API an einem Tag ohne Daten mit [] oder {"results":[]}, scheitert der Abruf und es wird kein Wert geschrieben. Connector-KPIs behandeln diesen Fall als 0, eigene HTTP-Quellen nicht.

Der Testlauf

Drück Test ausführen — die Beschriftung wechselt auf „Läuft…“. Der Test schickt den echten Request mit aufgelösten Datums-Tokens und zeigt das Ergebnis in einem Panel. Geschrieben wird dabei nichts: kein KPI-Wert, keine Zeile im Fetch-Log, keine Warnung.

Das Panel zeigt von oben nach unten:

  • Eine Kopfzeile Ermittelter Wert mit Vorschau: <Zahl> in Grün — oder Test fehlgeschlagen in Rot.
  • URL — die URL, wie sie tatsächlich rausging, mit eingesetzten Tokens, dazu ein Kopieren-Button.
  • Antwort — bis zu 4096 Zeichen. Ab 400 Zeichen oder 6 Zeilen gibt es Mehr anzeigen / Weniger.
  • Fehler — die Meldung, falls es eine gibt.
  • Eine Meta-Zeile: Methode (GET), HTTP-Status und Dauer in ms.

Deine Header-Werte tauchen im Panel nicht auf. Änderst du URL, eine Header-Zeile oder den JSONPath, wird das Ergebnis gelöscht — ein veraltetes grünes Panel kann dich also nicht in die Irre führen.

Nutz das Panel auch andersherum: Wenn ein Pfad nicht trifft, lies den Abschnitt Antwort, such die Zahl mit dem Auge und schreib den Pfad dorthin.

Die Freigabe

Ein HTTP-KPI ist eine URL, die Pulsaria selbstständig und wiederholt aufruft. Deshalb läuft er erst, wenn du zu genau dieser Konfiguration Ja gesagt hast.

Solange der KPI nicht freigegeben ist, sitzt über dem URL-Feld ein bernsteinfarbenes Banner Freigabe erforderlich: „Auto-KPIs laufen erst, wenn du sie freigibst. Teste den Befehl unten und klicke dann auf „Freigeben“. Ohne Freigabe überspringt der Scheduler diesen KPI.“ (Das Banner sagt „Befehl“, auch bei einer HTTP-Quelle — es ist derselbe Text wie bei Bash.)

Drück Freigeben, direkt neben Test ausführen. Es öffnet sich der Dialog HTTP-Konfiguration bestätigen mit dem Text „Diese URL wird bei jedem Fetch von Pulsaria abgerufen. Pulsaria sperrt URL, Headers und JSONPath nach deiner Freigabe gegen unbemerkte Änderungen.“ Darunter steht in Monospace die URL in der ersten und der JSONPath in der zweiten Zeile, dann die Zeile „Gib nur Quellen frei, denen du vertraust. Bei späteren Edits ist eine erneute Bestätigung erforderlich.“ Mit Freigeben bestätigst du, mit Abbrechen brichst du ab.

Danach wird das Banner grün: Freigegeben — „Der Scheduler ruft diesen KPI gemäß Cron-Zeitplan automatisch ab.“

Zwei ehrliche Hinweise zu diesem Dialog. Er zeigt die URL-Vorlage mit nicht aufgelösten {{tokens}}, nicht die URL, die tatsächlich rausgeht. Und er listet deine Header-Zeilen nicht auf, obwohl sie Teil dessen sind, was du freigibst — prüf sie vorher im Formular.

Was gesperrt wird

Pulsaria speichert einen SHA-256-Fingerabdruck über drei Dinge zusammen: die URL, die nach Namen sortierten Header-Zeilen und den JSONPath. Daraus folgt einiges Praktisches:

  • Die Reihenfolge der Header-Zeilen zu ändern hebt die Freigabe nicht auf. Einen Header umzubenennen oder seinen Wert zu ändern schon.
  • Eine Zeile ohne Namen zählt nicht mit, eine leere Zusatzzeile ändert also nichts.
  • Datums-Tokens gehören zur Vorlage, nicht zum aufgelösten Request — die Freigabe überlebt jeden Datumswechsel.
  • Freigeben ist auch anklickbar, wenn du nie getestet hast oder der Test fehlschlug. Der grüne Hinweis „✓ Test erfolgreich — du kannst jetzt freigeben“ ist ein Anstoß, keine Bedingung.

Nach einer Änderung

Änderst du URL, einen Header oder den JSONPath, springt das grüne Banner sofort beim Tippen zurück auf Bernstein. Was dann passiert:

  • Speicherst du trotzdem, kommt der Hinweis „Gespeichert — noch nicht freigegeben“ / „Der Scheduler überspringt diese KPI, bis du sie freigibst.“ Jeder geplante Lauf wird danach als übersprungen protokolliert, mit „HTTP-Konfiguration nicht freigegeben — in den KPI-Einstellungen testen und freigeben“.
  • Änderst du nur Name, Zielwert, Einheit, Richtung, Zeitraum, Bereich oder Cron, bleibt die Freigabe bestehen.
  • Duplizierst du einen KPI, verliert die Kopie die Freigabe.
  • Beim Import aus einem JSON-Export geht sie immer verloren. Jeder importierte HTTP-KPI muss auf der neuen Maschine erneut freigegeben werden — mit Absicht: Ein Import darf deinem Mac niemals eine bereits freigegebene URL unterschieben können.
  • „HTTP-Konfiguration wurde geändert — bitte erneut freigeben“ ist ein anderer Fall. Er bedeutet, dass die gespeicherte Konfiguration nicht mehr zum gespeicherten Fingerabdruck passt, also kpi-definitions.json außerhalb von Pulsaria verändert wurde.

Wo Header-Werte gespeichert werden

Header-Namen und -Werte liegen im Klartext in kpi-definitions.json, neben dem Rest der KPI-Definition. Sie landen nicht im macOS-Schlüsselbund — das gilt nur für Connector-Zugangsdaten.

Daraus folgen drei Dinge, die du vor dem Einfügen eines Keys entscheiden solltest:

  • Wer diese Datei lesen kann, liest auch das Token. Hast du dein Datenverzeichnis auf iCloud Drive oder einen anderen Sync-Ordner gelegt, liegt das Token dort ebenfalls.
  • Ein JSON-Export enthält das Token. Behandle eine Exportdatei wie ein Geheimnis und verschick sie nicht per Mail.
  • Löschst du den KPI, verschwindet der Header aus der Datei.

Nimm hier einen lesenden, eng zugeschnittenen API-Key und rotiere ihn, falls ein Export je deinen Mac verlassen hat. Wo es für denselben Dienst einen Connector gibt, ist der der sicherere Weg — dessen Zugangsdaten liegen im Schlüsselbund und sind in keinem Export enthalten.

Mehr zu beiden Dateien: Wo deine Daten liegen und Backup, Export & Umzug.

Drei Beispiele

1 — Eine Zahl in einem verschachtelten Objekt

Eine Analytics-API, die einen Monat auf einmal liefert.

URL

https://api.example.com/v1/stats?site=example.com&from={{firstDayOfMonth}}&to={{lastDayOfMonth}}

Header — eine Zeile: Authorization / Bearer sk_live_abc123

Antwort

{
  "site": "example.com",
  "range": { "from": "2026-07-01", "to": "2026-07-31" },
  "totals": { "visitors": 4821, "pageviews": 11930, "bounce_rate": 38.4 }
}

JSONPath: totals.visitors4821

Sinnvolle Zieleinstellungen dazu: Einheit visitors, KPI-Typ Wiederkehrender Wert, Zeitraum Monat, Cron 0 9 * * *. Jeder Abruf überschreibt den heutigen Eintrag mit dem Monatswert bis dato.

2 — Ein Eintrag aus einer Liste

Eine Newsletter-API, die alle deine Listen in einem Aufruf zurückgibt.

URL

https://api.example.com/v1/lists

Header — eine Zeile: X-API-Key / 8f2c9d…

Antwort

{
  "results": [
    { "id": "lst_weekly",  "name": "Weekly letter",   "subscriber_count": 1284 },
    { "id": "lst_product", "name": "Product updates", "subscriber_count": 412 }
  ],
  "count": 2
}

JSONPath: results[0].subscriber_count1284

Der Haken: [0] ist das, was die API zufällig zuerst ausliefert. Nach id auswählen geht nicht, und ändert sich die Reihenfolge, misst dein KPI still und leise die andere Liste — ohne Fehler, weil der Pfad ja weiterhin eine Zahl ergibt. Kann die API serverseitig filtern, mach es in der URL. Brauchst du 1284 + 412, kann diese Quelle nicht addieren; dafür nimmst du einen Bash-KPI mit jq.

3 — Ein Array auf oberster Ebene, Zahlen als Strings

Ein Support-Tool, das eine Zeile pro Tag liefert und alles in Anführungszeichen setzt.

URL

https://api.example.com/v1/queue?day={{today}}

Antwort

[
  { "day": "2026-07-25", "open_tickets": "37", "first_response_minutes": "42.5" }
]

JSONPath: [0].open_tickets37

Ein führendes [0] ist der Weg in eine Antwort, die ein Array statt eines Objekts ist; ein numerischer String wird akzeptiert und geparst. Für einen Ticket-Rückstand setzt du Richtung auf Niedriger ist besser.

Achte auf den Leerfall: An einem Tag ohne Zeilen liefert diese API [], der Pfad trifft nicht, und der geplante Abruf scheitert mit „JSONPath-Ergebnis ist keine Zahl“, statt 0 zu speichern. Das ist Absicht — Pulsaria erfindet für eine eigene HTTP-Quelle keine Null.

Fehlermeldungen

Aus dem Testlauf, wo die Meldungen am genauesten sind:

MeldungWas sie bedeutet
HTTP 401, HTTP 404, HTTP 500, …Die API hat mit einem Status außerhalb von 2xx geantwortet. Mehr als den Status bekommst du nicht; der Body steht im Panel
Antwort ist kein gültiges JSONDer Body ließ sich nicht parsen. Falscher Endpunkt, eine HTML-Fehlerseite oder eine bei 64 KB abgeschnittene Antwort
JSONPath "totals.visitors" hat nicht gematchtDer Pfad löste auf nichts auf. Vergleich ihn mit dem Abschnitt Antwort
Aufgelöster Wert ist keine Zahl: …Der Pfad hat etwas gefunden, aber keine Zahl. Die ersten 100 Zeichen des Fundes werden angehängt
Only http:// and https:// URLs are supportedDie URL nutzt ein anderes Schema
Empty URLIm URL-Feld stand nur Leerraum
Request failed: …DNS, TLS, Verbindung oder Timeout. Die Rohmeldung der Netzwerkschicht, nach 300 Zeichen abgeschnitten

Die letzten drei Meldungen stammen aus der nativen Schicht und bleiben auch bei deutscher Oberfläche englisch.

Aus einem echten Lauf — Scheduler oder Jetzt ausführen — sind die Meldungen gröber, und ein paar neue kommen dazu:

MeldungWas sie bedeutet
HTTP-Konfiguration nicht freigegeben — in den KPI-Einstellungen testen und freigebenNie freigegeben. Wird als übersprungener Versuch protokolliert, nicht als Fehler
HTTP-Konfiguration wurde geändert — bitte erneut freigebenDie gespeicherte Konfiguration passt nicht mehr zum gespeicherten Fingerabdruck
URL oder JSONPath fehltEines der beiden Felder ist im Speicher leer
JSONPath-Ergebnis ist keine ZahlDer Pfad hat nicht getroffen oder der Wert war keine Zahl — der echte Lauf unterscheidet das nicht
Trial expired, License revokedNur-Lesen-Modus. Das ist ein Toast beim Klick auf Jetzt ausführen, keine Log-Zeile: Es wird nichts abgerufen und nichts protokolliert. Diese beiden Meldungen bleiben auch auf Deutsch englisch. Siehe Testphase & Lizenz

Meldet ein geplanter Abruf „JSONPath-Ergebnis ist keine Zahl“, öffne den KPI und drück Test ausführen. Der Testlauf zerlegt diese eine Meldung in „hat nicht gematcht“ und „ist keine Zahl“ — zwei sehr unterschiedliche Reparaturen.

Die freundlichen Erklärungen, die Connector-KPIs bei 401, 403 und 429 bekommen, gelten hier nicht. Eine eigene HTTP-Quelle protokolliert den nackten Statuscode.

Ein fehlgeschlagener Abruf schreibt nie einen Wert — der KPI behält einfach seine vorherige Zahl. Jeder Versuch, erfolgreich oder nicht, landet im Fetch-Log; ein Klick auf die Zeile zeigt die gesendete URL, den HTTP-Status, die Dauer und bis zu 4096 Zeichen der Antwort. Siehe Fetch-Log.

Was diese Quelle nicht kann

  • Kein POST, kein PUT, kein GraphQL und keinerlei Request-Body.
  • Keine Authentifizierung jenseits von „ein fester String in einem Header“: kein OAuth, kein Token-Refresh, keine signierten Requests, keine Cookies, keine Client-Zertifikate.
  • Kein Summieren, Zählen oder Mitteln über ein Array, und keinen zweiten Wert aus derselben Antwort — ein Request, eine Zahl, ein KPI.
  • Keine Pagination.
  • Keine Nicht-JSON-Quellen: kein HTML-Scraping, kein XML, kein CSV, kein Klartext.
  • Kein einstellbares Timeout, kein Proxy, keine eigene CA.
  • Automatische Abrufe laufen nur, solange Pulsaria geöffnet ist, und nur unter macOS. Siehe Zeitpläne & Aktualisierung.

Stößt du an eine dieser Grenzen, sind Shell-Befehle der Notausgang — alles, was curl und jq können, mit derselben Freigabe und demselben 10-Sekunden-Limit.

Wie es weitergeht

Nicht gefunden?

Schreib uns — jede Mail liest ein Mensch, meistens der, der das Feature gebaut hat.

admin@one-pixel-ahead.com