Worksonar Mobile API v1

REST-API für die geplante native Mitarbeiter-App. Klar getrennt von der Admin-API (/api/v1). Diese Seite ist auch als Markdown verfügbar; siehe auch die Mobile-Roadmap.

1. Einleitung

Die Mobile-API liefert die Teilnehmer-Sicht für native iOS/Android-Apps: Survey-Schemas, Antwort-Submission, Push-Token-Registrierung. Sie ist nicht die Admin-API (die heißt weiter /api/v1/...). Mandantenisolation ist über den JWT erzwungen.

UmgebungBase-URL
Diese Instanzhttps://beta.worksonar.de/api/mobile/v1
Hosted (Beispiel)https://app.worksonar.de/api/mobile/v1

Versionierung über das URL-Präfix (/mobile/v1). Breaking Changes erscheinen ausschließlich in einer neuen Major-Version mit mindestens 6 Monaten Parallelbetrieb.

2. Authentifizierung

Alle geschützten Endpoints erwarten einen JWT im Authorization: Bearer <jwt>-Header. Der JWT wird ausschließlich auf dem Server signiert (HMAC-SHA256) und ist 7 Tage gültig.

3. Auth-Endpoints (kein JWT erforderlich)

POST /auth/by-token

Tauscht einen Long-lived Participant-Token (aus dem Einladungs-Link) gegen einen Mobile-JWT.

{ "token": "PARTICIPANT_TOKEN" }

Response 200: { jwt, expires_at, participant: {…}, tenant: {…} }

POST /auth/by-access-code

Anonyme Teilnahme via 6–8-stelligen Kampagnen-Access-Code. Der JWT ist auf diese Kampagne beschränkt (anon: true).

{ "access_code": "CON-GBU" }

POST /auth/magic-link/request

Schickt einen 6-stelligen Code + Deep-Link per E-Mail. Aus Datenschutzgründen wird immer 200 OK geantwortet — auch wenn die E-Mail unbekannt ist.

{ "email": "max@firma.de" }

POST /auth/magic-link/verify

Verifiziert den Code zusammen mit dem aus dem Deep-Link extrahierten link_token. Der Code allein reicht nicht aus (Brute-Force-Schutz).

{ "code": "123456", "link_token": "..." }

4. Geschützte Endpoints (JWT erforderlich)

GET /me

Profil + Liste aktiver Kampagnen mit Status (pending/invited/completed).

GET /campaigns/:cid

Liefert Survey-Schema, Submission-Regeln und Anonymitätsschwelle.

POST /campaigns/:cid/submit

Speichert Antworten und markiert den Teilnehmer als completed.

{
  "answers": { "vollst_1": 4, "interest_sachbezugskarte": 5 },
  "department": "Vertrieb",       // nur bei anonymem JWT
  "location": "Berlin"            // nur bei anonymem JWT
}

POST /devices/register

Push-Token (FCM/APNs) für diesen Teilnehmer registrieren. Upsert auf push_token + participant_id.

{
  "push_token": "fcm-token-string...",
  "platform":   "ios",          // ios | android | web
  "device_name": "iPhone von Max",
  "app_version": "1.0.3",
  "locale":      "de"
}

GET /devices · DELETE /devices/:id

Eigene registrierte Geräte auflisten und einzeln widerrufen (Logout-Funktion in der App).

5. Schema-Formate je Modul

Der Endpoint GET /campaigns/:cid liefert je nach Modul ein passendes Schema:

ModulFelder
gbu_psychsections[].questions[] mit Likert-Skala 1–4
benefitsprofile_questions, existing, categories, catalog, category_intros
bgmwie Benefits + who5: [{ key, text }]
Custom-Modul{ type: 'custom', module: {...} } mit eigenem Schema-Format

6. Fehler-Format

{ "error": { "code": "validation", "message": "...", "details": {...} } }
HTTPCodes
400validation
401unauthenticated · invalid_token
403plan_restriction · tenant_inactive
404not_found
422campaign_inactive
429rate_limited

7. Rate-Limits

Auth-Endpoints sind auf 30 Versuche / IP / Minute begrenzt. Geschützte Endpoints haben kein hartes Limit, werden aber auf dem Server überwacht.

8. JWT-Format

HS256-signiertes JSON, ohne JWT-Standard-Header (Format: <b64url(payload)>.<b64url(hmac-sha256)>).

{
  "sub": "participant-id",
  "tid": "tenant-id",
  "anon": false,
  "cid": "campaign-id",    // nur bei anonymer Session
  "iat": 1718208000,
  "exp": 1718812800        // +7 Tage
}

Constant-Time-Vergleich gegen Timing-Angriffe. Geheimnis wird deterministisch aus SESSION_SECRET abgeleitet.

9. Versionierung

Additive Erweiterungen (neue Felder, neue Endpoints) gelten als non-breaking; die App muss unbekannte Felder ignorieren. Breaking Changes nur in neuer Major-Version (/mobile/v2) mit Parallelbetrieb von mindestens 6 Monaten.

10. End-to-End-Flow (Beispiel)

// 1) Anmelden mit Deep-Link aus Einladungs-Mail
const auth = await fetch(API + '/auth/by-token', {
  method: 'POST', headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ token: deepLinkParams.token }),
}).then(r => r.json());

// 2) Aktive Befragungen laden
const me = await fetch(API + '/me', {
  headers: { Authorization: 'Bearer ' + auth.jwt }
}).then(r => r.json());

// 3) Schema laden & Antworten submitten
const survey = await fetch(API + `/campaigns/${me.campaigns[0].id}`, {
  headers: { Authorization: 'Bearer ' + auth.jwt }
}).then(r => r.json());

await fetch(API + `/campaigns/${campaignId}/submit`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + auth.jwt },
  body: JSON.stringify({ answers: collectedAnswers }),
});

Mobile-Roadmap · Admin-API-Doc · Datenschutz