9bc6a54783
Bug 1: all 171 category locale variants were returned; now filtered by locale parameter (default "de") → 13 German categories for shopping lists. Bug 2: TODO lists returned 171 shopping categories because sortingIndexByTaskList contains ALL list IDs regardless of type. Fix: look up list's taskListType via taskgettasklists, then match against category's taskListType. TODO lists (type=TODOS) return empty list since all API categories are SHOPPING_LIST type. sortingIndexByTaskList is explicitly documented as unreliable for type filtering in both code comments and SPEC.md. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
411 lines
17 KiB
Markdown
411 lines
17 KiB
Markdown
# Family Wall API – Spezifikation
|
||
|
||
Erarbeitet durch Browser-Traffic-Analyse (April 2026).
|
||
Es gibt keine offizielle API-Dokumentation.
|
||
|
||
## Base URL
|
||
https://api.familywall.com/api
|
||
|
||
## Authentifizierung
|
||
|
||
### Login
|
||
POST https://api.familywall.com/api/log2in
|
||
Content-Type: application/x-www-form-urlencoded
|
||
|
||
**Request-Parameter:**
|
||
|
||
| Parameter | Wert |
|
||
|---|---|
|
||
| `identifier` | E-Mail-Adresse |
|
||
| `password` | Passwort |
|
||
| `type` | nicht senden — wird als `undefined` ignoriert (verifiziert per JS-Analyse) |
|
||
| `clientId` | weglassen |
|
||
| `clientSecret` | weglassen |
|
||
| `generateAutologinToken` | weglassen |
|
||
| `countryCode` | weglassen |
|
||
|
||
**Response (Erfolg):**
|
||
|
||
```json
|
||
{ "a00": { "r": { "r": <SessionObject> }, "cn": "log2in" } }
|
||
```
|
||
|
||
`SessionObject` enthält u.a. `tokenCsrf` und `webApiUrl`.
|
||
`tokenCsrf` ist die Session-ID – identisch zur `JSESSIONID` im Cookie.
|
||
|
||
**Response (Fehler):**
|
||
|
||
```json
|
||
{ "ex": { "ex": <ErrorObject> } }
|
||
{ "un": { "un": <ErrorObject> } }
|
||
```
|
||
|
||
Der Server setzt nach erfolgreichem Login ein Session-Cookie:
|
||
Set-Cookie: JSESSIONID=<session-id> (= tokenCsrf)
|
||
|
||
### Folgecalls (nach Login)
|
||
|
||
Alle API-Calls nach dem Login benötigen:
|
||
|
||
| | |
|
||
|---|---|
|
||
| **Cookie** | `JSESSIONID=<session-id>` |
|
||
| **Header** | `Tokencsrf: <session-id>` (identisch zur JSESSIONID) |
|
||
| **Content-Type** | `application/x-www-form-urlencoded` |
|
||
|
||
### Logout
|
||
POST https://api.familywall.com/api/log2out
|
||
Content-Type: application/x-www-form-urlencoded
|
||
|
||
Keine Parameter. Session wird serverseitig invalidiert.
|
||
|
||
### Session-Strategie
|
||
|
||
Kein Session-Caching. Jeder MCP-Tool-Call führt folgende Sequenz aus:
|
||
|
||
POST /api/log2in → Session-ID
|
||
POST /api/<endpoint> → Nutzdaten
|
||
POST /api/log2out → Session invalidieren
|
||
|
||
|
||
Credentials (E-Mail + Passwort) werden einmalig via `mcp-familywall setup`
|
||
im OS Keyring gespeichert (Keys: `email`, `password`). Kein Keyring-Eintrag
|
||
für `session_id`.
|
||
|
||
## Bekannte Endpoints
|
||
|
||
### `famlistfamily` – Kreise abrufen
|
||
POST https://api.familywall.com/api/famlistfamily
|
||
Content-Type: application/x-www-form-urlencoded
|
||
|
||
**Body-Parameter:** keine (verifiziert)
|
||
|
||
**Response-Struktur (verifiziert):**
|
||
```
|
||
a00.r.r[] → Kreise
|
||
.metaId → eindeutige Kreis-ID (Format family/<id>)
|
||
.name → Kreisname
|
||
.family_id → numerische Kreis-ID
|
||
.members[] → Mitglieder des Kreises
|
||
.accountId → numerische Account-ID
|
||
.metaId → Mitglieds-ID (Format familymember/<accountId>_<familyId>)
|
||
.firstName → Vorname (Display-Name; bevorzugen gegenüber .name)
|
||
.name → E-Mail-Adresse (Family Wall Default wenn kein Anzeigename)
|
||
.role → Familienrolle (z.B. "Unknown", "Parent", "Child")
|
||
.right → Berechtigung (z.B. "SuperAdmin", "Admin", "Member")
|
||
.color → Profilfarbe als Hex-String (z.B. "#FF8086")
|
||
.medias[0].pictureUrl → Avatar-URL (generierter Default wenn pictureDefault=true)
|
||
.identifiers[] → Kontaktdaten
|
||
.type → Typ (z.B. "Email")
|
||
.value → Wert (z.B. E-Mail-Adresse)
|
||
.familyId → Zugehöriger Kreis (= metaId des Kreises)
|
||
.isloggedaccount → "true" wenn das der angemeldete Account ist
|
||
.joinDate → Beitrittsdatum (ISO 8601)
|
||
.lastLoginDate → Letzter Login (ISO 8601)
|
||
.locale → Spracheinstellung (z.B. "de_DE")
|
||
.timeZone → Zeitzone (z.B. "Europe/Berlin")
|
||
.invitations[] → Offene Einladungen (leer wenn keine)
|
||
.coverUri → Cover-Bild URL
|
||
```
|
||
|
||
### `taskgettasklists` – Listen abrufen
|
||
POST https://api.familywall.com/api/taskgettasklists
|
||
Content-Type: application/x-www-form-urlencoded
|
||
|
||
**Body-Parameter:** keine
|
||
|
||
**Response-Struktur:** zu verifizieren beim ersten echten Call
|
||
|
||
### `accgetallfamily` – Listen + Tasks abrufen
|
||
POST https://api.familywall.com/api/accgetallfamily
|
||
Content-Type: application/x-www-form-urlencoded
|
||
|
||
**Body-Parameter:**
|
||
|
||
| Parameter | Wert |
|
||
|---|---|
|
||
| `a01call` | `"taskcategorysync"` |
|
||
| `a02call` | `"tasksync"` |
|
||
|
||
Hinweis: `a03call=tasklistsync` ist **kein gültiger Endpoint** — API antwortet mit
|
||
"The call tasklistsync is not registered". Nicht verwenden.
|
||
`partnerScope`, `a03id`, `withStateBean` werden weggelassen.
|
||
|
||
**Response-Struktur (verifiziert):**
|
||
```
|
||
a00 → famlistfamily-Daten (Kreise inkl. members[]) – Nebeneffekt
|
||
a01.r.r.updatedCreated[] → taskcategorysync (Kategorien/Abteilungen pro Liste)
|
||
.metaId → Kategorie-ID (Format taskCategory/<familyId>_<sysId>)
|
||
.name → Kategoriename (sprachabhängig, z.B. "Beverages")
|
||
.emoji → Emoji-Symbol der Kategorie
|
||
.systemCategoryId → numerische System-ID (sprach-unabhängig)
|
||
.taskListType → Listentyp der Kategorie (z.B. "SHOPPING_LIST")
|
||
**Wichtig:** Alle 171 Kategorien sind SHOPPING_LIST —
|
||
es gibt keine TODO-Kategorien in der API
|
||
.sortingIndexByTaskList → dict: Listen-ID → Sortierposition
|
||
**Achtung:** enthält ALLE Listen-IDs unabhängig vom Typ
|
||
→ NICHT für Typ-Filterung verwenden!
|
||
Stattdessen: taskListType der Kategorie mit
|
||
taskListType der Liste (aus taskgettasklists) vergleichen
|
||
.locale → Sprache des Namens (z.B. "de", "en", "ru", "fr", "es",
|
||
"it", "nl", "pt", "sv", "ko", "ja")
|
||
Jede Sprache = eigener Eintrag mit eigenem metaId/systemCategoryId
|
||
.hiddenByTaskList → Liste von Listen-IDs, in denen die Kat. versteckt ist
|
||
a02.r.r.updatedCreated[] → tasksync (Tasks)
|
||
.metaId → eindeutige Task-ID
|
||
.taskId → identisch zu metaId (zweiter Alias)
|
||
.text → Aufgabentext
|
||
.description → optionale Beschreibung
|
||
.taskListId → Zugehörigkeit zur Liste
|
||
.complete → "true" / "false" (String, nicht Boolean!)
|
||
.categories[] → zugewiesene Kategorien des Tasks
|
||
.system → "true" wenn System-Kategorie, "false" wenn custom
|
||
.name → Kategoriename (z.B. "SYS-CAT-TODOS", "Beverages")
|
||
.assignee / .assigneeIds → zugewiesene Mitglieder
|
||
.reminder → Erinnerungsdatum (ISO 8601, optional)
|
||
.recurrency → Wiederholungsregel (optional)
|
||
.sortingIndex → Anzeigereihenfolge
|
||
```
|
||
|
||
**Kategorie-Zuweisung bei taskcreate2 / taskupdate2:** Parameter-Name noch unbekannt.
|
||
Getestete Varianten (`taskCategoryId`, `categoryId`, `category`, `categoryMetaId`) werden
|
||
serverseitig ignoriert — die Task bekommt stets die Default-Systemkategorie der Liste.
|
||
Service Worker in der Web-App verhindert Inspektion des echten Requests. Noch zu verifizieren.
|
||
|
||
## Systembezeichnungen für Listen-Namen
|
||
|
||
Bekannte Systembezeichnungen werden deutsch übersetzt:
|
||
|
||
| Systembezeichnung | Deutsch |
|
||
|---|---|
|
||
| `SYS-CAT-SHOPPINGLIST` | `Einkaufsliste` |
|
||
|
||
Unbekannte Bezeichnungen werden unverändert zurückgegeben.
|
||
Mapping-Tabelle bei Bedarf erweitern.
|
||
|
||
### `wallget` – Aktivitäten (Wall) abrufen
|
||
POST https://api.familywall.com/api/wallget
|
||
Content-Type: application/x-www-form-urlencoded
|
||
|
||
**Body-Parameter:**
|
||
|
||
| Parameter | Wert |
|
||
|---|---|
|
||
| `nb` | Anzahl Einträge (z.B. `"20"`) |
|
||
| `date` | optional, für Paginierung (Datum des letzten Eintrags) |
|
||
| `accountId` | optional |
|
||
| `type` | optional |
|
||
| `nested` | optional |
|
||
| `masterNested` | optional |
|
||
| `sortBy` | optional |
|
||
|
||
**Response-Struktur (verifiziert):**
|
||
```
|
||
a00.r.r[]
|
||
.metaId → ID der Aktivität (= wallMessageId)
|
||
.refType → Aktivitätstyp (z.B. STATUS, FAMILY_CREATED)
|
||
.text → Text (optional, fehlt bei System-Events)
|
||
.creationDate → Datum (ISO 8601)
|
||
.accountId → Autor-ID
|
||
```
|
||
|
||
**Response-Struktur:** zu verifizieren beim ersten echten Call
|
||
|
||
### `wallactivityget` – Einzelne Aktivität abrufen
|
||
POST https://api.familywall.com/api/wallactivityget
|
||
Content-Type: application/x-www-form-urlencoded
|
||
|
||
**Body-Parameter:**
|
||
|
||
| Parameter | Wert |
|
||
|---|---|
|
||
| `accountId` | optional |
|
||
| `masterNested` | optional |
|
||
|
||
**Response-Struktur:** zu verifizieren beim ersten echten Call
|
||
(Endpoint noch nicht implementiert — für spätere Versionen)
|
||
|
||
## Debug-Logging
|
||
|
||
Wenn die Umgebungsvariable `FW_DEBUG=1` gesetzt ist, loggt `fw_client.py`
|
||
vollständige Request-Bodies und Responses nach stderr. Dient zur Verifikation
|
||
offener Punkte (z.B. `type`-Parameter beim Login, Kreis-Felder in Response).
|
||
|
||
**Wichtig:** Keine Secrets in Debug-Ausgaben (Passwort maskieren).
|
||
|
||
### `taskcreate2` – Task erstellen
|
||
POST https://api.familywall.com/api/taskcreate2
|
||
Content-Type: application/x-www-form-urlencoded
|
||
|
||
**Body-Parameter (verifiziert):**
|
||
|
||
| Parameter | Pflicht | Wert |
|
||
|---|---|---|
|
||
| `taskListId` | ja | Listen-ID aus `get_lists` (z.B. `taskList/123_456`) |
|
||
| `text` | ja | Aufgabentitel |
|
||
| `description` | nein | Optionale Beschreibung |
|
||
|
||
**Response-Struktur (verifiziert):**
|
||
```
|
||
a00.r.r → vollständiges Task-Objekt der neu erstellten Task
|
||
.metaId → eindeutige Task-ID (z.B. "task/23431854_726362809")
|
||
.taskListId → Listen-ID
|
||
.text → Titel
|
||
.complete → "false" (immer, direkt nach Erstellung)
|
||
```
|
||
|
||
### `taskupdate2` – Task aktualisieren
|
||
POST https://api.familywall.com/api/taskupdate2
|
||
Content-Type: application/x-www-form-urlencoded
|
||
|
||
**Body-Parameter (verifiziert):**
|
||
|
||
| Parameter | Pflicht | Wert |
|
||
|---|---|---|
|
||
| `metaId` | ja | Task-ID aus `get_tasks` |
|
||
| `text` | nein | Neuer Titel (mindestens `text` oder `description` erforderlich) |
|
||
| `description` | nein | Neue Beschreibung |
|
||
|
||
Hinweis: `taskListId` ist **nicht** erforderlich (verifiziert – Update ohne `taskListId` funktioniert).
|
||
|
||
**Response-Struktur:** kein spezifischer Rückgabewert – Erfolg = kein `ex`/`un`-Key auf Top-Level.
|
||
|
||
### `taskmark` – Task als erledigt/offen markieren
|
||
POST https://api.familywall.com/api/taskmark
|
||
Content-Type: application/x-www-form-urlencoded
|
||
|
||
**Body-Parameter (verifiziert):**
|
||
|
||
| Parameter | Pflicht | Wert |
|
||
|---|---|---|
|
||
| `taskId` | ja | Task-ID aus `get_tasks` (**WICHTIG: `taskId`, nicht `metaId`!**) |
|
||
| `complete` | ja | `"true"` oder `"false"` (String, nicht Boolean!) |
|
||
|
||
**Achtung:** Der Endpoint heißt intern `taskId`, nicht `metaId`.
|
||
Falsche Parameter (`metaId`, `id`, `taskMetaId`) werden serverseitig ignoriert –
|
||
die API antwortet dann mit einem Fehler in `a00.un.un` (nicht Top-Level!),
|
||
der vom Standard-Error-Check im fw_client übersehen wird.
|
||
|
||
**Response-Struktur (verifiziert):**
|
||
```
|
||
a00.r.r → vollständiges Task-Objekt mit aktuellem Stand (inkl. lastAction: "MARK_COMPLETED")
|
||
```
|
||
|
||
### `metadelete` – Objekt löschen
|
||
POST https://api.familywall.com/api/metadelete
|
||
Content-Type: application/x-www-form-urlencoded
|
||
|
||
**Body-Parameter (verifiziert):**
|
||
|
||
| Parameter | Pflicht | Wert |
|
||
|---|---|---|
|
||
| `id` | ja | Task-ID aus `get_tasks` (**WICHTIG: `id`, nicht `metaId`!**) |
|
||
|
||
Hinweis: `metadelete` ist ein generischer Lösch-Endpoint für beliebige Objekte (Tasks, etc.).
|
||
Entsprechend vorsichtig verwenden.
|
||
|
||
**Response-Struktur (verifiziert):**
|
||
```
|
||
a00.r.r → "true" (String)
|
||
```
|
||
|
||
**Fehlerverhalten:** Bei falschem Parameter-Namen (`metaId`, `taskId` etc.) antwortet die API
|
||
mit `{"a00": {"un": {"un": {"message": "missing value in: id"}}}}` auf Top-Level ohne `un`-Key
|
||
→ wird vom fw_client fälschlich als Erfolg interpretiert. Daher ist der korrekte Parameter-Name
|
||
kritisch.
|
||
|
||
### `wallmood` – Wall-Post liken
|
||
POST https://api.familywall.com/api/wallmood
|
||
Content-Type: application/x-www-form-urlencoded
|
||
|
||
**Body-Parameter (verifiziert via FW_DEBUG=1):**
|
||
|
||
| Parameter | Pflicht | Wert |
|
||
|---|---|---|
|
||
| `wall_message_id` | ja | Post-ID aus `get_activities` (z.B. `wall/23431854_31119189`) |
|
||
| `moodType` | ja | `"STAR"` (einziger bekannter Wert — entspricht dem Like-Button in der App) |
|
||
|
||
**Verhalten (verifiziert):**
|
||
- `wallmood` mit `moodType: "STAR"` ist eine **idempotente SET-Operation** — kein Toggle!
|
||
- Mehrfaches Aufrufen mit denselben Parametern hinterlässt denselben Zustand
|
||
- `moodType: "LIKE"` wurde serverseitig als `"STAR"` gespeichert → korrekter Wert ist `"STAR"`
|
||
- Nur für Post-Typ `STATUS` wirksam; `FAMILY_CREATED` und vermutlich System-Posts ignorieren den Call
|
||
|
||
**Unlike – nicht implementierbar (Stand: April 2026):**
|
||
|
||
Ausgiebig getestete Ansätze, die alle fehlschlugen:
|
||
|
||
| Ansatz | Ergebnis |
|
||
|---|---|
|
||
| `moodType: "NONE"` / `"REMOVE"` / `"DELETE"` / `""` | moodMap unverändert |
|
||
| `moodType` ganz weglassen | moodMap unverändert |
|
||
| `moodStarShortcut: "false"` als Parameter | moodMap unverändert |
|
||
| Alternative Endpoints: `wallmooddelete`, `wallmoodremove`, `wallmoodelete`, `wallunmood`, `wallcommentdelete`, `wallmoodstar`, `wallstar`, `wallreact`, `wallreactdelete` | alle: `"The call X is not registered"` (502) |
|
||
| `metadelete` auf Mood-Comment-ID | löscht den Comment, aber `moodStarShortcut` bleibt gesetzt |
|
||
|
||
Die Web-App nutzt einen Service Worker, der Requests abfängt — der echte Unlike-Payload
|
||
ist dadurch nicht per Browser-DevTools inspizierbar. Unlike bleibt bis zur weiteren
|
||
Analyse nicht unterstützt.
|
||
|
||
**Response-Struktur (verifiziert):**
|
||
```
|
||
a00.r.r → vollständiges Wall-Message-Objekt
|
||
.metaId → Post-ID (= wallMessageId)
|
||
.wallMessageId → Post-ID (identisch zu metaId)
|
||
.refType → Aktivitätstyp (z.B. "STATUS")
|
||
.refAction → Letzte Aktion (z.B. "MOOD_STAR")
|
||
.text → Post-Text
|
||
.postAccountId → Account-ID des Post-Autors
|
||
.accountId → Account-ID (identisch)
|
||
.moodMap → dict: accountId → ["STAR"] wenn geliked
|
||
.moodStarShortcut → "true" wenn Like-Shortcut aktiv
|
||
.comments[] → Liste von Kommentaren/Moods
|
||
.commentId → Kommentar-ID (Format wallComment/...)
|
||
.accountId → Account-ID des Kommentators
|
||
.mood → Mood-Typ (z.B. "STAR")
|
||
.text → Kommentartext (falls vorhanden)
|
||
.creationDate → Erstelldatum (ISO 8601)
|
||
.creationDate → Erstelldatum des Posts (ISO 8601)
|
||
.modifDate → Letztes Änderungsdatum (ISO 8601)
|
||
.familyId → Kreis-ID (Format family/...)
|
||
.rights.canUpdate → "true"/"false"
|
||
.rights.canDelete → "true"/"false"
|
||
```
|
||
|
||
**Like-Zustand bestimmen (zwei Indikatoren, beide auswerten):**
|
||
|
||
| Feld | Typ | Bedeutung |
|
||
|---|---|---|
|
||
| `moodStarShortcut` | `"true"` / `"false"` | Primär: direktes User-Like-Flag für den anfragenden Account |
|
||
| `moodMap[accountId]` | `["STAR"]` | Sekundär: accountId → Mood-Liste; enthält `"STAR"` wenn geliked |
|
||
|
||
Beide Indikatoren können den Like-Zustand korrekt abbilden — je nach API-internem
|
||
Speicherpfad ist nur einer gesetzt. Immer beide prüfen.
|
||
|
||
**Silent-Fail-Szenarien (API antwortet 200, aber Like wird nicht gesetzt):**
|
||
- **Self-Like-Restriction**: Eigener Post kann nicht geliked werden
|
||
(verifiziert: Account 23431898 kann Post `wall/23431854_31119189` nicht liken,
|
||
obwohl API regulär antwortet — `modifDate` bleibt eingefroren)
|
||
- **Unsupported Post-Typ**: `FAMILY_CREATED`-Posts ignorieren wallmood-Calls
|
||
- **Rate-Limit**: Nach vielen Calls kann die API Still-Fails zurückgeben
|
||
|
||
Erkennungsmerkmal für Silent-Fail: `modifDate` im Response identisch zum Vorherigen
|
||
AND `moodStarShortcut: false` AND `moodMap: {}`.
|
||
|
||
## Noch zu verifizieren
|
||
|
||
- ~~Exakter Wert für `type`-Parameter beim Login~~ → nicht senden (verifiziert per JS-Analyse)
|
||
- ~~Response-Struktur von `famlistfamily` (Kreise)~~ → a00.r.r[], metaId + name (verifiziert)
|
||
- ~~Ob `a03call=tasklistsync` benötigt wird~~ → **nein**, kein gültiger Endpoint (verifiziert)
|
||
- Listen-IDs aus `a01.r.r.updatedCreated[].sortingIndexByTaskList`-Keys (verifiziert)
|
||
- Listen-Namen und Zähler (remainingTaskNumber, totalTaskNumber) → noch unbekannt
|
||
- Kreis-Zuordnung in `accgetallfamily`-Response → noch offen
|
||
- ~~Ob `partnerScope` / `withStateBean` benötigt werden~~ → nein (verifiziert)
|
||
- Session-Lebensdauer (irrelevant da kein Caching)
|
||
- ~~`taskcreate2`: Response-Struktur~~ → `a00.r.r` = vollständiges Task-Objekt (verifiziert)
|
||
- ~~`taskupdate2`: ob `taskListId` Pflichtfeld ist~~ → **nein**, nicht erforderlich (verifiziert)
|
||
- ~~`taskmark`: korrekter Parameter-Name~~ → **`taskId`** (nicht `metaId`!) (verifiziert)
|
||
- ~~`metadelete`: korrekter Parameter-Name + Response-Struktur~~ → **`id`**, Response `"true"` (verifiziert)
|
||
- ~~`wallmood`: Parameter-Name `wallId`~~ → **`wall_message_id`** (verifiziert via API-Fehlermeldung)
|
||
- ~~`wallmood`: `moodType`-Werte, Toggle vs. explizit, Response-Struktur~~ → verifiziert: idempotentes SET mit `"STAR"`, kein Toggle (siehe oben)
|
||
- `wallmood` Unlike: Mechanismus unbekannt — Service Worker verhindert Browser-Inspektion; alle getesteten Ansätze fehlgeschlagen (siehe oben) |