Files
logbuch/docs/api.md
T
Matthias G 03d514b3c4 Close the last gaps in the API documentation
The image endpoint, the spec itself and the scheduled publishing run were
missing from the spec and the admin page.
2026-08-01 18:27:04 +02:00

11 KiB

Logbuch API

Stand: 01.08.2026. Diese Datei beschreibt den tatsächlichen Stand der Schnittstelle.

Wofür Logbuch da ist

Logbuch hält fest, was in den Produkten der Firmengruppe passiert ist. Alle zwei Wochen entsteht pro Projekt ein Eintrag: eine neue Funktion, eine Verbesserung, ein behobener Fehler, eine Änderung mit Auswirkung.

Es ist kein Änderungsprotokoll für Entwickler. Es ist für Kollegen und später Kunden, die wissen wollen, was sich geändert hat, ohne Tickets oder Code zu lesen.

Die Anwendung besteht aus drei Teilen: dem Archiv zum Lesen, dem Adminbereich zum Schreiben, und dieser API. Über die API holen andere Anwendungen ihre Einträge und zeigen sie ihren Nutzern an, und über sie können Einträge verfasst werden.

Grundlagen

Basis-Adresse lokal: http://localhost:4700

Alle Endpunkte liegen unter /api/v1. Angemeldet wird sich mit einem Token im Kopf:

Authorization: Bearer <token>

Zugänge werden im Adminbereich unter Verwaltung, Zugänge angelegt. Beim Anlegen wird das Token einmal im Klartext gezeigt, danach nie wieder. Gespeichert ist nur ein Hash.

Ein Zugang hat drei Eigenschaften, die zusammen bestimmen, was er darf:

Eigenschaft Bedeutung
Modus read liest, write schreibt zusätzlich
Zielgruppe internal, customer oder public. Bestimmt, welche Einträge der Zugang überhaupt sieht
Projektbindung Ist ein Projekt gesetzt, sieht und schreibt der Zugang ausschließlich dort

Die Zielgruppe ist die wichtigste Einstellung. Ein Zugang mit customer sieht Kundenbeiträge und öffentliche, aber niemals interne. Das gilt serverseitig, kein Parameter kann das umgehen.

Ein Zugang mit Schreibrecht kann niemals veröffentlichen. Alles landet als Entwurf, ein Mensch gibt frei.

Fehler kommen einheitlich als Problem-JSON:

{ "type": "unauthorized", "title": "unauthorized", "status": 401, "detail": null }

Lesen

GET /api/v1/projects

Die Projekte, die der Zugang sehen darf.

curl -s -H "Authorization: Bearer $TOKEN" \
  http://localhost:4700/api/v1/projects

Antwort: { "items": [ { "id", "slug", "name", "code", "color", "description", "sort", "isActive" } ] }

GET /api/v1/posts

Die veröffentlichten Einträge, neueste zuerst.

Parameter Wirkung
project Slug eines Projekts
since Zeitpunkt, ab dem gesucht wird
type feature, improvement, fix, breaking, info
tag Schlagwort
page Seite, ab 1
per_page 1 bis 100, Standard 25
curl -s -H "Authorization: Bearer $TOKEN" \
  "http://localhost:4700/api/v1/posts?project=trakk&per_page=5"

Antwort: { "items": [...], "meta": { "total", "page", "per_page" } }

Ein Eintrag in der Liste trägt: id, slug, title, teaser, type, audience, publishAt, number, coverMediaId, coverUrl, projectSlug, projectName, projectCode, projectColor.

GET /api/v1/posts/:slug

Ein einzelner Eintrag. Ist er für die Zielgruppe des Zugangs nicht sichtbar, kommt 404, nicht 403. Damit verrät die Antwort nicht, dass es ihn gibt.

GET /api/v1/unread

Was ein Nutzer der einbindenden Anwendung noch nicht gelesen hat.

curl -s -H "Authorization: Bearer $TOKEN" \
  "http://localhost:4700/api/v1/unread?external_user_id=u-42"

external_user_id ist die Nutzerkennung der aufrufenden Anwendung, nicht die eines Logbuch-Kontos. Antwort: { "count", "items": [...] }.

Der Zugang muss dafür an ein Projekt gebunden sein, sonst 422.

POST /api/v1/read

Markiert Einträge als gelesen.

curl -s -X POST -H "Authorization: Bearer $TOKEN" \
  -H "content-type: application/json" \
  -d '{"external_user_id":"u-42","post_ids":["<id>"]}' \
  http://localhost:4700/api/v1/read

Antwort: { "marked": 1 }, also die Zahl der tatsächlich neu vermerkten Einträge. Unbekannte Kennungen und Einträge fremder Projekte werden still verworfen, die Antwort bleibt 200.

Schreiben

Ein Zugang mit Schreibrecht legt immer Entwürfe an. Veröffentlicht wird von Hand in der Verwaltung.

GET /api/v1/post-types

Die verwaltbaren Beitragsarten mit Schlüssel, Bezeichnung und Farbe. Vor dem Schreiben abfragen statt raten.

POST /api/v1/posts

Legt einen Beitrag als Entwurf an.

Feld Pflicht Bedeutung
project ja Slug des Projekts
title ja höchstens 200 Zeichen
type ja Schlüssel einer Beitragsart
teaser nein höchstens 500 Zeichen
audience nein internal, customer oder public, Standard internal
author_name nein sonst der Name des Zugangs
slug nein sonst aus dem Titel gebildet
cover_media_id nein Kennung eines hochgeladenen Bildes
publish_at nein gewünschtes Datum, siehe unten
blocks nein Inhalt, höchstens 100 Blöcke
idempotency_key nein derselbe Schlüssel liefert denselben Beitrag zurück
curl -s -X POST -H "Authorization: Bearer $TOKEN" \
  -H "content-type: application/json" \
  -d '{
    "project": "trakk",
    "title": "SLA-Uhr zählt Feiertage nicht mehr mit",
    "teaser": "Reaktionszeiten liefen über Wochenenden weiter.",
    "type": "fix",
    "audience": "customer",
    "cover_media_id": "<medien-id>",
    "publish_at": "2026-06-04T09:00:00Z",
    "idempotency_key": "sla-2026-07-31"
  }' \
  http://localhost:4700/api/v1/posts

Antwort 201 mit dem angelegten Entwurf. number ist null, die Nummer kommt beim Veröffentlichen.

PATCH /api/v1/posts/:id

Ändert Titel, Anreißer, Art, Zielgruppe, Slug, Aufmacherbild oder Datum. Nur solange nichts veröffentlicht ist, sonst 409. Zum Ändern wird die Kennung gebraucht, nicht der Slug.

curl -s -X PATCH -H "Authorization: Bearer $TOKEN" \
  -H "content-type: application/json" \
  -d '{"teaser":"Neuer Anreißer.","publish_at":"2026-06-04T09:00:00Z"}' \
  http://localhost:4700/api/v1/posts/<id>

PUT /api/v1/posts/:id/blocks

Setzt die Blöcke vollständig, in der übergebenen Reihenfolge. Höchstens 100.

curl -s -X PUT -H "Authorization: Bearer $TOKEN" \
  -H "content-type: application/json" \
  -d '{"blocks":[{"type":"text","data":{"text":"Erster Absatz."}}]}' \
  http://localhost:4700/api/v1/posts/<id>/blocks

GET /api/v1/posts/:id

Liest den Entwurf mit allen Blöcken zurück, zum Prüfen vor der Freigabe. Mit Schreibrecht findet dieser Aufruf den Entwurf auch über den Slug.

DELETE /api/v1/posts/:id

Löscht einen Entwurf. Veröffentlichtes lässt sich nicht löschen.

Bilder

POST /api/v1/media

Lädt ein Bild hoch, erzeugt WebP-Varianten und legt einen Datensatz an. Braucht einen Zugang mit Schreibrecht.

curl -s -X POST -H "Authorization: Bearer $TOKEN" \
  -F "file=@screenshot.png" \
  -F "project=trakk" \
  -F "alt=Spaltenmenü mit den neuen Einträgen" \
  http://localhost:4700/api/v1/media

project ist der Slug des Projekts. Bei einem projektgebundenen Token darf das Feld wegbleiben.

Grenzen: nur Bildformate, höchstens 15 MB.

DELETE /api/v1/media/:id

Löscht ein Bild samt seiner Fassungen. Hängt es noch an einem Beitrag, als Aufmacher oder in einem Block, kommt 409 zurück.

curl -s -X DELETE -H "Authorization: Bearer $TOKEN" \
  http://localhost:4700/api/v1/media/<id>

GET /api/v1/media/file/...

Liefert eine Variante aus. Die Pfade stehen am Medium unter variants, je Eintrag mit width, format und path.

Zeitsteuerung

POST /api/v1/publish-due

Veröffentlicht alle Beiträge, deren Termin erreicht ist. Der Aufruf gehört einer Zeitsteuerung, nicht einem Zugang, und verlangt CRON_SECRET als Bearer-Token.

curl -s -X POST -H "Authorization: Bearer $CRON_SECRET" \
  http://localhost:4700/api/v1/publish-due

Antwort: { "published": [...], "meta": { "total": 0 } }.

Werkzeuge

Adresse Inhalt
GET /api/v1/openapi.json maschinenlesbare Beschreibung für Postman, Bruno, Insomnia oder einen KI-Assistenten
GET /api/v1/style-guide dieser Redaktionsleitfaden als Markdown

Beide verlangen ein Token. In der Verwaltung unter API lädt ein angemeldeter Admin beide Dateien ohne Token herunter.

Redaktionsleitfaden

Wer über die API schreibt, schreibt für Menschen. Diese Regeln gelten für jeden Eintrag.

Aufbau. Titel, Anreißer, dann der Text. Der Titel sagt, was sich geändert hat, nicht dass sich etwas geändert hat. Der Anreißer fasst in zwei bis drei Sätzen zusammen, was der Leser davon hat. Der Text erklärt, was vorher war, was jetzt ist, und was zu tun bleibt.

Gute Titel:

  • "SLA-Uhr zählt Feiertage nicht mehr mit"
  • "Spalten per Rechtsklick verwalten"

Schlechte Titel:

  • "Verbesserungen an der Tabelle" (sagt nichts)
  • "Wir haben ein spannendes neues Feature gebaut" (Werbung, keine Information)

Ton. Sachlich und direkt. Aktive Verben. Keine Werbesprache, keine Superlative, keine Ausrufezeichen, keine Emojis. Keine Gedankenstriche, immer normales Minus. Echte Umlaute.

Länge. So lang wie nötig. Meist drei bis sechs Absätze. Ein Fehlerbehebung darf auch aus zwei Sätzen bestehen.

Bilder. Zeigen, was sich geändert hat, nicht das Firmenlogo. Jedes Bild bekommt einen Alt-Text, der beschreibt was zu sehen ist. Bei öffentlichen Beiträgen ist der Alt-Text Pflicht, sonst lässt sich der Beitrag nicht veröffentlichen.

Zielgruppe. internal für alles, was intern bleibt: Umbauten, Zwischenstände, Interna über Kunden. customer für alles, was ein Kunde des Produkts wissen soll. public nur für das, was auch außerhalb stehen darf.

Wer veröffentlicht. Nicht der Zugang. Ein Mensch schaut drauf und gibt frei. Ein über die API verfasster Beitrag ist immer ein Vorschlag.

Datum

Beim Anlegen und beim Ändern nimmt die API publish_at als ISO-Zeitangabe entgegen. Der Zugang veröffentlicht damit nicht, er hinterlegt nur das Datum. Gibt ein Mensch den Beitrag frei, übernimmt Logbuch genau dieses Datum statt der aktuellen Uhrzeit. Damit lassen sich ältere Meldungen richtig einsortieren.

curl -s -X POST -H "Authorization: Bearer $TOKEN" \
  -H "content-type: application/json" \
  -d '{"project":"trakk","title":"...","type":"fix","publish_at":"2026-06-04T09:00:00Z"}' \
  http://localhost:4700/api/v1/posts

Nummern

Die Nummer eines Eintrags wird beim Veröffentlichen vergeben, nicht beim Anlegen. Ein Entwurf hat number: null. Damit bleiben die Nummern im Archiv lückenlos, auch wenn Entwürfe wieder verworfen werden.

Blocktypen

Der Inhalt eines Beitrags besteht aus Blöcken in fester Reihenfolge.

Typ Daten
text text, als Markdown: Absätze, Fett, Kursiv, Verweise, Listen, Zwischenüberschriften
image mediaId
gallery mediaIds
before_after beforeMediaId, afterMediaId
video url, title
quote text, source
code code, language
link url, title, description
callout text, tone

Unbekannte Typen werden bei der Anzeige übersprungen, sie zerlegen die Seite nicht.