Skip to content

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:

  1. Mobile renders the quest log from GET /quest/available.
  2. Mobile starts a quest via POST /quest/start (gate evaluator re-validates).
  3. Mobile advances steps via POST /quest/:id/step/:n/advance (this doc’s sibling).
  4. 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-AppCheck header 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: true are excluded from available[] until the repeatable-quest replay table ships (post-13-5).

Endpoint

FieldValue
MethodGET
Path/quest/available
AuthInternal session JWT (Bearer)
IdempotentYes — pure read
Rate limit60 req/min/walker

Request

Headers

HeaderRequiredNotes
AuthorizationyesBearer <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"
}
FieldTypeNotes
available[]arrayAll quests visible to the walker. Includes not-started, in-progress, and completed.
available[].idstringQuest 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[].regionIdstringRegion id (region badge on quest row, 11e-2 §3.1).
available[].factionIdstring | nullQuest’s faction tie (story quests are null; faction-type quests carry an id).
available[].currentStepIndexnumber | null0-indexed step the walker is currently on. null if not-started; equals totalSteps for completed. Derived from QuestProgress.currentStepId.
available[].totalStepsnumberquest.steps.length from the data layer.
available[].statusenum"not-started" | "in-progress" | "completed". Drives section assignment in the quest log.
available[].rewards.treePointsnumberFlat 1 per D-016. Surface-differentiation for arc-vs-courier is D-016 follow-up #4 (cosmetic, not point delta).
available[].prerequisites.metAtstring (ISO)When this quest’s gates last became satisfied. In mock mode: startedAt for in-progress/completed, current time for not-started.
hiddenCountnumberQuests blocked by gates that are NOT returned in available[]. Surfaces “more to unlock” hint per 11e-2 §3.1 hidden slot.
evaluatorVersionstringForensic + 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).

GateStatus in Phase 13Source
PREREQUISITEActivequest.prerequisites[] must all have completedAt IS NOT NULL in QuestProgress.
REGION_GATEActiveWalker’s totalLifetimeSteps must meet region.gatingSteps for quest.regionId.
KEYSTONE_GATESchema active; adapter mirror pendingPillar 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_GATESchema active; adapter mirror pendingQuestSchema.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:

  1. Quests authored without the fields → pass-through (no gate).
  2. 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

  1. JWT verifies → 401 UNAUTHORIZED
  2. Walker resolves in Postgres → 404 (internal)
  3. Quest evaluator runs against the four gates → produces available[] + hiddenCount
  4. Return 200

Forward compatibility

  • evaluatorVersion is 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 to 13-10.v1 or similar).
  • available[] ordering is server-defined and not stable — the mobile client should not depend on order.
  • New fields on AvailableQuest are 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” slothiddenCount > 0
  • Quest detail headername.en (chrome, D-011) + region badge
  • Quest detail prosedescription.en or description.pl per UI locale
  • Step progress bar(currentStepIndex + 1) / totalSteps for in-progress quests
  • Reward calloutrewards.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/allocate allocates a keystone, the next GET /quest/available shows quests whose keystoneRequirement.keystoneId matches. Sub-phase 13-10 wiring; evaluator already handles this.