API Routes Additions¶
Two new routes plus modifications to existing mutating routes. All new routes require a valid access JWT.
POST /api/minigame/session¶
Issues a signed minigame session token for an activity the character is about to perform.
Request¶
activity_ref_id is the board order id, expedition template id, recipe id, or other entity reference depending on activity_type. See Activity → Minigame Map for valid activity_type values.
Worker Logic¶
- Validate JWT → extract
character_id - Check
character_flag(passive_mode)— if set, return synthetic session (see Passive Mode below) - Validate
activity_ref_idexists and is accessible to character (not expired, not already accepted, etc.) - Resolve
minigame_typefromactivity_type(and expedition category if applicable) - Fetch character's skill band for the relevant skill from D1
- Generate session:
jti = crypto.randomUUID()seed = (crypto.getRandomValues(new Uint32Array(1))[0]) | 0— 32-bit signedissued_at = Math.floor(Date.now() / 1000)expires_at = issued_at + 300min_duration_ms,max_duration_msfrom type table- Compute HMAC-SHA256 signature
- INSERT into
minigame_session - Return token
Response 200¶
{
"token": "<signed_payload.hmac>",
"seed": 2147483647,
"type": "rhythm",
"min_duration_ms": 8000,
"max_duration_ms": 45000,
"skill_band": 3
}
Passive Mode Response 200¶
If character has passive_mode = 1:
The client skips the minigame and uses result_token directly in the subsequent activity action.
Error Responses¶
| Code | Body | Reason |
|---|---|---|
| 401 | { "error": "unauthorized" } |
Missing or invalid JWT |
| 403 | { "error": "character_mismatch" } |
JWT character does not match context |
| 404 | { "error": "activity_not_found" } |
activity_ref_id does not exist or expired |
| 409 | { "error": "activity_unavailable" } |
Board order already accepted, expedition departed, etc. |
| 429 | { "error": "rate_limited" } |
20 session requests/character/min |
POST /api/minigame/result¶
Submits a completed minigame session for validation. Returns a result token for use in the subsequent activity action.
Request¶
{
"token": "<session_token>",
"score": 0.82,
"duration_ms": 14300,
"event_hash": "base64-sha256-string"
}
Worker Logic¶
Full validation as specified in Minigame Architecture — Worker Validation.
On success:
- Mark
minigame_session.used = 1 - Compute
modifier_applied = clamp(0.7 + score * 0.7, 0.7, 1.4) - INSERT
minigame_resultrow - Sign and return result token (expires 10 minutes)
Response 200¶
Error Responses¶
| Code | Body | Reason |
|---|---|---|
| 400 | { "error": "duration_too_short" } |
duration_ms < min_duration_ms |
| 400 | { "error": "duration_too_long" } |
duration_ms > max_duration_ms |
| 400 | { "error": "invalid_event_hash" } |
event_hash does not match seed derivation |
| 400 | { "error": "invalid_signature" } |
Token HMAC verification failed |
| 401 | { "error": "unauthorized" } |
Missing or invalid JWT |
| 403 | { "error": "character_mismatch" } |
Token sub ≠ JWT character_id |
| 404 | { "error": "session_not_found" } |
jti not in D1 (never issued or pruned) |
| 408 | { "error": "session_expired" } |
exp < now |
| 409 | { "error": "session_already_used" } |
used = 1 (replay attack or duplicate submit) |
Modified: Existing Mutating Routes¶
All board, craft, gather, trade, and healing action routes now require a Minigame-Result header containing the signed result token from POST /api/minigame/result. Arcana skills are passive — no standalone arcana route exists.
Header¶
Worker change (all affected routes)¶
Before executing business logic, the Worker:
- Reads
Minigame-Resultheader - Verifies HMAC signature
- Checks
exp— rejects if expired (10-minute window) - Checks
submatches JWT character_id - Checks result's
jtimatches aminigame_resultrow that maps to the correctactivity_typeandactivity_ref_id - Extracts
modifier_appliedand passes to settlement logic
If missing or invalid:
HTTP 403.Affected Routes¶
Only routes that trigger active activities require a Minigame-Result header. Routes for passive skills (Routefinding, Campcraft, Weather Sense, etc.) do not exist as standalone endpoints — those skills feed into the settlement pass internally.
| Route | Activity Types |
|---|---|
POST /api/board/:id/accept |
expedition_accept |
POST /api/crafting/start |
craft_smithing | craft_carpentry | craft_leatherworking | craft_clothworking | craft_tinkering | craft_artifice | craft_masonry | craft_alchemy | craft_cooking | craft_preservation |
POST /api/gathering/start |
gather_mining | gather_quarrying | gather_logging | gather_salvage | gather_herbalism | gather_farming | gather_hunting |
POST /api/trade/offer |
trade | appraise | negotiate |
POST /api/heal/start |
heal |
POST /api/scouting/start |
scout | survey | cartograph |
POST /api/animals/interact |
animal_handling | beastbond | tame | mount_care | mount_train |
Routes NOT affected (read-only / non-resolution)¶
GETroutes (all)POST /api/auth/*POST /api/messages/*POST /api/minigame/*(these are the new routes)POST /api/characters/*(character creation/update)POST /api/guild/*(membership actions)POST /api/governance/*(voting)- Any route for passive skills — passive skills have no standalone API endpoints; they feed settlement internally
GET /api/minigame/history¶
Returns the character's recent minigame results for display in the activity log.
Response 200:
{
"results": [
{
"jti": "uuid",
"minigame_type": "rhythm",
"activity_type": "expedition_accept",
"score": 0.82,
"modifier_applied": 1.274,
"submitted_at": 1716000000
}
],
"total": 47
}
Read from minigame_result JOIN minigame_session USING (jti) ordered by submitted_at DESC. No cache — D1 direct read. Max limit = 50.
PATCH /api/characters/me/passive-mode¶
Toggles Passive Mode for the authenticated character.
PATCH /api/characters/me/passive-mode
Authorization: Bearer <access_jwt>
Content-Type: application/json
{ "enabled": true }
Response 200:
Worker upserts character_flag(character_id, 'passive_mode', enabled ? 1 : 0, now).
No confirmation required — player can toggle freely. Takes effect on the next session issuance.