Play sessions and the 15-minute rule
The rule
Every free player may play 900 seconds per calendar day (configurable), counted across all games, reset at midnight in the configured timezone (Europe/Berlin by default). Unlimited members and staff accounts have no limit.
How time is measured
- Opening
/play/{slug}loads the play shell. The shell callsPOST /api/v1/play/start, which checks the entitlement, creates aplay_sessionand returns a session token. - The shell loads the game into an iframe and sends
POST /api/v1/play/heartbeatevery 30 seconds while the tab is visible. - Each heartbeat credits the seconds elapsed since the previous one (capped at 90 s, so a sleeping laptop does not burn the pass) to the session and to the player's daily counter.
- When the counter reaches the allowance, the heartbeat returns
limit_reached: true, the session is closed, the shell removes the iframe and shows the Unlimited overlay. The game receivespauseandlimitevents just before. - Leaving the page sends
POST /api/v1/play/end(viasendBeacon). Sessions without heartbeats for five minutes are closed by cron.
Because the shell, not the game, owns the heartbeat, the rule applies to every game automatically, including games that do not use the SDK at all.
What a game sees
ag.entitlement
// { unlimited: false, allowance_seconds: 900, used_seconds: 612,
// remaining_seconds: 288, limit_reached: false, resets_in_seconds: 31234, day: '2026-10-11' }
Updated after every heartbeat (entitlement event). A thoughtful game shows a gentle hint at 60 seconds left and autosaves.
What the player sees
- The ticket on the home page and the dashboard shows today's remaining time.
- The play bar shows a live countdown; it turns red in the last minute.
- When time is up: "Your free 15 minutes for today are used up", with the two membership options (€9.90 monthly, €99 yearly) and a "Come back tomorrow" link.
- Launching a game with no time left shows the same overlay immediately; the entry file is not served (HTTP 403 if requested directly).
Premium-only games
"requires_premium": true in the manifest makes a game launchable by Unlimited members only. Free players see the game page and a "Unlock with Unlimited" button.
Endpoints
| Method | Path | Body | Notes |
|---|---|---|---|
| POST | /api/v1/play/start | { game } | 402 limit_reached / premium_required, 403 otherwise. |
| POST | /api/v1/play/heartbeat | { session } | Returns credited_seconds, session_seconds, entitlement. 410 when the session ended. |
| POST | /api/v1/play/end | { session, reason? } | Idempotent. |
| GET | /api/v1/me/entitlement | Current allowance without touching a session. | |
| GET | /api/v1/me/playtime?days=14 | Daily history and total. |
You normally do not call these yourself; the shell does. They are documented for companion apps and testing.
Configuration
'play' => [
'free_seconds_per_day' => 900,
'heartbeat_interval' => 30,
'max_heartbeat_gap' => 90,
'reset_timezone' => 'Europe/Berlin',
], Last updated October 11, 2026.