API: GET /quest/available
GET /quest/available
Returns the set of quests visible to the authenticated walker right now, evaluated against the four canonical gates. Used by the mobile quest log (11e-2 wireframe) to render Active / Completed / Hidden sections.
Status: draft contract — implementation in sub-phase 13-5.
Related: data/src/schemas/quest.ts, data/src/content/quests/, Prisma model QuestProgress, POST /quest/start, POST /quest/:id/step/:n/advance.
Sub-phase 13-5 scope
This endpoint is part of the Phase 13 quest-log loop:
- Mobile renders the quest log from
GET /quest/available. - Mobile starts a quest via
POST /quest/start(gate evaluator re-validates). - Mobile advances steps via
POST /quest/:id/step/:n/advance(this doc’s sibling). - Mobile completes a quest via
POST /quest/complete(grants the D-016 flat 1pt).
Test-phase mock mode
Per ADR-0006:
- No App Check token requirement.
X-Firebase-AppCheckheader is ignored. - No caching. The gate evaluator runs server-side on every request. Caching is a VPS-migration (Phase 14) concern.
- Repeatable quests filtered. Quests marked
repeatable: trueare excluded fromavailable[]until the repeatable-quest replay table ships (post-13-5).
Endpoint
| Field | Value |
|---|---|
| Method | GET |
| Path | /quest/available |
| Auth | Internal session JWT (Bearer) |
| Idempotent | Yes — pure read |
| Rate limit | 60 req/min/walker |
Request
Headers
| Header | Required | Notes |
|---|---|---|
Authorization | yes | Bearer <internal-session-JWT> |
No body, no query parameters.
Responses
200 OK
{ "available": [ { "id": "quest.001-first-road", "name": { "en": "Quest 001 — The First Road", "pl": "Quest 001 — Pierwszy Trakt" }, "description": { "en": "The Walker's first commission. Bertranda has issued the wooden ring...", "pl": "Pierwsze zlecenie Walkera. Bertranda wydała drewnianą obrączkę..." }, "regionId": "region.plenny", "factionId": null, "currentStepIndex": 2, "totalSteps": 5, "status": "in-progress", "rewards": { "treePoints": 1 }, "prerequisites": { "metAt": "2026-05-20T10:00:00Z" } } ], "hiddenCount": 1, "evaluatorVersion": "13-5.v1"}| Field | Type | Notes |
|---|---|---|
available[] | array | All quests visible to the walker. Includes not-started, in-progress, and completed. |
available[].id | string | Quest id from data/src/content/quests/. |
available[].name | { en, pl } | Bilingual quest name. Chrome surfaces use name.en per D-011; prose uses name.pl. |
available[].description | { en, pl } | Bilingual quest description for the detail screen. |
available[].regionId | string | Region id (region badge on quest row, 11e-2 §3.1). |
available[].factionId | string | null | Quest’s faction tie (story quests are null; faction-type quests carry an id). |
available[].currentStepIndex | number | null | 0-indexed step the walker is currently on. null if not-started; equals totalSteps for completed. Derived from QuestProgress.currentStepId. |
available[].totalSteps | number | quest.steps.length from the data layer. |
available[].status | enum | "not-started" | "in-progress" | "completed". Drives section assignment in the quest log. |
available[].rewards.treePoints | number | Flat 1 per D-016. Surface-differentiation for arc-vs-courier is D-016 follow-up #4 (cosmetic, not point delta). |
available[].prerequisites.metAt | string (ISO) | When this quest’s gates last became satisfied. In mock mode: startedAt for in-progress/completed, current time for not-started. |
hiddenCount | number | Quests blocked by gates that are NOT returned in available[]. Surfaces “more to unlock” hint per 11e-2 §3.1 hidden slot. |
evaluatorVersion | string | Forensic + cache-invalidation handle. Bumps when evaluator semantics change. Today: "13-5.v1". |
401 UNAUTHORIZED
Missing or invalid Bearer token.
404 NOT_FOUND
Walker not found for the authenticated user (defense-in-depth — should not happen if /auth/callback succeeded).
Gate evaluator
Server-side, four gates are evaluated per quest. A quest is in available[] iff ALL gates pass (or it is already in-progress/completed).
| Gate | Status in Phase 13 | Source |
|---|---|---|
| PREREQUISITE | Active | quest.prerequisites[] must all have completedAt IS NOT NULL in QuestProgress. |
| REGION_GATE | Active | Walker’s totalLifetimeSteps must meet region.gatingSteps for quest.regionId. |
| KEYSTONE_GATE | Schema active; adapter mirror pending | Pillar 3 / D-006. QuestSchema.keystoneRequirement?: { keystoneId } shipped in commit 8f40e35. Backend’s local common/game-content.ts adapter does not yet mirror the field on the runtime Quest shape; evaluator inspects with in-guards so the gate is a no-op pass-through until the mirror lands. |
| FACTION_REP_GATE | Schema active; adapter mirror pending | QuestSchema.factionRepRequirement?: { factionId, minTier } shipped in commit 8f40e35. minTier is a TIER INDEX (0..N-1) into reputationTiers[], NOT raw rep. Evaluator resolves via deriveTierIndex. Same adapter-mirror pending state as keystone gate. |
Adapter mirror follow-up (tech-architect, W-level)
The canonical schema in data/src/schemas/quest.ts carries the two optional fields. The backend’s local game-content adapter (backend/src/common/game-content.ts) strips them from the runtime Quest interface. The evaluator (backend/src/quest/quest-gate-evaluator.service.ts) inspects the fields with in-guards so:
- Quests authored without the fields → pass-through (no gate).
- Quests authored with the fields → gate fires correctly once the adapter mirror extends to surface them.
Mirror extension scope: add keystoneRequirement?: { keystoneId: string } and factionRepRequirement?: { factionId: string; minTier: number; minRep?: number } to the Quest interface in game-content.ts; populate the runtime quests[] instances from the canonical data/src/content/quests/* instances. Estimated < 50 LOC; B-level.
The inverse relationship (KeystoneSchema.unlockQuestId — keystone allocatable only when quest is completed) is honoured by POST /tree/allocate today. The symmetric direction (quest visible only when keystone is allocated) is the new 13-5 hook required for the closed Pillar 3 loop.
Hidden quest semantics
Quests blocked by any gate are NOT returned in available[]. Instead, the count is surfaced via hiddenCount. The mobile UI (11e-2 §3.1) renders a placeholder ”???” slot with the count, communicating “there is more to unlock” without revealing the quest.
In Phase 13 the hidden slot is binary (count only, no per-quest detail). When the keystone gate goes active, the wireframe specifies showing the region badge on hidden slots; if that requires per-slot detail, the response shape will grow hidden[] in a non-breaking additive update.
Validation order
- JWT verifies → 401
UNAUTHORIZED - Walker resolves in Postgres → 404 (internal)
- Quest evaluator runs against the four gates → produces
available[]+hiddenCount - Return 200
Forward compatibility
evaluatorVersionis a stable handle for clients to invalidate cached state when evaluator semantics shift (e.g. when the keystone gate goes from pass-through to active, version bumps to13-10.v1or similar).available[]ordering is server-defined and not stable — the mobile client should not depend on order.- New fields on
AvailableQuestare additive; clients should ignore unknown fields.
Mobile contract reference
mobile-developer in sub-phase 13-5 builds Quest Log + Quest Detail screens against this contract. Key bindings:
- Quest Log Active section (11e-2 §3.1) ←
available[].filter(q => q.status === "in-progress") - Quest Log Completed section (collapsed) ←
available[].filter(q => q.status === "completed") - Quest Log “More to unlock” slot ←
hiddenCount > 0 - Quest detail header ←
name.en(chrome, D-011) + region badge - Quest detail prose ←
description.enordescription.plper UI locale - Step progress bar ←
(currentStepIndex + 1) / totalStepsfor in-progress quests - Reward callout ←
rewards.treePoints(always 1 in Phase 13 per D-016)
Open follow-ups
- Backend game-content adapter mirror for
keystoneRequirement+factionRepRequirement(tech-architect, B-level, post-13-5). - Caching strategy — defer to Phase 14 (VPS migration); design likely Redis with short TTL, invalidated on quest-progress write.
- Per-quest hidden detail (region badge on hidden slot) — UI follow-up post-keystone-gate adapter mirror activation.
- Hidden quest reveal hook — when
POST /tree/allocateallocates a keystone, the nextGET /quest/availableshows quests whosekeystoneRequirement.keystoneIdmatches. Sub-phase 13-10 wiring; evaluator already handles this.