Skip to content

Authentication

Four credentials open the API. Each one opens one group of routes and nothing else. The reference says, under every route, which one it takes.

CredentialLooks likeSent asValid on
Player tokenfp1.<payload>.<hmac>Authorization: Bearer/v1/sdk/*, /v1/account/*, ratings, comments, feedback
Game tokenfg1.<payload>.<hmac>Authorization: BearerFive /v1/sdk/* routes, for its own game
Console sessioncookie filudi_consolethe cookie, with credentials: 'include'/v1/console/*, /v1/admin/*
Server keyfsk_<site slug>_<32 base62>Authorization: Bearer/v1/server/*

A player is a guest from the first page view. POST /v1/sdk/players creates one for a site and answers { player, token }; the magic link, its 6-digit code and the password sign-in answer the same shape for an account. The token has no expiry. It opens the player’s saves, scores, events, profile and the whole /v1/account/* group, deletion included.

Create a guest and read it back

Terminal window
curl -X POST 'https://api.filudi.com/v1/sdk/players' \
-H 'content-type: application/json' \
-d '{ "site": "filudi", "locale": "en" }'
curl 'https://api.filudi.com/v1/sdk/players/me' \
-H "authorization: Bearer $FILUDI_PLAYER_TOKEN"

Rules for the token:

  • It lives in the browser, in localStorage. PlayerClient of @filudi/hub-core uses the key filudi:player:<site>. Never a cookie, never a URL, never a log.
  • It never enters a game frame. A game gets a game token instead.
  • A 401 means the API refused it: forget it and create a new guest. When the owner rotates the API’s token secret, every player is signed out this way.
  • A player belongs to one site. The same email on two sites makes two players.

A game is untrusted. The page that frames it exchanges the player token with POST /v1/sdk/game-tokens { game } and passes the result in the filudi:init message of the handshake. The token is fg1, valid 24 hours, and opens these routes for the game it names:

RouteWith a game token
GET /v1/sdk/players/meAllowed; emailMasked is null and hasPassword false
GET, PUT, DELETE /v1/sdk/saves/{gameId}Its own game
POST /v1/sdk/sessionsIts own game
/v1/sdk/leaderboards/{gameId}/*Its own game
POST /v1/sdk/eventsAn item that names another game refuses the batch with 403

Every other route answers 401 to a game token. The exchange is limited to 240 per player per hour. A hub sends a fresh token to the frame every 12 hours as filudi:token; the SDK swaps it without a reload. A portal backend gets the same token for one of its players with POST /v1/server/game-tokens.

Exchange a player token for a game token

Terminal window
curl -X POST 'https://api.filudi.com/v1/sdk/game-tokens' \
-H "authorization: Bearer $FILUDI_PLAYER_TOKEN" \
-H 'content-type: application/json' \
-d '{ "game": "gloop" }'

The developer console and the back office sign in with POST /v1/console/sign-in (email, password), then POST /v1/console/sign-in/totp when the account has an authenticator. The answer sets the cookie filudi_console: HttpOnly, Secure, SameSite Strict, host-only on the API, 14 days. The browser sends it on its own; a script adds credentials: 'include' to each fetch.

Every non-GET request under /v1/console/* and /v1/admin/* must carry an Origin header listed in the API’s console origins (developers.filudi.com and admin.filudi.com). A browser sets it. A request without one, or with another, answers 403 forbidden.

Staff without an authenticator gets 403 on every /v1/admin/* route except GET /v1/admin/me. A staff member who opens an impersonation gets a second cookie, filudi_impersonation, valid one hour; a request with the header X-Filudi-Impersonation: 1 is read from that cookie instead.

Sign in and read the account

Terminal window
curl -X POST 'https://api.filudi.com/v1/console/sign-in' \
-H 'origin: https://developers.filudi.com' \
-H 'content-type: application/json' \
-c cookies.txt \
-d '{ "email": "you@example.com", "password": "string" }'
curl 'https://api.filudi.com/v1/console/me' -b cookies.txt

A portal backend calls /v1/server/* with a key created in the console, under the site, Keys. The key is fsk_<site slug>_<32 base62 characters>, shown once; the API stores a hash and the console shows the first 12 characters. Each key carries scopes (catalog, players, tokens, leaderboards, stats) and each route says which one it needs. 600 requests a minute per key; over that, 429 with Retry-After. Only an approved site has keys, and a revoked key answers 403.

A server key never reaches a browser: /v1/server/* sends no CORS headers, so a page cannot call it. The key sits in your backend’s environment.

Read the catalog with a server key

Terminal window
curl 'https://api.filudi.com/v1/server/catalog?locale=en' \
-H "authorization: Bearer $FILUDI_SITE_KEY"

GET /v1/catalog/{site}, GET /v1/catalog/{site}/games/{slug}, the reviews of a game, the bundle and media files, POST /v1/sdk/players, POST /v1/feedback, POST /v1/console/invitation-requests and the health routes take no credential. A few routes accept a player token and answer without one too; the reference marks them “Credential, optional”.