War ein Film einmal als gesehen markiert, gab es keinen Weg zurueck: im
Zustand "gesehen" stand statt eines Knopfes ein <p> — ein Etikett ohne
Klickziel. Der Statuswechsel konnte das nicht heilen, weil "gesehen" aus den
Sichtungen abgeleitet wird und setStatus() die absichtlich nicht anfasst.
Und eine Gegenfunktion fuer Filme gab es nicht: unmarkEpisodes() und
unmarkSeason() waren da, ein unmarkMovie() nie geschrieben.
unmarkMovie() ist auf run: entry.currentRun und episodeId: null eingegrenzt.
Wer einen Film dreimal gesehen hat, hat drei Sichtungen — ohne die
Eingrenzung loeschte das Zuruecknehmen alle drei und verfaelschte
rueckwirkend die Statistik.
Die gruene Plakette bleibt stehen, ist jetzt aber klickbar, so wie sich
Episodenhaekchen auch abwaehlen lassen. Ueber die Schnittstelle geht es mit
DELETE {"action":"movie"} als Gegenstueck zum gleichnamigen POST, ohne dass
man die watchIds kennen muss.
Drei Tests: Zuruecknehmen samt erneutem Abhaken, die Eingrenzung auf den
laufenden Durchlauf, und dass Serien abgewiesen werden. Klickweg und
Endpunkt gegen einen Produktionsbau geprueft.
Der im Issue vermutete Nebenpunkt trifft nicht zu: report() blendet das
Zuruecknehmen nur bei ids.length > 0 ein, ein wirkungsloses Angebot gab es
also nie.
Refs #5
6.9 KiB
Flimmer — REST-API
Alles, was die Oberfläche kann, geht auch über die Schnittstelle: Bibliothek lesen und pflegen, Episoden abhaken, suchen, Kalender und Statistik abrufen, Universen verwalten, Daten exportieren.
Die maschinenlesbare Beschreibung liegt unter
GET /api/v1/openapi.json (OpenAPI 3.1) — damit lassen sich Clients erzeugen
oder die Endpunkte in Insomnia, Bruno oder Postman importieren.
Anmeldung
Jede Anfrage trägt einen persönlichen API-Schlüssel im Authorization-Kopf:
curl -H "Authorization: Bearer flm_a1b2c3d4.xxxxxxxxxxxx" \
https://flimmer.example.com/api/v1/me
Schlüssel legst du unter Einstellungen → API-Schlüssel an. Der Klartext ist einmalig sichtbar; danach liegt in der Datenbank nur ein Hash. Ein Schlüssel hat dieselben Rechte wie dein Konto — behandle ihn wie ein Passwort und zieh ihn zurück, sobald du ihn nicht mehr brauchst.
Aus dem Browser heraus funktioniert stattdessen das Sitzungscookie; dieselben Endpunkte bedienen also Skripte und das eigene Frontend.
Antwortformat
Alle Antworten sind JSON. Zeitangaben in ISO 8601, Laufzeiten in Minuten.
Fehler tragen immer ein Feld error:
{ "error": "Diesen Eintrag gibt es nicht." }
| Status | Bedeutung |
|---|---|
| 200 | In Ordnung |
| 401 | Kein oder ungültiger Schlüssel |
| 404 | Nicht vorhanden (oder nicht deins) |
| 422 | Anfrage unvollständig — details nennt die Felder |
| 502 / 503 | TMDB nicht erreichbar bzw. kein Schlüssel hinterlegt |
Endpunkte
Konto
GET /api/v1/me
Bibliothek
GET /api/v1/bibliothek?status=WATCHING&typ=SERIES&genre=Drama&q=nord&limit=50&offset=0
POST /api/v1/bibliothek
Filter: q, status, typ, genre, tag, dienst, sammlung, limit,
offset. Status: WATCHING, PAUSED, COMPLETED, DROPPED, WATCHLIST.
sammlung=1 liefert nur Titel, die in der eigenen Sammlung liegen.
Aufnehmen — entweder mit interner Kennung oder direkt mit der TMDB-Kennung:
curl -X POST -H "Authorization: Bearer $KEY" -H "content-type: application/json" \
-d '{"tmdbId": 1396, "type": "SERIES", "status": "WATCHING"}' \
https://flimmer.example.com/api/v1/bibliothek
GET /api/v1/bibliothek/{entryId}
PATCH /api/v1/bibliothek/{entryId}
DELETE /api/v1/bibliothek/{entryId}
GET liefert den Eintrag samt progress und nextEpisode.
PATCH ändert status, rating (1–10), notes, whereToWatch, favorite
sowie owned (liegt der Titel in der eigenen Sammlung?) und ownedSource
(wo genau — Jellyfin, Plex, Blu-ray …).
DELETE legt den Eintrag in den Papierkorb — dort bleibt er 30 Tage.
Sichtungen
GET /api/v1/bibliothek/{entryId}/sichtungen
POST /api/v1/bibliothek/{entryId}/sichtungen
DELETE /api/v1/bibliothek/{entryId}/sichtungen
POST kennt fünf Formen:
| Rumpf | Wirkung |
|---|---|
{"action":"next"} |
Die nächste offene Episode abhaken |
{"action":"movie"} |
Einen Film als gesehen markieren |
{"action":"episodes","episodeIds":["…"]} |
Eine Auswahl abhaken |
{"action":"season","seasonId":"…"} |
Eine ganze Staffel |
{"action":"upTo","episodeId":"…"} |
Alles bis einschließlich dieser Episode |
Die Antwort trägt watchIds — damit lässt sich genau diese Aktion zurücknehmen:
# Nächste Episode abhaken
IDS=$(curl -s -X POST -H "Authorization: Bearer $KEY" -H "content-type: application/json" \
-d '{"action":"next"}' \
https://flimmer.example.com/api/v1/bibliothek/$ENTRY/sichtungen | jq -c .watchIds)
# … und wieder zurücknehmen
curl -X DELETE -H "Authorization: Bearer $KEY" -H "content-type: application/json" \
-d "{\"watchIds\": $IDS}" \
https://flimmer.example.com/api/v1/bibliothek/$ENTRY/sichtungen
Alternativ nimmt DELETE auch {"episodeIds":["…"]} — dann verschwindet die
Markierung dieser Episoden im aktuellen Durchlauf.
Für Filme gibt es {"action":"movie"} als Gegenstück zum gleichnamigen POST;
dafür braucht man die watchIds nicht zu kennen:
curl -X DELETE -H "Authorization: Bearer $KEY" -H "content-type: application/json" \
-d '{"action":"movie"}' \
https://flimmer.example.com/api/v1/bibliothek/$ENTRY/sichtungen
Zurückgenommen wird dabei nur der laufende Durchlauf. Wer einen Film dreimal gesehen hat, verliert also nicht die Sichtungen der früheren Durchläufe — die zählen weiter in die Statistik.
Suche
GET /api/v1/suche?q=nordlicht&typ=serie
Durchsucht TMDB. Die Treffer tragen tmdbId und type — direkt verwendbar für
POST /api/v1/bibliothek. Ohne hinterlegten TMDB-Schlüssel antwortet der
Endpunkt mit 503.
Kalender
GET /api/v1/kalender?von=2026-09-01&bis=2026-09-30
Kommende Episoden der getrackten Serien. Ohne Zeitraum: die nächsten 30 Tage.
Statistik
GET /api/v1/statistik?jahr=2026
Liefert totals, monthly, providers, genres und yearReview auf einmal.
Universen
GET /api/v1/universen
POST /api/v1/universen
GET /api/v1/universen/{id}?reihenfolge={orderId}
POST /api/v1/universen/{id}
PATCH /api/v1/universen/{id}
GET /api/v1/universen/{id} gibt die Einträge in der gewählten Reihenfolge
zurück, dazu progress und nextUp. Ohne reihenfolge gilt die
Standardreihenfolge des Universums — in orders trägt sie isDefault.
PATCH legt fest, welche das ist. Die Reihenfolge muss zu diesem Universum
gehören, und es braucht Schreibrecht:
curl -X PATCH -H "Authorization: Bearer $KEY" -H "content-type: application/json" \
-d '{"defaultOrderId": "…"}' \
https://flimmer.example.com/api/v1/universen/$ID
Je Universum trägt immer genau eine Reihenfolge den Standard. Wird die Standardreihenfolge gelöscht, übernimmt die Erscheinungsreihenfolge.
Export
GET /api/v1/export?format=json
GET /api/v1/export?format=csv
JSON enthält zusätzlich alle Sichtungen und Universen.
Beispiel: was ich habe, aber noch nicht gesehen habe
curl -s -H "Authorization: Bearer $KEY" \
"$HOST/api/v1/bibliothek?sammlung=1&status=WATCHLIST" \
| jq -r '.items[] | "\(.title) — liegt bei: \(.ownedSource // "ohne Angabe")"'
Beispiel: Serien mit offener Episode auflisten
#!/usr/bin/env bash
set -euo pipefail
KEY="flm_…"
HOST="https://flimmer.example.com"
curl -s -H "Authorization: Bearer $KEY" \
"$HOST/api/v1/bibliothek?status=WATCHING&typ=SERIES" \
| jq -r '.items[] | "\(.title): \(.watched)/\(.total)"'
Grenzen
Es gibt bewusst keine Ratenbegrenzung: Flimmer läuft auf deinem eigenen Server, und ein Limit gegen dich selbst hilft niemandem. Wer die Instanz öffentlich erreichbar macht, setzt so etwas besser im Reverse Proxy.
Der Abgleich mit TMDB lässt sich über einen eigenen Endpunkt anstoßen — der nimmt allerdings das Cron-Geheimnis, nicht einen API-Schlüssel:
curl -X POST -H "Authorization: Bearer $CRON_SECRET" \
https://flimmer.example.com/api/cron/sync