Skip to main content
This is unreleased documentation for the next Zaparoo Core release.
For up-to-date documentation, see the latest version (Stable).
Version: Next

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.visibility​

An indexed media item's hidden preference changed. No payload. Refresh browse/search, system counts, and favorites/history hidden indicators; discard existing media.browse, media.browse.index and media.search cursors, all of which stop being valid. Clients should also refresh after reconnect because notifications are not replayed. Visibility is a shared installation-wide preference, not a launch restriction.

media.history.changed​

A platform recorded or updated play history outside the media.started and media.stopped lifecycle - for example, an externally launched game's playtime confirmed from host foreground evidence after it closed. No payload. Refetch media.history, media.history.latest and media.history.top. Notifications are not replayed, so also refetch after reconnect.

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.
scanobjectNoThe current system's folder scan, only included while a scan is running: path is the folder being read and entries the files and folders read so far.

Indexing Progress: Track using currentStep out of totalSteps systems processed. A system's folder scan can run for many minutes on a large library; while it does, scan updates every few seconds.

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. Per-item progress is sent at most every 250 ms; changes of state, system, step, pause or throttle, a system's last item, errors and the final notification are always sent. media.scrape.status returns the latest progress between notifications. 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, across the scraper/library rather than the current scope.
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
}
}
}

Runs​

run.failed​

A ZapScript run ended in failure. It is sent once for every run, whatever started it: a reader scan, the run method, a playlist item, a hook, a launcher control, or a remote operation. That includes a run refused before it started, such as one over the length limit, one blocked by a profile or playtime limit, or an invalid next action. A run call that fails gets the same category and message in its error response too. A run refused because ZapScript execution is disabled is not a failure and sends nothing, and neither does a run cut short by the service shutting down.

When a playlist item fails, the playlist is paused on that item unless another launch was already in progress or the item started its media before failing. Playing the playlist again retries the item, and next, previous or goto move on and play. A playlist command that would start a launch while another launch is still in progress is refused as busy and leaves the playlist where it is.

Response​

KeyTypeRequiredDescription
sourcestringYesWhat started the run: Reader, API, Playlist, Hook, Control, Remote or GMC.
readerIdstringNoID of the reader the token was scanned on.
scriptstringNoThe token's own ZapScript with credentials redacted, as token history stores it. It can hold a media path. Absent when the token had no text, such as a tag known only by its UID, or when it was over the length limit.
commandstringNoName of the command that failed, when one command's failure ended the run.
categorystringYesError category, the same values the run method reports.
messagestringYesShort description; never contains a path or token contents.
playlistIdstringNoID of the playlist the run was an item of.
playlistIndexnumberNoZero-based index of that item in the playlist.

Example​

{
"jsonrpc": "2.0",
"method": "run.failed",
"params": {
"source": "Playlist",
"script": "@Arcade/Metal Slug X (year:1999)",
"command": "launch.title",
"category": "media_not_found",
"message": "media not found",
"playlistId": "deck://lhm6n9t8",
"playlistIndex": 0
}
}

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"
}
}

playtime.extended​

Sent when extra playtime was granted to the session currently being limited, either through the playtime.extend method or by scanning a physical extension card. Warning thresholds are re-armed by a grant, so they fire again against the newly granted time.

Profiles are identified by ID only. The switch ID authorizing a card grant is a bearer credential and is never published.

A repeated request that granted no additional time emits no notification.

Parameters​

KeyTypeRequiredDescription
modestringYes"duration" when time was added, "today" when the session limit was waived.
durationstringNoTime this grant added (Go duration format). Omitted for "today".
expiresstringNoRFC 3339 timestamp when a "today" waiver lapses. Omitted for "duration".
sessionExtensionstringNoThe session's accumulated extension after this grant.
profileIdstringNoRecipient profile. Omitted for the shared profile.
grantedBystringNoProfile that authorized the grant. Omitted when authorized by an admin client rather than a profile credential.

Example​

{
"jsonrpc": "2.0",
"method": "playtime.extended",
"params": {
"mode": "duration",
"duration": "15m0s",
"sessionExtension": "15m0s",
"profileId": "0194e2a1-6c3f-7b21-9d4e-8a5b6c7d8e9f",
"grantedBy": "0194e2a1-9f8e-7c65-b432-1a0f9e8d7c6b"
}
}

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"
}
}

Decks​

decks.changed​

Sent when a deck is created, edited or deleted on the device, refreshed from its source, or when Core links its title items to local files after matching them, so clients reload their deck lists. Deck edits also change media preferences, so open browse cursors should be discarded.

Parameters​

KeyTypeRequiredDescription
deckIdstringYesThe deck's ID.
actionstringYescreated, updated, deleted or refreshed.

Example​

{
"jsonrpc": "2.0",
"method": "decks.changed",
"params": {
"deckId": "0k3v9x2rq7bm",
"action": "updated"
}
}

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
}
}

Updates​

update.state​

Sent while an update is being applied, so a client can show progress instead of a spinner that outlasts a large download.

The stages that happen after the restart — confirming, succeeded and rolledBack — are not sent here, because no client is connected while they run. Read them from update.check's lastResult once the service is back.

Download progress is reported at most a few times a second, and the final byte always produces an event.

Parameters​

KeyTypeRequiredDescription
stagestringYeschecking, downloading, verifying, probing, installing, restarting, or failed.
versionstringNoThe version being installed.
triggerstringNoWho asked for it: manual or auto.
errorstringNoWhat went wrong. Only present on failed.
bytesDownloadednumberNoBytes downloaded so far. Only present during downloading.
bytesTotalnumberNoTotal bytes to download, when the server reported a length.

Examples​

Downloading​
{
"jsonrpc": "2.0",
"method": "update.state",
"params": {
"stage": "downloading",
"version": "2.10.0",
"trigger": "manual",
"bytesDownloaded": 4194304,
"bytesTotal": 12582912
}
}
Failed​
{
"jsonrpc": "2.0",
"method": "update.state",
"params": {
"stage": "failed",
"version": "2.10.0",
"trigger": "auto",
"error": "verifying the download: checksum mismatch"
}
}