Skip to content
Fyndi docs
Polski

Data model — what lives where#

This page is a simplified map of the data: which object is a row in the database, which one is a file on disk, and which one is never stored at all.

What lives where#

ObjectWhat it contains
Accounte-mail, password hash (Argon2id), role (user / mod / admin), session refresh tokens
Characterappearance (hair, hat, top, bottom, shoes, extras, colours), the handle name#tag, visibility on the map for other players
Routename, description, an ordered list of stops, distance, estimated time, travel mode (on foot / by bike / by scooter)
Stopposition, completion radius (45 m by default), completion rule (GPS / puzzle / AR), description and media
Findposition, description, model (plain or AR), sponsor it belongs to
Runone walk of a route: active → completed, or abandoned, with times and completed stops
Eventan immutable fact: a stop completed, a find collected, a route started or finished. Its unique ID is what prevents duplicates when a queue is synced after being offline
Offline packa versioned set of city content (routes, descriptions, graphics, models) to download to the phone
Media and 3D modelsfiles in the content store on the server: photos, audio, models for the map and for AR
NPC botname, kind, icon, route loop (geometry along pavements plus pace), gift (a one-off find), on/off switch and a duty calendar. The database does not store its position — the server computes it deterministically from the time
Gift from a botwho received a gift, from whom and on which day — one per player, per bot, per day, plus a 50 m radius measured by GPS
Reward ruleevent (find, stop, route) → how many tokens, a daily limit, on/off. Alongside it a wallet (the balance is computed from ledger entries, not taken on trust) and a reward catalogue with redemptions
Lobby (exploring together)a 6-character code, host, members, status (open / started / closed), a 2-hour time to live, and member positions (visible to members only, removed on leaving and after 5 minutes without a refresh)
Planner carda card from the planner: title, description (the owner's note), column, deadline — plus the issue number and labels on the GitHub side (Workflow)
Player positionthe last position used for “see others” — only with both sides' consent, kept for 5 minutes, within a 3 km radius

A note on languages: the city is Warszawa, while the API and the database use the slug warsaw. When you see warsaw in a response, it is the same city the player sees.

Answers without plaintext passwords#

Stops with a puzzle work like this:

  1. In the creator you type the question and the answer.
  2. The answer is hashed — SHA-256 over a normalised form: lower case, trimmed spaces.
  3. Only the hash is stored; the database never holds the answer in readable form.
  4. A player may type the answer in any mix of upper and lower case, and it still matches.

The same principle applies to accounts: passwords exist only as Argon2id hashes, and sessions exist as tokens that can be revoked.

Files and models#

Content is split in two on purpose: descriptions and references in the database, bytes on disk.

KindWhere it livesHow the client sees it
Photos, audio, modelsfiles in the content store on the production serveras a URL under /content/…
3D models for the map and ARthe same content store, referenced by stop or find payloada link plus scale and rotation in the payload
Character base mesha GLB file served to the app and to the websiteone file, one atlas — both clients read the same source
Offline packa versioned manifest plus files, built ahead of timedownload once, then work without a network (Offline mode)

The offline pack manifest carries the version, the file list, checksums and the mapping from routes and stops to local paths — that mapping is what allows the app to show a route with no signal at all.

Two rules are enforced by the API rather than by convention:

  • A find is never a stop on a route. Adding a find as a route stop is rejected with 400 collectible_is_not_a_route_stop, and turning an existing stop into a find is rejected with 409 point_is_route_stop. If a route is meant to pass through the place where a find lies, it gets its own stop at the same coordinates (Map and markers).
  • Stops are shared, but the order belongs to the route. The same stop can appear in several routes; the sequence is a property of the route, not of the stop.

The content API mirrors that split: stops and routes are read and written under /points and /routes, their walking geometry comes from /routing/directions (the foot profile), media goes in through /media/upload, and offline packs are listed under /packs with /packs/:city/latest for the newest one. Progress has its own endpoints — runs, completing a stop, collecting a find and syncing queued activity — and they only ever touch the calling player's own data.


Documented for app version 0.120.1 (updated 2026-09-26).

Machine translation — the Polish version prevails.

App version: 0.120.1 · API version: 0.35.0 ·

docs.fyndi.app · 2026-09-26