# Statistics

What the console shows for a site, the fields behind each chart, the filters, the CSV export, and the same numbers from a server key.

The console shows your site's usage: players, sessions, retention, the funnel, where and on what players play.
Nothing about ads or money; the platform offers none to portals in this release. Every number comes from the
play sessions, players, saves, scores and events of your site. filudi.com's players are not in it.

## What the console shows

For a range of UTC days (400 at most), with the previous range of the same length for comparison:

| Block   | Fields                                                                                                                                                                                                                          |
| ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Per day | players created, accounts linked, active players, `wau` and `mau` (distinct players over the 7 and 30 days ending that day), returning players, sessions, scores, saves, links sent and consumed, events, median session length |
| Totals  | the same over the range, plus mean and median session length, returning players (a session on two distinct days), D1, D7 and D30 retention                                                                                      |
| Cohorts | players by the ISO week of their first play, with the share that played again on day 1, days 2 to 7, days 8 to 30                                                                                                               |
| Funnel  | distinct players who loaded a frame, started a session, wrote a save, sent a score, linked an email                                                                                                                             |
| Games   | every game of the catalog with sessions, players, scores and mean session length, most sessions first                                                                                                                           |
| Splits  | sessions per host, per device (`phone`, `tablet`, `desktop`, `unknown`), players per locale, events by name                                                                                                                     |
| Catalog | games, visible, live, categories, tags                                                                                                                                                                                          |

A retention share is null below 10 players old enough to be counted, so a small cohort shows no percentage
rather than a misleading one. The device comes from the user agent at the start of a session; nothing else of
the user agent is kept.

The game screen of a site shows the same set for one game on your site, with its boards (scores, players, best,
median) and sessions per bundle version. A game's developer sees their game across every site that lists it,
with a row per site; they see your site's name and its sessions, nothing about your players.

## The funnel's first step

"Loaded" counts players whose frame posted `filudi:loaded`. `GameFrame` of hub-core reports it by sending the
reserved event `frame_loaded` to `POST /v1/sdk/events` with the player's token. A front of your own sends it
the same way; a game cannot, since the API refuses that name with a game token.

<CodePanel title="Report a loaded frame">
  <Fragment slot="curl">

    ```sh
    curl -X POST 'https://api.filudi.com/v1/sdk/events' \
      -H "authorization: Bearer $FILUDI_PLAYER_TOKEN" \
      -H 'content-type: application/json' \
      -d '{ "events": [{ "name": "frame_loaded", "ts": 1761825600000, "gameId": "gloop", "host": "filudi" }] }'
    ```

  </Fragment>
  <Fragment slot="javascript">

    ```js
    // In the page, when the frame posts filudi:loaded. hub-core's GameFrame does this itself.
    await fetch('https://api.filudi.com/v1/sdk/events', {
      method: 'POST',
      headers: { authorization: `Bearer ${playerToken}`, 'content-type': 'application/json' },
      body: JSON.stringify({ events: [{ name: 'frame_loaded', ts: Date.now(), gameId: 'gloop', host: 'filudi' }] }),
    });
    ```

  </Fragment>
  <Fragment slot="python">

    ```py
    # A server does not send player events: the player token stays in the browser.
    # Read the numbers instead, with a server key: GET /v1/server/stats/overview.
    ```

  </Fragment>
</CodePanel>

## Filters and export

The site overview takes `from` and `to`. The game screen of a developer also filters by site, host, bundle
version and locale. Every table of the console has a CSV export, written in the browser with the table's
column labels; nothing is sent anywhere.

## From a server key

`GET /v1/server/stats/overview?from=2026-10-01&to=2026-10-30` and
`GET /v1/server/stats/games/{gameId}?from&to` answer the same shapes as the console
([StatsOverview](/docs/api/reference/schemas/#schema-statsoverview),
[GameStats](/docs/api/reference/schemas/#schema-gamestats)), with the `stats` scope. The answer is cached 60
seconds per range in Redis; a 400 says the range is over 400 days or `to` is before `from`.

<CodePanel title="Last 30 days">
  <Fragment slot="curl">

    ```sh
    curl 'https://api.filudi.com/v1/server/stats/overview?from=2026-10-01&to=2026-10-30' \
      -H "authorization: Bearer $FILUDI_SITE_KEY"
    ```

  </Fragment>
  <Fragment slot="javascript">

    ```js
    const response = await fetch(
      'https://api.filudi.com/v1/server/stats/overview?from=2026-10-01&to=2026-10-30',
      { headers: { authorization: `Bearer ${process.env.FILUDI_SITE_KEY}` } },
    );
    const { totals, days, funnel, cohorts } = await response.json();
    ```

  </Fragment>
  <Fragment slot="python">

    ```py
    response = requests.get(
        'https://api.filudi.com/v1/server/stats/overview',
        params={'from': '2026-10-01', 'to': '2026-10-30'},
        headers={'authorization': f"Bearer {os.environ['FILUDI_SITE_KEY']}"},
    )
    totals = response.json()['totals']
    ```

  </Fragment>
</CodePanel>

## Web audience

The console's web audience block (visitors, page views, sources, countries) comes from CausalIT, read with the
site's read key when you set one in the site's settings. Without a key the block says it is not configured and
nothing else changes. The API never sends your front's analytics anywhere.
