Skip to content

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

POST /api/minigame/session
Authorization: Bearer <access_jwt>
Content-Type: application/json
{
  "activity_type": "expedition_accept",
  "activity_ref_id": "board-item-uuid-here"
}

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

  1. Validate JWT → extract character_id
  2. Check character_flag(passive_mode) — if set, return synthetic session (see Passive Mode below)
  3. Validate activity_ref_id exists and is accessible to character (not expired, not already accepted, etc.)
  4. Resolve minigame_type from activity_type (and expedition category if applicable)
  5. Fetch character's skill band for the relevant skill from D1
  6. Generate session:
  7. jti = crypto.randomUUID()
  8. seed = (crypto.getRandomValues(new Uint32Array(1))[0]) | 0 — 32-bit signed
  9. issued_at = Math.floor(Date.now() / 1000)
  10. expires_at = issued_at + 300
  11. min_duration_ms, max_duration_ms from type table
  12. Compute HMAC-SHA256 signature
  13. INSERT into minigame_session
  14. 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:

{
  "passive": true,
  "result_token": "<signed_result_token>",
  "modifier": 1.0,
  "score": 0.5
}

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

POST /api/minigame/result
Authorization: Bearer <access_jwt>
Content-Type: application/json
{
  "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:

  1. Mark minigame_session.used = 1
  2. Compute modifier_applied = clamp(0.7 + score * 0.7, 0.7, 1.4)
  3. INSERT minigame_result row
  4. Sign and return result token (expires 10 minutes)

Response 200

{
  "result_token": "<signed_result_token>",
  "modifier": 1.274,
  "score": 0.82
}

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.

Minigame-Result: <result_token>

Worker change (all affected routes)

Before executing business logic, the Worker:

  1. Reads Minigame-Result header
  2. Verifies HMAC signature
  3. Checks exp — rejects if expired (10-minute window)
  4. Checks sub matches JWT character_id
  5. Checks result's jti matches a minigame_result row that maps to the correct activity_type and activity_ref_id
  6. Extracts modifier_applied and passes to settlement logic

If missing or invalid:

{ "error": "minigame_result_required" }
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)

  • GET routes (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.

GET /api/minigame/history?limit=20&offset=0
Authorization: Bearer <access_jwt>

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:

{ "passive_mode": true }

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.