Being Human — Internal Docs
API

Achievements

Definitions, unlocks, and Steam mirroring

This backend is the canonical achievement store; Steam is a mirror. That ordering matters: it means achievements work in the browser build (no Steam), survive Steam outages, and can be re-synced.

Definitions

One source of truth: packages/shared/src/achievements.ts.

export const ACHIEVEMENTS = [
  { key: "FIRST_STEPS", name: "First Steps",
    description: "Survive your first day.", hidden: false },
  // ...
] as const;

The key is the stable API name used by this backend, the game client, and Steamworks — when creating achievements on the partner site, enter the same key as the achievement's API Name so mirroring is 1:1. Adding an achievement = adding an entry here; the Zod enum, the OpenAPI docs, and the endpoints all follow. The current entries are placeholders keyed to real game beats — rename freely before launch.

Endpoints

# Definitions (public)
curl http://localhost:3001/api/achievements

# Your progress (signed in) — every definition + unlockedAt or null
curl http://localhost:3001/api/achievements/me -b cookies.txt

# Unlock (signed in, idempotent)
curl -X POST http://localhost:3001/api/achievements/unlock \
  -H 'Content-Type: application/json' -b cookies.txt \
  -d '{"key": "FIRST_STEPS"}'
# → {"ok": true, "key": "FIRST_STEPS", "alreadyUnlocked": false}

Unknown keys are rejected at validation (400) — the enum comes from the definitions.

What a first-time unlock triggers

  1. Row in user_achievements (the canonical fact).
  2. Discord: 🏆 **Player** unlocked *First Steps* (if webhook configured).
  3. Steam mirror — if the user has a linked Steam account (account row with providerId: "steam", whose accountId is their steamId64) and STEAM_WEB_API_KEY + STEAM_APP_ID are set, the unlock is pushed via ISteamUserStats/SetUserStatsForGame. Failures are logged, never surfaced — see Steam.

Repeat unlocks are no-ops (alreadyUnlocked: true) and trigger nothing.

How the game will report unlocks

The Rust client (or eventually the multiplayer server) calls POST /api/achievements/unlock with the player's session. Until Steam sign-in lands, unlocks simply accumulate server-side; once an account links Steam, future unlocks mirror automatically. A backfill sync for pre-link unlocks is a small follow-up (iterate user_achievements, call the Steam client per row).

On this page