Docs generated by AI
This document explains how to report activity events to the system.
Unified API reference:
- Scalar API Reference:
/api-reference - OpenAPI JSON:
/api/openapi.json
- Open the admin panel:
/admin - After signing in, go to the
API Tokenpage - Click create Token
- Save the full Token shown in the dialog. It is displayed only once.
- Method:
POST - URL:
/api/activity - Authentication:
Authorization: Bearer <YOUR_TOKEN> - Content Type:
application/json
Required fields:
generatedHashKey: unique device identifier, obtained after creating a device in adminDevice Managementprocess_name: process name, such asVS Code
Optional fields:
device: display name for the device, such asMacBook Pro; used only for display and not for unique identificationprocess_title: process titlebattery_level: current device battery level, from 0 to 100, if supported by the device or browseris_charging: whether the device is charging, as a boolean. The server writes it tometadata.deviceBatteryCharging. AliasisChargingis also supported. If omitted, the previous charging state is preserved during merge.device_type: device type, one ofdesktop/tablet/mobilepush_mode: push mode, one ofrealtime/activemetadata: extended JSON object
Identifies the media source for this report, such as system_media or music_sdk. Admins can configure Media Source Rules for this value:
block: hidesmetadata.mediafrom public feed output without deleting the activity record.rename: overrides the source label shown by the home page hover card.
Legacy media-source blocklists are still supported and are treated as block rules.
Used to show the currently playing track in Current Status and Recent Activity on the home page. The media block is displayed only when metadata.media.title is present and non-empty.
| Field | Required | Description |
|---|---|---|
title |
Yes, if you want media to display | Track title. The media block is shown only when the trimmed value is non-empty. |
singer / artist |
No | Singer or artist. Displayed together with the title when present. |
album |
No | Album name, displayed in the hover card when present. |
coverDataUrl |
No | Base64 image data URL. When media cover display is enabled, the server stores it and writes back a coverUrl. A plain URL is also accepted by the frontend for compatibility. |
coverUrl |
No | Public cover image URL, usually generated by the server after accepting coverDataUrl. |
appIconDataUrl / iconDataUrl |
No | Base64 image data URL for the playback app icon. When “Report playback app icon” is enabled, the server stores it and writes back appIconUrl. Aliases such as sourceIconDataUrl, playerIconDataUrl, and programIconDataUrl are also accepted. |
appIconUrl / iconUrl |
No | Public playback app icon URL. When present and accepted, the current status music row shows it before the track title. |
status |
No | Playback state: playing, paused, or stopped. Boolean aliases isPlaying and isPaused are also accepted. |
positionMs |
No | Current playback position in milliseconds. Aliases include elapsedMs, currentMs, and progressMs. |
durationMs |
No | Track duration in milliseconds. Aliases include lengthMs and totalMs. |
timestamps |
No | Discord-style playback timestamps, for example { "start": 1778017800000, "end": 1778018040000 }. Values may be Unix milliseconds, Unix seconds, or ISO strings. |
Example:
"metadata": {
"play_source": "cloudmusic.exe",
"media": {
"title": "Song Title",
"singer": "Artist Name",
"album": "Album Name",
"status": "playing",
"positionMs": 83000,
"durationMs": 244000,
"timestamps": {
"start": 1778017800000,
"end": 1778018044000
}
}
}Merge rule for repeated reports from the same process updating the same activity: top-level metadata is shallow-merged. metadata.media is merged as a one-level object with { ...existingMedia, ...incomingMedia }. This means clients may report only part of the media fields, and missing keys are preserved when possible. For example, a client may report title first and report singer later.
Notes:
started_atandended_atdo not need to be uploaded. The server manages timestamps automatically.- When the same
generatedHashKeyreports a new event, the server automatically ends the previous unfinished status for that device. push_mode = realtime: offline state is determined automatically by timeout rules.push_mode = active: the activity stays visible for a long time, is not ended automatically by timeout, and shows the last update time.
For the desktop Go Reporter in reporter/go, including polling/heartbeat behavior, review flow, and background running, see reporter/go/README.md.
- Open the admin panel:
/admin - Go to the
Device Managementtab - Click Add Device to create a device record. You may optionally enter a custom
GeneratedHashKey, matching the key generated in Quick Add Activity. - Copy the corresponding
GeneratedHashKeyfrom the device list - Include this value as
generatedHashKeywhen reporting from the client
GeneratedHashKeycan be left empty. The server will use the reserved devicewaken-web-admin, displayed as Web Admin Quick Add, so no device needs to be created beforehand.- If a key is provided, it must match an enabled device in Device Management. You can also generate a random key in the form, then create a device with the same key in Device Management.
Notes:
GeneratedHashKeyis the unique device identifier and must remain stable.- If a device is revoked, reports using that key are rejected.
- If a device is bound to a specific Token, reports must use the matching Token.
curl -X POST "http://localhost:3000/api/activity" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"generatedHashKey": "YOUR_DEVICE_HASH_KEY",
"device": "MacBook Pro",
"device_type": "desktop",
"process_name": "VS Code",
"process_title": "editing setup-form.tsx",
"battery_level": 82,
"is_charging": true,
"push_mode": "realtime",
"metadata": {
"play_source": "manual-test",
"media": {
"title": "Example Track",
"singer": "Example Artist"
}
}
}'const battery = await navigator.getBattery?.().catch(() => null)
const batteryLevel = battery ? Math.round(battery.level * 100) : undefined
const isCharging = battery ? battery.charging : undefined
await fetch('http://localhost:3000/api/activity', {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
generatedHashKey: deviceHashKey, // required
device: navigator.platform || 'My PC',
device_type: 'desktop',
process_name: 'Chrome',
process_title: 'Dashboard',
battery_level: batteryLevel,
...(typeof isCharging === 'boolean' ? { is_charging: isCharging } : {}),
push_mode: 'active', // active push: long-lived display with last update time
metadata: {
media: {
title: 'Example Track',
singer: 'Example Artist', // optional
},
},
}),
})Success response:
- HTTP
200 {"success": true, "data": ...}
Common errors:
401: Token is invalid or disabled400: required fields are missing, such asgeneratedHashKeyorprocess_name403: device does not exist, is revoked, or does not match the current Token binding500: server error
If reporting fails, check the following in order:
- The Token is the full value, not a truncated value
- The
Bearerprefix is included generatedHashKeyandprocess_nameare present- The Token is enabled in the admin panel
- The device exists and is in the
activestate in adminDevice Management
The admin Settings page provides a Copy Integration Config as Base64 button.
The copied content includes:
- Web configuration, such as name, bio, avatar, history window, and text copy
- Token list, including name, token, and enabled state
- Reporting URL as
reportEndpoint
Other devices can decode the Base64 value into JSON.
Example Node.js decoding:
const decoded = Buffer.from(base64Text, 'base64').toString('utf8')
const config = JSON.parse(decoded)
console.log(config.token.reportEndpoint)
console.log(config.token.items[0]?.token)After signing in as admin, call:
- Method:
GET - URL:
/api/admin/activity/apps-export
Example response shape:
{
"success": true,
"data": {
"version": 1,
"exportedAt": "2026-04-01T00:00:00.000Z",
"groups": {
"pc": [
{ "appName": "code.exe", "titles": ["project-a"], "lastSeenAt": "2026-04-01T00:00:00.000Z" }
],
"mobile": [
{ "appName": "chrome", "titles": ["home"], "lastSeenAt": "2026-04-01T00:00:00.000Z" }
]
}
}
}Notes:
- Grouping rule:
device_typevaluesmobileandtabletare grouped intomobile; everything else is grouped intopc. - Each app is unique by
process_name. Only the latest 3 titles are kept, deduplicated with newest first.
The admin Web Settings page provides the toggle Save app records from reported activity for rule selection/export:
- Disabled: no new history records are added, but existing history data is kept.
- Enabled: reports enter a buffer and are periodically written to the history table. Redis is preferred; without Redis, the system falls back to direct database writes.