API: GET /walker/profile
GET /walker/profile
Returns the walker’s full profile state in one call. Mobile reads this on app foreground and after critical state-changing actions (allocation, quest completion, sync). It is the canonical cold-start payload for the home screen.
Status: draft contract — implementation in phase 8b. Updated 2026-05-20 (sub-phase 13-3) for D-016 progression fields.
Related: ADR-0001 (component topology), ADR-0003 (streak fields), D-016 (progression — emergent level, treePointsBanked/treePointsSpent), POST /walker/class.
D-016 progression fields (updated 2026-05-20)
The walker object carries D-016 progression state:
| Field | Type | Notes |
|---|---|---|
level | int | Emergent view (= treePointsSpent). No DB field per D-016 §“Walker has no level integer field”. Surfaced on the wire so ADR-0007 mobile DTOs stay valid. |
classId | string | null | References data/src/content/classes/ by id (e.g. class.cartographer). null after /auth/callback lazy-create until the walker completes class-pick via POST /walker/class. |
treePointsBanked | int | Unspent passive points („Punkty w skarbcu” per D-016 §Player-facing surface). Was availablePoints pre-D-016 — wire field renamed. |
treePointsSpent | int | Cumulative points consumed by allocations. Source of truth for the emergent level. |
Mobile rendering: Home shows Poziom <level> as a single number per D-016. Banked banner when treePointsBanked > 0. No XP bar. Tree page shows <treePointsSpent> / 180 allocated.
Test-phase mock mode
Phase 8b ships against the test-phase infrastructure per ADR-0006, not the production target. Differences from the contract below:
- No App Check token requirement.
X-Firebase-AppCheckheader is ignored. - No caching headers required. The
Cache-Control: private, max-age=15, stale-while-revalidate=60policy andIf-None-Match/ETagflow are nice-to-have, not mandatory.Cache-Control: no-storeis acceptable in mock mode. Phase 11 production migration tightens this. - Faction tier computation is skipped if no
FactionReprows exist for the walker. In production,/auth/callbackinitializes neutral tier 0 rows for all 5 factions; mock mode may also do so but the server tolerates their absence and returns an emptyfactionRanksarray instead of erroring. flags.isInQuarantineis alwaysfalsein mock mode (no quarantine state exists without the reconciliation worker).subscription.tieris always"none"in mock mode (no payment integration in phase 8b).
The production contract below describes the target state, unfrozen when the production migration is greenlit.
Endpoint
| Field | Value |
|---|---|
| Method | GET |
| Path | /walker/profile |
| Auth | Internal session JWT (Bearer) |
| Idempotent | Yes (read-only) |
| Rate limit | 60 req/min/walker |
Request
Headers
| Header | Required | Notes |
|---|---|---|
Authorization | yes | Bearer <internal-session-JWT> |
X-Firebase-AppCheck | yes | Standard mobile request hardening |
Accept-Language | optional | pl or en — server picks one for localized strings in the response (region name, faction name, quest titles). Defaults to en. |
If-None-Match | optional | ETag of prior payload; server returns 304 if no change |
Query parameters
None at MVP. Phase 11 may add ?fields=region,streak projection.
Response
200 OK
{ "walker": { "id": "01HM4...", "displayName": "Wanderer-9f3a2c", "level": 12, "classId": "class.cartographer", "totalLifetimeSteps": 472300, "treePointsBanked": 7, "treePointsSpent": 12, "currentRegionId": "region.plenny", "createdAt": "2026-04-22T10:14:22Z", "lastActiveAt": "2026-05-18T20:43:00Z" }, "region": { "id": "region.plenny", "name": { "en": "Plenny", "pl": "Plennia" }, "color": "#7DA86F", "gatingSteps": 0, "primaryClassId": null, "factionIds": ["faction.unfinished-guild", "faction.practical-academy"] }, "streak": { "currentLengthDays": 14, "lastAttestedDate": "2026-05-17", "bonusTier": "T1_7D", "energyMultiplier": 1.20, "decayAt": "2026-05-19" }, "activeQuests": [ { "questId": "quest.001-first-road", "currentStepId": "step-2", "progress": { "stepsWalked": 3204, "stepsRequired": 5000 }, "rewardPreview": { "points": 1, "xp": 50 } } ], "factionRanks": [ { "factionId": "faction.unfinished-guild", "reputation": 320, "tierIndex": 2, "tierName": { "en": "Surveyor", "pl": "Lustrator" } }, { "factionId": "faction.practical-academy", "reputation": 80, "tierIndex": 1, "tierName": { "en": "Apprentice", "pl": "Czeladnik" } }, { "factionId": "faction.quiet-lights", "reputation": 0, "tierIndex": 0, "tierName": { "en": "Stranger", "pl": "Obcy" } }, { "factionId": "faction.free-merchantry", "reputation": 0, "tierIndex": 0, "tierName": { "en": "Stranger", "pl": "Obcy" } }, { "factionId": "faction.wild-path", "reputation": -20, "tierIndex": 0, "tierName": { "en": "Stranger", "pl": "Obcy" } } ], "subscription": { "tier": "none" | "guild-master", "validUntil": "2026-06-18T00:00:00Z" }, "flags": { "isFirstLogin": false, "hasPendingDelete": false, "isInQuarantine": false, "appUpgradeAvailable": null }}304 Not Modified
Returned when If-None-Match matches current ETag. Empty body.
401 Unauthorized
Session JWT invalid.
410 Gone
{ "error": "WALKER_DELETED", "message": "This walker account has been deleted."}Returned when User.deleteRequestedAt triggered hard-delete but the session JWT was still valid in cache. Client should clear local session and prompt for re-signup.
Caching
Server emits Cache-Control: private, max-age=15, stale-while-revalidate=60 with strong ETag.
max-age=15: minor staleness OK for 15s (covers refresh bursts on home screen mount).stale-while-revalidate=60: client may show last payload up to 75s while revalidating.
Cache invalidation triggers (server-side ETag bump):
- Step accepted via reconciliation
- Tree allocation (provisional or final)
- Quest progress event
- Subscription state change
- Faction rep delta
Mobile can also pass Cache-Control: no-cache to force a fresh read (used after walker performs a state-changing action).
Localization
Server returns BOTH en and pl for LocalizedString fields (per data/src/schemas/common.ts). Mobile renders the one matching Accept-Language. This contract avoids round-trips when the walker changes language in settings.
Telemetry
info-level log with: walkerId, etag, cacheHit (304 vs 200), latencyMs. No PII.
Open follow-ups
- WebSocket push variant: phase 11 adds
walker.profile.updatedevents over the realtime gateway so the home screen updates without polling. - Field projection (
?fields=): phase 11 — currently always returns the full payload (~2-4 KB, acceptable). - Faction rank reward preview (what unlocks at next tier): phase 11 — closed beta shows only current tier.