Skip to content

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:

FieldTypeNotes
levelintEmergent 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.
classIdstring | nullReferences 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.
treePointsBankedintUnspent passive points („Punkty w skarbcu” per D-016 §Player-facing surface). Was availablePoints pre-D-016 — wire field renamed.
treePointsSpentintCumulative 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-AppCheck header is ignored.
  • No caching headers required. The Cache-Control: private, max-age=15, stale-while-revalidate=60 policy and If-None-Match / ETag flow are nice-to-have, not mandatory. Cache-Control: no-store is acceptable in mock mode. Phase 11 production migration tightens this.
  • Faction tier computation is skipped if no FactionRep rows exist for the walker. In production, /auth/callback initializes neutral tier 0 rows for all 5 factions; mock mode may also do so but the server tolerates their absence and returns an empty factionRanks array instead of erroring.
  • flags.isInQuarantine is always false in mock mode (no quarantine state exists without the reconciliation worker).
  • subscription.tier is 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

FieldValue
MethodGET
Path/walker/profile
AuthInternal session JWT (Bearer)
IdempotentYes (read-only)
Rate limit60 req/min/walker

Request

Headers

HeaderRequiredNotes
AuthorizationyesBearer <internal-session-JWT>
X-Firebase-AppCheckyesStandard mobile request hardening
Accept-Languageoptionalpl or en — server picks one for localized strings in the response (region name, faction name, quest titles). Defaults to en.
If-None-MatchoptionalETag 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.updated events 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.