Skip to main content
Version: Stable

Notifications

Notifications are sent from the server to connected clients to inform them of events.

Readers

readers.added

A new reader was connected to the server.

Response

KeyTypeRequiredDescription
connectedbooleanYesWhether the reader is connected.
driverstringYesDriver type for the reader.
pathstringYesSystem path or identifier of reader.

readers.removed

A connected reader was disconnected from the server.

Response

KeyTypeRequiredDescription
connectedbooleanYesWhether the reader is connected.
driverstringYesDriver type for the reader.
pathstringYesSystem path or identifier of reader.

Tokens

tokens.added

A token was detected by a connected reader.

Response

KeyTypeRequiredDescription
typestringYesType of token (e.g., "nfc", "barcode").
uidstringYesUnique identifier of the token.
textstringNoText data associated with the token.
datastringNoRaw binary data of the token (base64 encoded).
scanTimestringYesISO 8601 timestamp when token was scanned.
readerIdstringNoID of the reader that scanned the token.

tokens.staged

A token was staged by the launch guard and is waiting for confirmation. Sent when launch guard is enabled and media is currently playing.

Response

KeyTypeRequiredDescription
typestringYesType of token (e.g., "nfc", "barcode").
uidstringYesUnique identifier of the token.
textstringNoText data associated with the token.
datastringNoRaw binary data of the token (base64 encoded).
scanTimestringYesISO 8601 timestamp when token was scanned.

tokens.staged.ready

A staged token's delay period has expired and is now ready for confirmation. Sent when launch guard delay is configured and the mandatory waiting period completes.

Response

KeyTypeRequiredDescription
typestringYesType of token (e.g., "nfc", "barcode").
uidstringYesUnique identifier of the token.
textstringNoText data associated with the token.
datastringNoRaw binary data of the token (base64 encoded).
scanTimestringYesISO 8601 timestamp when token was scanned.

tokens.removed

A token was removed from a connected reader.

Response

Returns null.

UI

ui.changed

Authoritative global UI state changed. Sent when event opens, updates, is replaced, or resolves. Clients should replace local event snapshot with events from newest revision rather than applying incremental patches.

Host platform and all connected clients may render same event in parallel. First valid response wins. A terminal update removes event from events and describes it in resolved, instructing every renderer to close.

ui.changed is latest-wins coalescible. Clients that reconnect or suspect missed notifications should query ui.

Parameters

KeyTypeRequiredDescription
revisionnumberYesMonotonic revision of process-wide UI state shared across clients. Ignore older values.
eventsobject[]YesComplete active event snapshot. Initial implementation contains zero or one event.
resolvedobject[]YesTerminal resolutions associated with this transition.

Event and resolution fields are defined under ui and ui.respond.

Picker opened

{
"jsonrpc": "2.0",
"method": "ui.changed",
"params": {
"revision": 12,
"events": [
{
"id": "68bb6f25-dbd4-46a0-a3ac-e4fb54928ac2",
"kind": "picker",
"title": "Favorites",
"choices": [
{"id": "e88560a7-76bb-4ab4-a914-da4be745778d", "label": "Game One"},
{"id": "ce99379a-b803-4489-9da4-e752e96471e9", "label": "Game Two"}
],
"selectedChoiceId": "e88560a7-76bb-4ab4-a914-da4be745778d",
"dismissible": true,
"createdAt": "2026-07-16T12:00:00Z",
"expiresAt": "2026-07-16T12:00:30Z"
}
],
"resolved": []
}
}

Picker resolved

{
"jsonrpc": "2.0",
"method": "ui.changed",
"params": {
"revision": 13,
"events": [],
"resolved": [
{
"id": "68bb6f25-dbd4-46a0-a3ac-e4fb54928ac2",
"outcome": "selected",
"choiceId": "ce99379a-b803-4489-9da4-e752e96471e9"
}
]
}
}

Launch guard continues emitting tokens.staged and tokens.staged.ready for compatibility while also opening a global confirm UI event for API clients. Core does not present this specific confirmation through host platform renderer, preserving existing sound and card re-tap behavior during active media.

Media

media.started

New media was started on server.

Response

KeyTypeRequiredDescription
systemIdstringYesInternal ID of system associated with media.
systemNamestringYesDisplay name of system.
mediaPathstringYesPath to media file on server.
mediaNamestringYesCleaned display title of media.

Example

{
"systemId": "SNES",
"systemName": "Super Nintendo Entertainment System",
"mediaPath": "/roms/snes/Super_Mario_World_(USA)_[!].sfc",
"mediaName": "Super Mario World"
}

media.stopped

Media has stopped on server.

Response

KeyTypeRequiredDescription
systemIdstringYesID of the system.
systemNamestringYesDisplay name of the system.
mediaNamestringYesDisplay name of the media.
mediaPathstringYesPath to media file on server.
launcherIdstringYesID of the launcher.
elapsednumberYesDuration of the media session in seconds.

Example

{
"jsonrpc": "2.0",
"method": "media.stopped",
"params": {
"systemId": "SNES",
"systemName": "Super Nintendo Entertainment System",
"mediaName": "Super Mario World",
"mediaPath": "/roms/snes/Super Mario World (USA).sfc",
"launcherId": "SNES",
"elapsed": 2730
}
}

media.indexing

Sent during media database generation to indicate indexing progress and completion status, as well as database optimization progress.

Priority: Indexing takes priority over optimization in notifications. If both are running, only indexing status will be shown. Optimization status is shown when no indexing is in progress.

Parameters

KeyTypeRequiredDescription
existsbooleanYesTrue if media database exists.
indexingbooleanYesTrue if indexing is currently in progress.
optimizingbooleanYesTrue if database optimization is currently in progress.
totalStepsnumberNoTotal number of systems to process during indexing.
currentStepnumberNoCurrent system being processed during indexing (1-based).
currentStepDisplaystringNoDisplay name of current system being indexed, or optimization step name (e.g., "vacuum").
totalFilesnumberNoTotal number of media files discovered during indexing.
totalMedianumberNoTotal number of media entries in the database. Only included when database exists and is ready.

Indexing Progress: Track using currentStep out of totalSteps systems processed.

Optimization Progress: When optimizing is true and indexing is false, currentStepDisplay shows the optimization operation name (e.g., "vacuum", "analyze").

Examples

Indexing in progress
{
"jsonrpc": "2.0",
"method": "media.indexing",
"params": {
"exists": true,
"indexing": true,
"optimizing": false,
"totalSteps": 50,
"currentStep": 25,
"currentStepDisplay": "SNES",
"totalFiles": 1523
}
}
Optimization in progress
{
"jsonrpc": "2.0",
"method": "media.indexing",
"params": {
"exists": true,
"indexing": false,
"optimizing": true,
"currentStepDisplay": "vacuum",
"totalMedia": 5432
}
}
Database ready
{
"jsonrpc": "2.0",
"method": "media.indexing",
"params": {
"exists": true,
"indexing": false,
"optimizing": false,
"totalMedia": 5432
}
}

media.scraping

Sent while a metadata scraper run is active and when it completes.

The first notification for a scraper run identifies the scraper and sets scraping to true. Progress notifications include the current system, per-system counters, whole-run system-step progress, pause state, and completion state. A final notification has scraping set to false and done set to true. Existing flat counter fields remain for compatibility; new UIs should prefer currentSystem for per-system progress and totalSteps/currentStep/currentStepDisplay for whole-run progress.

Parameters

KeyTypeRequiredDescription
scraperIdstringNoScraper ID, for example gamelist.xml.
systemIdstringNoSystem currently being scraped.
processednumberYesNumber of source records processed so far.
totalnumberYesTotal source records for the current system, or 0 before known.
matchednumberYesNumber of records matched to existing media rows.
skippednumberYesNumber of records skipped because they were unmatched, already scraped, or failed per-record processing.
totalScrapednumberYesNumber of media records already marked scraped.
scrapingbooleanYesTrue while scraping is active.
donebooleanYesTrue on the terminal update for the scraper run.
pausedbooleanYesTrue when the active scrape is paused.
statestringNoExplicit lifecycle state: idle, running, paused, completed, cancelled, or failed.
errorstringNoFatal scrape error on failed terminal updates.
totalStepsnumberNoTotal systems in the scrape run, when known.
currentStepnumberNo1-based current system step, when known.
currentStepDisplaystringNoDisplay name for the current system step, falling back to system ID.
currentSystemobjectNoPer-system progress object with systemId, systemName, processed, total, matched, and skipped.

Examples

Scraping in progress
{
"jsonrpc": "2.0",
"method": "media.scraping",
"params": {
"scraperId": "gamelist.xml",
"systemId": "SNES",
"processed": 42,
"total": 100,
"matched": 38,
"skipped": 4,
"totalScraped": 1200,
"scraping": true,
"done": false,
"paused": false,
"state": "running",
"totalSteps": 12,
"currentStep": 3,
"currentStepDisplay": "Super Nintendo Entertainment System",
"currentSystem": {
"systemId": "SNES",
"systemName": "Super Nintendo Entertainment System",
"processed": 42,
"total": 100,
"matched": 38,
"skipped": 4
}
}
}
Scraping complete
{
"jsonrpc": "2.0",
"method": "media.scraping",
"params": {
"scraperId": "gamelist.xml",
"systemId": "SNES",
"processed": 100,
"total": 100,
"matched": 92,
"skipped": 8,
"totalScraped": 1250,
"scraping": false,
"done": true,
"paused": false,
"state": "completed",
"totalSteps": 12,
"currentStep": 12,
"currentStepDisplay": "Super Nintendo Entertainment System",
"currentSystem": {
"systemId": "SNES",
"systemName": "Super Nintendo Entertainment System",
"processed": 100,
"total": 100,
"matched": 92,
"skipped": 8
}
}
}

Playtime

playtime.limit.reached

Sent when a playtime limit (session or daily) has been reached and enforced by the system. The currently playing game will be stopped when this notification is sent.

Parameters

KeyTypeRequiredDescription
reasonstringYesThe type of limit that was reached: "session" or "daily".

Examples

Session limit reached
{
"jsonrpc": "2.0",
"method": "playtime.limit.reached",
"params": {
"reason": "session"
}
}
Daily limit reached
{
"jsonrpc": "2.0",
"method": "playtime.limit.reached",
"params": {
"reason": "daily"
}
}

playtime.limit.warning

Sent at configured time intervals to warn that a playtime limit is approaching. These notifications are sent based on the warnings configuration (e.g., at 5 minutes, 2 minutes, and 1 minute before limit).

The warning applies to whichever limit will be reached first (session or daily).

Parameters

KeyTypeRequiredDescription
intervalstringYesThe configured warning interval that triggered this notification (Go duration format).
remainingstringYesThe actual time remaining before the limit is reached (Go duration format).

Note: remaining may differ slightly from interval due to timing precision.

Example

{
"jsonrpc": "2.0",
"method": "playtime.limit.warning",
"params": {
"interval": "5m",
"remaining": "4m58s"
}
}

Inbox

inbox.added

Sent when a new inbox message is added to the server.

Parameters

KeyTypeRequiredDescription
idnumberYesUnique identifier of the message.
titlestringYesTitle of the message.
bodystringNoBody text of the message.
severitynumberYesSeverity level (0=info, 1=warning, 2=error).
categorystringNoCategory of the message.
profileIdnumberNoAssociated profile ID, if applicable.
createdAtstringYesTimestamp when message was created in RFC3339 format.

Example

{
"jsonrpc": "2.0",
"method": "inbox.added",
"params": {
"id": 1,
"title": "Update Available",
"body": "A new version of Zaparoo is available.",
"severity": 0,
"category": "update",
"createdAt": "2024-09-24T17:49:42.938167429+08:00"
}
}

Profiles

profiles.active

Sent when the device's active profile changes, including deactivation.

Parameters

KeyTypeRequiredDescription
profileobject | nullYesThe new active profile, or null when the device deactivated.

The profile object contains profileId, name, hasPin and any playtime limit overrides (limitsEnabled, dailyLimit, sessionLimit).

Example

{
"jsonrpc": "2.0",
"method": "profiles.active",
"params": {
"profile": {
"profileId": "1ad28b9a-7aef-11ef-9817-020304050607",
"name": "Kid A",
"hasPin": true,
"limitsEnabled": true,
"dailyLimit": "2h"
}
}
}

profiles.data

Sent when a profile data swap (save files, save states) changes state on platforms that support data swapping. The profile switch itself always succeeds independently of data swapping; this notification reports the data side.

Parameters

KeyTypeRequiredDescription
profileIdstringYesProfile whose data the swap targets. Empty for the shared profile.
statusstringYesOne of applied, deferred (media is running; applies when it stops), failed, or unavailable (the storage setup does not permit swapping, e.g. a read-only network share).
reasonstringNoHuman-readable explanation for failed/unavailable.

Example

{
"jsonrpc": "2.0",
"method": "profiles.data",
"params": {
"profileId": "1ad28b9a-7aef-11ef-9817-020304050607",
"status": "deferred"
}
}

Auth

auth.link.status

Sent on every state transition of a device link flow started with settings.auth.link. Notification payloads always omit the user code and verification URLs; clients that need them read the settings.auth.link result or poll settings.auth.link.status.

Parameters

KeyTypeRequiredDescription
statusstringYesOne of pending, approved, failed, or cancelled.
expiresAtstringNoRFC 3339 time when the link request expires.
errorstringNoHuman-readable reason when status is failed.

Example

{
"jsonrpc": "2.0",
"method": "auth.link.status",
"params": {
"status": "approved"
}
}

Backup

backup.state

Sent while a backup operation (local create, cloud upload, or restore) is running, whenever its pause/throttle state changes because a game started or stopped, and once with finished true when the operation ends. Backup work follows the same policy as media indexing: most games throttle it, storage-sensitive CD-based cores pause it entirely, and it resumes when the game stops. A notification with paused, throttled, and finished all false means the operation returned to full speed.

The finished notification is terminal for that operation, whatever its outcome — use it to clear any paused/slowed indicator, and read settings.backup.status for the result.

Parameters

KeyTypeRequiredDescription
operationstringNoThe active operation kind, matching activeOperation from settings.backup.status.
pausedbooleanYesTrue if the operation is fully paused until the running game stops.
throttledbooleanYesTrue if the operation is running slowed to stay out of the running game's way.
finishedbooleanNoTrue when the operation has ended; no further backup.state events follow for it.

Examples

Upload paused by a running game
{
"jsonrpc": "2.0",
"method": "backup.state",
"params": {
"operation": "remote-upload",
"paused": true,
"throttled": false
}
}
Operation finished
{
"jsonrpc": "2.0",
"method": "backup.state",
"params": {
"operation": "remote-upload",
"paused": false,
"throttled": false,
"finished": true
}
}