Skip to main content
Version: 2.8

Remote Control API

info

This page documents the technical details of the vacs remote control WebSocket API. If you're just looking to use vacs from a browser or different device, see Remote Control instead.

vacs exposes a WebSocket-based remote control API for programmatic interaction by external clients. The built-in browser frontend communicates through the same API - there is no separate internal protocol.

This reference covers the wire format, available commands, subscribable events, and connection lifecycle. It is intended for developers building custom integrations, alternative frontends, or automation tooling against a running vacs instance.

note

While this API can be consumed by any external client, it is heavily geared towards the Tauri/Preact desktop frontend and mirrors the internal interactions used there. Some commands, events, and payload structures may reflect frontend-specific concerns that are not immediately relevant to third-party integrations.

As of now, there are no guarantees of backward compatibility for this API. If you are building a third-party integration, please reach out to the developers to discuss your use case and ensure it can be supported in future releases.

Connectionโ€‹

Endpointโ€‹

ws://<host>:<port>/ws

The default port is 9600. The remote control server must be explicitly enabled in the vacs configuration - see Remote Control - Enabling remote control.

Transportโ€‹

  • Standard WebSocket (RFC 6455)
  • All application messages are JSON text frames - binary frames are not used
  • WebSocket protocol-level Ping/Pong frames are supported - the server responds to any WebSocket Ping frame with a matching Pong frame
  • No sub-protocols are required

Authenticationโ€‹

The WebSocket endpoint does not perform any authentication or authorization. Access control is the responsibility of the network environment.

Security

Do not expose the remote server on untrusted networks. The connection is unencrypted (no TLS).
If you require additional security or must expose the server on a public network, forward the port through a reverse proxy that provides TLS termination and authentication.


Message Formatโ€‹

Every message is a JSON object with a type field that identifies the message kind.

Client โ†’ Server:

TypePurpose
invokeExecute a command on the desktop host
subscribeRegister for a named event stream
unsubscribeDeregister from a named event stream
pingConnection keepalive (expects pong)

Server โ†’ Client:

TypePurpose
responseResult of a preceding invoke
eventForwarded event matching an active subscription
pongKeepalive acknowledgement
Field naming convention

The wire protocol uses snake_case for command names (audio_get_volumes, signaling_start_call, etc.). However, command return values and event payloads use camelCase for their fields (e.g. callId, positionId, clientPageSettings). This is intentional - the payload schema matches the format used by the default Preact frontend.


Client Messagesโ€‹

invokeโ€‹

Dispatches a command to the desktop application. The server responds with a response message carrying the same id.

{
"type": "invoke",
"id": "1",
"cmd": "audio_get_volumes",
"args": {}
}
FieldTypeRequiredDescription
idstringyesClient-assigned opaque identifier, echoed in the corresponding response. Used to correlate requests and responses when multiple commands are in flight concurrently.
cmdstringyesCommand name in snake_case. See Commands.
argsobjectyesJSON object containing command arguments. Must be {} for commands that accept no arguments.

Timeout: Commands that do not resolve within 10 seconds produce an error response with "type": "urn:vacs:error:remote:timeout".

Unknown commands: An unrecognized cmd value fails to parse and produces an error response with "type": "urn:vacs:error:remote:invalid-message", correlated by the id field so the pending request is always settled.

subscribeโ€‹

Registers a subscription for the specified event. Once subscribed, the server forwards all matching event messages to this connection.

{
"type": "subscribe",
"event": "signaling:client-list"
}
FieldTypeRequiredDescription
eventstringyesEvent name to subscribe to. See Events.

No response is sent. Unrecognized event names are dropped without any reply - the subscription simply never fires.

unsubscribeโ€‹

Removes an active event subscription.

{
"type": "unsubscribe",
"event": "signaling:client-list"
}

pingโ€‹

Keepalive heartbeat. The server responds with a pong message.

{
"type": "ping"
}

Server Messagesโ€‹

responseโ€‹

Returned for every invoke message. Exactly one response is produced per invoke.

Success:

{
"type": "response",
"id": "1",
"ok": true,
"data": { "input": 80, "output": 100 }
}

Error:

{
"type": "response",
"id": "1",
"ok": false,
"error": {
"type": "urn:vacs:error:remote:desktop-only",
"title": "Desktop only",
"detail": "This operation is only available on the desktop application",
"isNonCritical": true
}
}
FieldTypeDescription
idstringEchoed from the originating invoke message.
okbooleantrue if the command completed successfully, false otherwise.
dataany or absentPresent when ok is true. Contains the command's return value. null for commands with no return value. Absent when ok is false.
errorProblemDetails or absentPresent when ok is false. RFC 7807-compatible error object. Absent when ok is true.

ProblemDetailsโ€‹

Error responses use RFC 7807 Problem Details. The type URI uniquely identifies the error category.

FieldTypeDescription
typeProblemType (URI)URI identifying the problem type.
titlestringShort human-readable summary.
detailstringLonger human-readable explanation.
isNonCriticalbooleantrue for expected/recoverable errors, false for unexpected failures.
timeoutMsnumber | absentAuto-dismiss timeout in milliseconds. Absent when not applicable.

eventโ€‹

Emitted when the desktop application produces an event matching an active subscription.

{
"type": "event",
"name": "signaling:client-list",
"payload": [{ "id": "1234567", "displayName": "LOVV_CTR", "frequency": "133.800", "positionId": "LOVV_CTR" }]
}
FieldTypeDescription
namestringEvent identifier matching the subscribed event name.
payloadanyEvent-specific payload. Schema depends on the event type.

pongโ€‹

Keepalive acknowledgement in response to a ping. Contains no additional fields.

{
"type": "pong"
}
WebSocket-level Ping/Pong

In addition to the application-level ping/pong messages above, the server also responds to WebSocket protocol-level Ping frames (RFC 6455 ยง5.5.2) with a matching Pong frame. Most WebSocket client libraries handle this transparently. Either mechanism can be used for keepalive.


Commandsโ€‹

Commands are grouped by domain. All command names use snake_case.

Commands that accept arguments expect them as a JSON object in the args field of the invoke message. Commands that take no arguments require args: {}.

note

Some commands are marked as desktop only1 and are unavailable over the remote API.

Applicationโ€‹

CommandArgsReturnsDescription
app_frontend_ready-nullSignal that the frontend has loaded. No-op over remote.
app_open_folder 1--Open a native folder dialog.
app_open_url 1url: string-Open a URL in the desktop host's default browser.
app_check_for_update-UpdateInfoCheck for application updates.
app_quit 1--Quit the application.
app_update 1--Apply a pending update.
app_platform_capabilities-CapabilitiesGet platform capability flags.
app_set_always_on_top 1--Set always-on-top window state.
app_set_fullscreen 1--Toggle fullscreen mode.
app_reset_window_size 1--Reset window to default size.
app_get_call_config-CallConfigGet the current call configuration.
app_set_call_configcallConfig: CallConfignullUpdate call configuration.
app_load_test_profilepath: string?string | nullLoad or reload a test profile. Over remote, path must be provided - null opens a file dialog (desktop only).
app_unload_test_profile-nullUnload the active test profile.
app_get_client_page_settings-ClientPageSettingsGet client page settings.
app_set_selected_client_page_configconfigName: string?nullSet the active client page config.
app_load_extra_client_page_config 1--Load extra client page config from a file.
app_get_version-stringGet the running application version.
app_get_clock_mode-ClockModeGet the clock display mode.
app_set_clock_modeclockMode: ClockModenullSet the clock display mode.
app_get_cpl_mode-CplModeGet the coupling mode.
app_set_cpl_modecplMode: CplModenullSet the coupling mode.
app_set_split_profile_widthprofileId: string, width: number?nullStore the width of the mixed view's phone side for a profile. Omit width or send null to reset it to the default.

Audioโ€‹

CommandArgsReturnsDescription
audio_get_hosts-AudioHostsList available audio backends.
audio_set_hosthostName: stringnullSwitch audio backend.
audio_get_devicesdeviceType: DeviceTypeAudioDevicesList audio devices for the given type.
audio_set_devicedeviceType: DeviceType, deviceName: stringAudioDevicesSet the active audio device. Returns updated device list.
audio_get_volumes-AudioVolumesGet current volume levels.
audio_set_volumevolumeType: VolumeType, volume: numbernullSet a volume level.
audio_get_ring_sounds-RingSoundsGet the custom ring sound files, if any.
audio_pick_ring_sound 1-string | nullOpen a native file dialog and return the chosen WAV path without applying it.
audio_set_ring_soundringType: RingSoundType, path: string?RingSoundsUse the WAV file at path for that ring, or the built-in chime when path is null. Plays it once.
audio_play_ui_click-nullPlay the UI click sound.
audio_start_input_level_meter-nullStart input level monitoring. Subscribe to audio:input-level to receive updates.
audio_stop_input_level_meter-nullStop input level monitoring.
audio_set_radio_prioprio: booleannullSet the radio priority flag.

Authenticationโ€‹

CommandArgsReturnsDescription
auth_open_oauth_url 1--Start the VATSIM OAuth flow. Opens the authorization URL on the desktop host.
auth_check_session-nullCheck the current session validity.
auth_logout-nullLog out the current user.

Keybindsโ€‹

CommandArgsReturnsDescription
keybinds_get_transmit_config-TransmitConfigGet transmit configuration (mic mode, PTT bindings).
keybinds_set_transmit_configtransmitConfig: TransmitConfignullUpdate transmit configuration.
keybinds_get_keybinds_config-KeybindsConfigGet keybind configuration.
keybinds_set_bindinginput: InputBinding?, keybind: KeybindnullBind an input to an action. Pass input: null to clear the binding.
keybinds_get_external_bindingkeybind: Keybindstring | nullGet the external (system-level) binding for a keybind. Only ever a string on Linux with Wayland, where keyboard shortcuts are system-managed; null everywhere else.
keybinds_open_system_shortcuts_settings 1--Open the OS shortcut settings.
keybinds_is_portal_shortcut_boundkeybind: KeybindbooleanWhether a system shortcut is currently bound for the keybind via the Linux global shortcuts portal. false outside a Wayland session; fails on non-Linux platforms.
keybinds_capture_joystick_buttoncaptureId: stringJoystickButton | nullWait for the next joystick button press and return it. Returns null on timeout or cancellation.
keybinds_cancel_joystick_capturecaptureId: stringnullCancel a pending capture. A no-op if a newer capture superseded it.
keybinds_list_joystick_devices-JoystickDeviceEntry[]List connected joystick devices, plus ignored ones that are currently unplugged.
keybinds_set_ignored_joysticksdevices: JoystickDevice[]nullReplace the set of devices excluded from capture. Persisted by device GUID.
warning

The joystick commands require the joystick capability. On a host without it they fail with an application error rather than returning an empty result.

keybinds_capture_joystick_button waits up to 15 seconds for a button press, which is longer than the 10 second invoke timeout. If no button is pressed within 10 seconds, the capture is still running on the host when the timeout response arrives - cancel it explicitly with keybinds_cancel_joystick_capture using the same captureId.

Playbackโ€‹

Recording and replay of radio traffic. Requires the playback capability; commands that reach the recorder return empty results when no recorder is running.

CommandArgsReturnsDescription
playback_get_enabled-booleanWhether playback recording is enabled.
playback_set_enabledenabled: booleannullEnable or disable playback recording. Persisted to the client config.
playback_list-ClipMeta[]List the recorded clips.
playback_deleteid: numberbooleanDelete a clip. Returns whether a clip was removed.
playback_clear-nullDelete all clips.
playback_startid: number, deviceType: PlaybackDeviceType, initialProgress: number?, startPaused: boolean?nullStart playing a clip. initialProgress is a fraction from 0.0 to 1.0.
playback_say_againdeviceType: PlaybackDeviceTypeClipMeta | nullReplay the newest recorded clip from the start, replacing any playing clip. Returns the clip started, or null when nothing has been recorded.
playback_pause-nullPause the playing clip.
playback_continue-nullResume the paused clip.
playback_stop-nullStop playback and release the audio source.
playback_seekmillis: numbernullSeek within the playing clip. Negative values rewind.
playback_exportid: numberstringCopy a clip to the saved directory, exempting it from eviction. Returns the destination path and opens the folder on the desktop host.

Radioโ€‹

Radio integration control. These commands were previously part of the keybinds domain (keybinds_get_radio_config, keybinds_set_radio_config, keybinds_get_radio_state, keybinds_reconnect_radio) and have moved to the radio prefix.

CommandArgsReturnsDescription
radio_get_config-RadioConfigGet radio integration configuration.
radio_set_configradioConfig: RadioConfignullUpdate radio integration configuration. Restarts the integration and recorder.
radio_get_state-RadioStateGet current radio state.
radio_reconnect-nullReconnect the radio integration.
radio_get_stations-RadioStation[]List the frequencies currently tuned in the radio backend.
radio_add_stationcallsign: stringRadioStationAdd a station by callsign.
radio_set_station_statefrequency: number, update: StationStateUpdateRadioStationApply a partial state update to a tuned station.
radio_fast_couple-nullTrigger a fast cross-couple across the tuned stations.
note

All radio_* commands except radio_get_config, radio_set_config and radio_get_state require an active radio integration. Without one, they fail with an application error. radio_set_config is how an integration is enabled in the first place; it only requires the keybind listener capability.

Remoteโ€‹

CommandArgsReturnsDescription
remote_broadcast_store_syncstore: string, state: any, sourceId: stringnullBroadcast a state sync event to all connected remote clients. See store:sync.
remote_request_store_sync-nullAsk every connected instance to re-broadcast its synced state.
remote_get_session_state-SessionStateSnapshotGet a full snapshot of the current session.
remote_get_config-RemoteConfigWithStatusGet the remote server configuration and its current status.
remote_is_enabled-booleanWhether the remote server is enabled in the configuration.

Signalingโ€‹

CommandArgsReturnsDescription
signaling_connectpositionId: stringnullConnect to the signaling server.
signaling_disconnect-nullDisconnect from the signaling server.
signaling_terminate-nullTerminate the signaling session.
signaling_start_calltarget: string, source: string, prio: booleanstringStart a call. Returns the call ID (UUID).
signaling_accept_callcallId: stringnullAccept an incoming call.
signaling_end_callcallId: stringnullEnd an active or pending call.
signaling_get_ignored_clients-string[]Get the ignore list. Returns an array of CIDs.
signaling_add_ignored_clientclientId: stringbooleanAdd a CID to the ignore list. Returns whether the CID was newly added.
signaling_remove_ignored_clientclientId: stringbooleanRemove a CID from the ignore list. Returns whether the CID was present.

Session State Snapshotโ€‹

The remote_get_session_state command returns a complete snapshot of the current application state. This is the recommended mechanism for initializing a newly connected client. See SessionStateSnapshot in the Type Reference for the full schema and a JSON example.


Eventsโ€‹

Subscribe to events to receive real-time updates. Event names use a domain:name format with kebab-case.

Audio Eventsโ€‹

EventPayloadDescription
audio:implicit-radio-priobooleanRadio priority was implicitly changed (e.g. by an incoming priority call).
audio:input-levelnumberInput audio level sample (0.0 to 1.0). Emitted at a regular interval while the input level meter is active.
audio:radio-priobooleanRadio priority state changed.
audio:stop-input-level-meternullThe input level meter was stopped.

Authentication Eventsโ€‹

EventPayloadDescription
auth:authenticatedstringThe user successfully authenticated. Payload is the VATSIM CID.
auth:errornullAn authentication error occurred.
auth:unauthenticatednullThe user was logged out or the session expired.

Playback Eventsโ€‹

EventPayloadDescription
playback:clips-modifiednullThe clip list changed (a recording finished, or clips were deleted). Re-fetch with playback_list.
playback:progressnumberPlayback position of the current clip as a fraction from 0.0 to 1.0. Emitted continuously while a clip plays.

Radio Eventsโ€‹

EventPayloadDescription
radio:stateRadioStateRadio integration state changed.
radio:station-addedRadioStationA frequency was tuned in the radio backend.
radio:station-updatedRadioStationA tuned station's state changed (RX/TX/cross-couple/mute).
radio:station-removednumberA frequency was untuned. Payload is the frequency in Hz.
radio:stations-syncedRadioStation[]The full station list was replaced (replaces previous list).

Signaling Eventsโ€‹

EventPayloadDescription
signaling:accept-incoming-callstringAn incoming call was accepted. Payload is the CallId.
signaling:add-incoming-to-call-listIncomingCallListEntryA new incoming call was added to the pending list.
signaling:ambiguous-positionstring[]The selected position matched multiple entries. Payload is an array of position IDs.
signaling:call-endstringA call ended. Payload is the CallId.
signaling:call-inviteCallInviteA new call invitation was received.
signaling:call-rejectstringA call was rejected by the remote party. Payload is the CallId.
signaling:client-connectedClientInfoA client connected to the signaling server.
signaling:client-disconnectedstringA client disconnected from the signaling server. Payload is the CID.
signaling:client-listClientInfo[]The full client list was updated (replaces previous list).
signaling:client-not-foundstringA client lookup failed. Payload is the CID.
signaling:client-page-configClientPageSettingsThe client page configuration was updated.
signaling:connectedSessionInfoSuccessfully connected to the signaling server. The payload adds splitProfileWidth to the server's session info.
signaling:disconnectednullDisconnected from the signaling server.
signaling:force-call-endstringA call was forcefully terminated (e.g. by the server). Payload is the CallId.
signaling:outgoing-call-acceptedCallAcceptAn outgoing call was accepted by the remote party.
signaling:reconnectingnullThe signaling connection is being re-established.
signaling:station-changesStationChange[]One or more stations changed.
signaling:station-listStationInfo[]The full station list was updated (replaces previous list).
signaling:test-profileobjectA test profile was loaded or unloaded.

WebRTC Eventsโ€‹

EventPayloadDescription
webrtc:call-connectedstringA voice call was established (media flowing). Payload is the CallId.
webrtc:call-degradedstringA voice call is up but no incoming audio is arriving, and vacs cannot repair it. Payload is the CallId.
webrtc:call-disconnectedstringA voice call was disconnected. Payload is the CallId.
webrtc:call-reconnectingstringA voice call lost its incoming audio and is being re-established over a relay. Payload is the CallId.
webrtc:call-errorCallErrorA voice call encountered an error.

webrtc:call-reconnecting and webrtc:call-degraded describe the same underlying condition, a call that stopped receiving audio, and differ only in whether vacs can do something about it. The desktop UI treats webrtc:call-reconnecting exactly like connecting and webrtc:call-degraded as its own state. A reconnect attempt ends in either webrtc:call-connected or webrtc:call-error, so a client that only cares about the final result can ignore both events. Both events were added in vacs 2.6.0; older clients never emit them. See One-way audio for the behavior they report on.

Store Sync Eventsโ€‹

The desktop frontend and every connected remote client mirror selected pieces of UI state to each other. These two events carry that mechanism; a third-party integration can ignore them, or use them to stay in step with the desktop UI.

EventPayloadDescription
store:syncStoreSyncPayloadA store slice was updated by one of the connected instances.
store:sync:requestnullEvery instance should re-broadcast its current synced state.

Other Eventsโ€‹

EventPayloadDescription
errorFrontendErrorA general application error.
remote:statusRemoteStatusThe remote server's listen state or connected client count changed.
update:progressnumberApplication update download progress (0 to 100).

Type Referenceโ€‹

This section documents all complex types used in command return values and event payloads. Field names are serialized in camelCase unless noted otherwise.

Identifiersโ€‹

Several identifier types appear throughout the API. All are serialized as plain JSON strings.

TypeFormatDescription
ClientIdnumeric stringVATSIM CID (e.g. "1234567").
PositionIdstringPosition callsign (e.g. "LOVV_CTR").
StationIdstringStation identifier (e.g. "LOVV_CTR").
CallIdUUID stringUnique call identifier (UUIDv7, e.g. "019cc8de-50f0-7624-a89c-61ba0b5cb784").

ProblemTypeโ€‹

Well-known problem type URIs used in ProblemDetails error responses.

URITitleDescription
urn:vacs:error:remote:desktop-onlyDesktop onlyThe command is only available on the desktop application.
urn:vacs:error:remote:invalid-argumentInvalid argumentOne or more command arguments were invalid or missing.
urn:vacs:error:remote:invalid-messageInvalid messageThe message could not be parsed, or named an unknown command.
urn:vacs:error:remote:timeoutTimeoutThe command did not complete within the time limit.
urn:vacs:error:remote:application(varies)An application-level error originating from the backend.

SessionStateSnapshotโ€‹

Returned by remote_get_session_state. Provides a complete snapshot of the current application state for bootstrapping a newly connected client.

{
"connectionState": "disconnected",
"sessionInfo": null,
"defaultCallSources": [],
"stations": [],
"clients": [],
"clientId": null,
"callConfig": {
"highlightIncomingCallTarget": true,
"enablePriorityCalls": true,
"enableCallStartSound": true,
"enableCallEndSound": true,
"useDefaultCallSources": true
},
"clientPageSettings": {
"selected": null,
"configs": {}
},
"capabilities": {
"alwaysOnTop": true,
"keybindListener": true,
"keybindEmitter": false,
"joystick": true,
"playback": true,
"platform": "LinuxWayland"
},
"incomingCalls": [],
"outgoingCall": null
}
FieldTypeDescription
connectionStateConnectionStateSignaling connection state.
sessionInfoSessionInfo | nullCurrent signaling session metadata.
defaultCallSourcesstring[]Station IDs configured as default call sources for the position.
stationsStationInfo[]Available stations in the current session.
clientsClientInfo[]Other clients visible in the current session.
clientIdstring | nullThe authenticated user's VATSIM CID.
callConfigCallConfigActive call configuration.
clientPageSettingsClientPageSettingsActive client page layout/settings.
capabilitiesCapabilitiesPlatform capabilities of the desktop host.
incomingCallsCallInvite[]Pending incoming call invitations.
outgoingCallCallInvite | nullThe pending outgoing call, if any.

Capabilitiesโ€‹

Platform capability flags returned by app_platform_capabilities and included in the session state snapshot.

{
"alwaysOnTop": true,
"keybindListener": true,
"keybindEmitter": false,
"joystick": true,
"playback": true,
"platform": "LinuxWayland"
}
FieldTypeDescription
alwaysOnTopbooleanWhether the platform supports always-on-top window mode.
keybindListenerbooleanWhether the platform supports global keybind listening.
keybindEmitterbooleanWhether the platform supports emitting key events.
joystickbooleanWhether joystick and gamepad button bindings are supported.
playbackbooleanWhether recording and replay of radio traffic is supported.
platformPlatformThe host platform identifier.

Platformโ€‹

"Unknown" | "Windows" | "MacOs" | "LinuxX11" | "LinuxWayland" | "LinuxUnknown"

UpdateInfoโ€‹

Returned by app_check_for_update.

{
"currentVersion": "2.0.0",
"newVersion": "2.1.0",
"required": false
}
FieldTypeDescription
currentVersionstringCurrently installed application version.
newVersionstring | absentAvailable update version. Absent when no update is available.
requiredbooleanWhether the update is mandatory.

CallConfigโ€‹

Returned by app_get_call_config. Accepted by app_set_call_config.

{
"highlightIncomingCallTarget": true,
"enablePriorityCalls": true,
"enableCallStartSound": true,
"enableCallEndSound": true,
"useDefaultCallSources": true,
"forceRelay": false
}
FieldTypeDescription
highlightIncomingCallTargetbooleanHighlight the caller in the client list on incoming call.
enablePriorityCallsbooleanAllow sending and receiving priority calls.
enableCallStartSoundbooleanPlay a sound when a call connects.
enableCallEndSoundbooleanPlay a sound when a call ends.
useDefaultCallSourcesbooleanPlace calls from the position's default source station instead of asking each time.
forceRelaybooleanAlways route call audio through a relay server instead of trying a direct connection first. Defaults to false.

ClientPageSettingsโ€‹

Returned by app_get_client_page_settings and the signaling:client-page-config event.

{
"selected": "default",
"configs": {
"default": {
"include": [],
"exclude": [],
"priority": [],
"frequencies": "ShowAll",
"grouping": "FirAndIcao"
}
}
}
FieldTypeDescription
selectedstring | nullName of the active client page configuration.
configsobjectMap of configuration name โ†’ ClientPageConfig.

ClientPageConfigโ€‹

FieldTypeDescription
includestring[]Position ID patterns to include in the client list.
excludestring[]Position ID patterns to exclude from the client list.
prioritystring[]Position ID patterns to prioritise in the client list.
frequenciesFrequencyDisplayModeHow to display frequencies.
groupingClientGroupModeHow to group clients.

FrequencyDisplayModeโ€‹

"HideAll" | "ShowAll"

ClientGroupModeโ€‹

"None" | "Fir" | "Icao" | "FirAndIcao"

AudioHostsโ€‹

Returned by audio_get_hosts.

{
"selected": "ALSA",
"all": ["ALSA", "JACK"]
}
FieldTypeDescription
selectedstringCurrently active audio backend.
allstring[]All available audio backends on the host.

AudioDevicesโ€‹

Returned by audio_get_devices and audio_set_device.

{
"preferred": "Headset (USB Audio)",
"picked": "Headset (USB Audio)",
"default": "Built-in Audio",
"all": ["Built-in Audio", "Headset (USB Audio)"]
}
FieldTypeDescription
preferredstringUser-preferred device name (from config).
pickedstringActually selected device (may differ from preferred if unavailable).
defaultstringSystem default device.
allstring[]All available devices of the requested type.

AudioVolumesโ€‹

Returned by audio_get_volumes.

{
"input": 80,
"output": 100,
"click": 50,
"chime": 70
}
FieldTypeDescription
inputnumberMicrophone input volume.
outputnumberAudio output volume.
clicknumberUI click sound volume.
chimenumberNotification chime volume.

VolumeTypeโ€‹

Used as the volumeType argument for audio_set_volume:

"input" | "output" | "click" | "chime"

DeviceTypeโ€‹

Used as the deviceType argument for audio_get_devices and audio_set_device:

"Input" | "Output"

RingSoundsโ€‹

Returned by audio_get_ring_sounds and audio_set_ring_sound. A field is absent when that ring uses the built-in chime.

{
"ring": {"path": "C:\\Users\\me\\Sounds\\ring.wav", "available": true},
"priorityRing": {"path": "C:\\Users\\me\\Sounds\\urgent.wav", "available": false}
}
FieldTypeDescription
ringRingSoundThe custom sound for normal calls, if any.
priorityRingRingSoundThe custom sound for priority calls, if any.

RingSoundโ€‹

FieldTypeDescription
pathstringAbsolute path of the WAV file on the desktop machine.
availablebooleanfalse when the file could not be loaded at startup, in which case the built-in chime plays instead.

audio_set_ring_sound fails, and keeps the previous sound, if the file is not readable as WAV, is shorter than 100 milliseconds, longer than 30 seconds, or silent. The path must be readable by the desktop client, not by the remote browser.

RingSoundTypeโ€‹

Used as the ringType argument for audio_set_ring_sound:

"ring" | "priorityRing"

TransmitConfigโ€‹

Returned by keybinds_get_transmit_config. Accepted by keybinds_set_transmit_config.

{
"callMicMode": "PushToTalk",
"pushToTalk": "Space",
"pushToMute": null,
"radioPushToTalk": {"device": "03000000...", "button": 4, "name": "Saitek Pro Flight Yoke"}
}
FieldTypeDescription
callMicModeCallMicModeHow the microphone is keyed during a call.
pushToTalkInputBinding | nullBinding for push-to-talk.
pushToMuteInputBinding | nullBinding for push-to-mute.
radioPushToTalkInputBinding | nullBinding for radio push-to-talk.

CallMicModeโ€‹

"VoiceActivation" | "PushToTalk" | "PushToMute"

InputBindingโ€‹

A physical input bound to an action. Either a keyboard key code string (a KeyboardEvent.code value, for example "KeyA" or "Space") or a JoystickButton object.

"KeyA"
{"device": "03000000adde0000efbe000000000000", "button": 4, "name": "Saitek Pro Flight Yoke"}

Distinguish the two by JSON type: a string is a keyboard key, an object is a joystick button.

JoystickButtonโ€‹

FieldTypeDescription
devicestringSDL joystick GUID as a hex string. Stable across reconnects and USB ports.
buttonnumberRaw button index in SDL joystick button numbering.
namestring | absentLast-seen device name. Display only, and ignored when comparing bindings.
note

Two physically identical devices share a GUID, so a binding made on one matches the other. Distinct products (yoke, throttle, pedals) always have distinct GUIDs.

JoystickDeviceโ€‹

Accepted by keybinds_set_ignored_joysticks.

FieldTypeDescription
devicestringSDL joystick GUID as a hex string.
namestring | absentLast-seen device name, kept for display while unplugged.

JoystickDeviceEntryโ€‹

Returned by keybinds_list_joystick_devices. A JoystickDevice with its presence and ignore state.

{
"device": "03000000adde0000efbe000000000000",
"name": "Saitek Pro Flight Yoke",
"connected": true,
"ignored": false
}
FieldTypeDescription
connectedbooleanWhether the device is currently plugged in.
ignoredbooleanWhether presses from this device are excluded from capture. Existing bindings keep working.

KeybindsConfigโ€‹

Returned by keybinds_get_keybinds_config.

{
"acceptCall": "KeyA",
"endCall": "KeyE",
"toggleRadioPrio": null,
"sayAgain": "F9"
}
FieldTypeDescription
acceptCallInputBinding | nullBinding for accepting an incoming call.
endCallInputBinding | nullBinding for ending the active call.
toggleRadioPrioInputBinding | nullBinding for toggling radio priority.
sayAgainInputBinding | nullBinding for SAY AGAIN: replays the newest clip, or stops that replay. 2.8.0+.

Keybindโ€‹

The action a binding applies to. Used as the keybind argument for keybinds_set_binding, keybinds_get_external_binding and keybinds_is_portal_shortcut_bound:

"PushToTalk" | "PushToMute" | "RadioPushToTalk" | "AcceptCall" | "EndCall" | "ToggleRadioPrio" | "SayAgain"

RadioConfigโ€‹

Returned by radio_get_config. Accepted by radio_set_config.

{
"integration": "AudioForVatsim",
"audioForVatsim": {
"emit": "F1"
},
"trackAudio": {
"endpoint": "ws://localhost:49080"
}
}
FieldTypeDescription
integrationRadioIntegration | nullActive radio integration backend. null disables the integration.
audioForVatsimobject | nullAudioForVATSIM-specific configuration. Contains emit (key code, string | null).
trackAudioobject | nullTrackAudio-specific configuration. Contains endpoint (WebSocket URL, string | null).

RadioIntegrationโ€‹

"AudioForVatsim" | "TrackAudio"

RadioStateโ€‹

Returned by radio_get_state and emitted with the radio:state event. Serialized as an object with the variant in state and, for RxActive, the receiving frequencies in data.

{"state": "VoiceConnected"}
{"state": "RxActive", "data": [133800000, 121500000]}
FieldTypeDescription
statestringThe state variant, see the table below.
datanumber[] | absentFrequencies in Hz currently being received. Present only for RxActive.
ValueDescription
NotConfiguredNo radio integration is configured.
DisconnectedConfigured but not connected to the radio backend.
ConnectedConnected to the radio backend, which has no VATSIM voice session.
VoiceConnectedThe backend is connected to the VATSIM voice server.
RxIdleMonitoring at least one frequency, nothing being received.
RxActiveActively receiving a transmission.
TxActiveActively transmitting. Takes priority over simultaneous reception.
ErrorThe radio integration encountered a fatal error.

RadioStationโ€‹

A frequency tuned in the radio backend. Returned by radio_get_stations, radio_add_station and radio_set_station_state, and emitted with the radio:station-* events.

{
"callsign": "LOVV_CTR",
"frequency": 133800000,
"rx": true,
"tx": true,
"xc": false,
"xca": false,
"headset": true,
"output_muted": false,
"is_available": true
}
FieldTypeDescription
callsignstring | nullStation callsign, when the backend reports one; null otherwise.
frequencynumberFrequency in Hz.
rxbooleanReceive enabled.
txbooleanTransmit enabled.
xcbooleanCross-coupling computed by the backend. Read-only.
xcabooleanCross-couple across. The user-settable cross-coupling mode.
headsetbooleanRouted to the headset rather than the speaker.
output_mutedbooleanOutput muted for this station.
is_availablebooleanWhether the station is currently available in the backend.
note

RadioStation and StationStateUpdate use snake_case for output_muted and is_available, unlike the rest of the API. These fields come straight from the radio backend's own schema.

StationStateUpdateโ€‹

Accepted as the update argument of radio_set_station_state. Only the fields present are changed. xc is intentionally absent - it is computed by the backend and cannot be set.

FieldTypeDescription
rxbooleanEnable or disable receive.
txbooleanEnable or disable transmit.
xcabooleanEnable or disable cross-couple across.
headsetbooleanRoute to the headset instead of the speaker.
output_mutedbooleanMute or unmute this station's output.

ClipMetaโ€‹

A recorded clip, returned by playback_list.

{
"id": 17,
"path": "/home/user/.local/share/network.vacs.client/playback/17.wav",
"callsigns": ["LOVV_CTR"],
"frequency": 133800000,
"startedAt": {"secs_since_epoch": 1753699200, "nanos_since_epoch": 0},
"endedAt": {"secs_since_epoch": 1753699214, "nanos_since_epoch": 500000000},
"durationMs": 14500
}
FieldTypeDescription
idnumberClip identifier, used by the other playback_* commands.
pathstringAbsolute path of the WAV file on the desktop host.
callsignsstring[]Callsigns heard in the clip.
frequencynumber | nullFrequency in Hz, when the clip came from a single frequency tap.
startedAtobjectRecording start, as {"secs_since_epoch": <number>, "nanos_since_epoch": <number>}.
endedAtobjectRecording end, same shape as startedAt.
durationMsnumberClip length in milliseconds.

PlaybackDeviceTypeโ€‹

Used as the deviceType argument for playback_start:

"Output" | "Speaker"

ClockModeโ€‹

Returned by app_get_clock_mode. Accepted by app_set_clock_mode.

"Realtime" | "Relaxed" | "Day"

CplModeโ€‹

Returned by app_get_cpl_mode. Accepted by app_set_cpl_mode.

"Original" | "Fast"

RemoteConfigWithStatusโ€‹

Returned by remote_get_config.

{
"config": {
"enabled": true,
"listenAddr": "0.0.0.0:9600",
"serveFrontend": true
},
"status": {
"listening": true,
"connectedClients": 1
}
}
FieldTypeDescription
configobjectThe configured remote server settings.
statusRemoteStatusThe remote server's current runtime status.

RemoteStatusโ€‹

Also emitted with the remote:status event.

FieldTypeDescription
listeningbooleanWhether the remote server is accepting connections.
connectedClientsnumberNumber of currently connected remote clients.

StoreSyncPayloadโ€‹

Emitted with the store:sync event, and the argument shape of remote_broadcast_store_sync.

{
"store": "settings",
"state": {"clockMode": "Realtime", "cplMode": "Original"},
"sourceId": "6f1c9a1e-6f3a-4a26-a5c7-1f0b8f9a2c31"
}
FieldTypeDescription
storestringWhich store slice changed: stations, call, callList, settings, radio or playback.
stateanyThe slice's new value. Shape depends on store.
sourceIdstringOpaque instance ID of the broadcaster. Ignore events carrying your own ID to avoid echo loops.
warning

The state shapes mirror the desktop frontend's internal stores and change without notice, even between patch releases. Treat them as opaque unless you are building a full replacement frontend.

ConnectionStateโ€‹

Used in the session state snapshot to indicate signaling connection state.

"disconnected" | "connecting" | "connected" | "test"

ClientInfoโ€‹

Represents a connected client visible in the session.

{
"id": "1234567",
"displayName": "LOVV_CTR",
"frequency": "133.800",
"positionId": "LOVV_CTR"
}
FieldTypeDescription
idstringVATSIM CID.
displayNamestringDisplay name (typically the callsign).
frequencystringActive frequency.
positionIdstring | absentPosition ID, if available. Absent when not applicable.

SessionInfoโ€‹

Emitted with the signaling:connected event and included in the session state snapshot.

{
"client": {
"id": "1234567",
"displayName": "LOVV_CTR",
"frequency": "133.800",
"positionId": "LOVV_CTR"
},
"profile": {
"type": "changed",
"activeProfile": {
"type": "specific",
"profile": {
"id": "LOVV",
"view": "split",
"tabbed": []
}
}
},
"splitProfileWidth": 352
}
FieldTypeDescription
clientClientInfoThe authenticated user's client entry.
profileobjectProfile state. { "type": "unchanged" } when the profile was not modified, or { "type": "changed", "activeProfile": ... } when it was. activeProfile is { "type": "specific", "profile": ... } with a Profile, or { "type": "custom" } or { "type": "none" }.
splitProfileWidthnumber | absentWidth in pixels stored for the phone side of the profile's mixed view, set through app_set_split_profile_width. Present only when a specific profile is active and a width has been stored for it.

Profileโ€‹

The layout the FIR's dataset defines for the session, carried inside SessionInfo and emitted by the signaling:test-profile event.

{
"id": "LOVV",
"view": "split",
"tabbed": [{ "label": ["S"], "page": { "rows": 4, "keys": [] } }]
}
FieldTypeDescription
idstringUnique profile identifier, also the key used by app_set_split_profile_width.
viewstringHow the client arranges the radio and phone pages: "page" (default), "split" or "cycle". Only tabbed profiles use a value other than "page".
tabbedarrayPresent on tabbed profiles. One entry per tab, with a label of one to three lines and a page of direct access keys.
geoobjectPresent on geo profiles instead of tabbed. Container-based layout tree.

The full page and key structure is defined by the dataset, not by this API.

StationInfoโ€‹

Represents a station in the current session.

{
"id": "LOVV_CTR",
"own": true
}
FieldTypeDescription
idstringStation identifier.
ownbooleanWhether this station belongs to the authenticated user's position.

StationChangeโ€‹

Contained in the signaling:station-changes event payload. Each entry is an externally-tagged enum with one of three variants:

Online:

{ "Online": { "stationId": "LOVV_CTR", "positionId": "LOVV_CTR" } }

Handoff:

{ "Handoff": { "stationId": "LOVV_CTR", "fromPositionId": "LOVV_N1", "toPositionId": "LOVV_N2" } }

Offline:

{ "Offline": { "stationId": "LOVV_CTR" } }

CallInviteโ€‹

Represents an incoming or outgoing call invitation.

{
"callId": "01916f6a-7b3c-7d4e-8f1a-2b3c4d5e6f70",
"source": {
"clientId": "7654321",
"positionId": "LOWW_APP",
"stationId": "LOWW_APP"
},
"target": { "Client": "1234567" },
"prio": false
}
FieldTypeDescription
callIdstringUnique call identifier (UUID).
sourceCallSourceThe originator of the call.
targetCallTargetThe intended recipient of the call.
priobooleanWhether this is a priority call.

CallSourceโ€‹

{
"clientId": "7654321",
"positionId": "LOWW_APP",
"stationId": "LOWW_APP"
}
FieldTypeDescription
clientIdstringVATSIM CID of the caller.
positionIdstring | absentPosition ID of the caller. Absent when unknown.
stationIdstring | absentStation ID of the caller. Absent when unknown.

CallTargetโ€‹

An externally-tagged enum identifying the call recipient. Exactly one variant is present:

{ "Client": "1234567" }
{ "Position": "LOWW_APP" }
{ "Station": "LOWW_APP" }

CallAcceptโ€‹

Emitted with the signaling:outgoing-call-accepted event.

{
"callId": "01916f6a-7b3c-7d4e-8f1a-2b3c4d5e6f70",
"acceptingClientId": "1234567"
}
FieldTypeDescription
callIdstringThe accepted call's identifier.
acceptingClientIdstringVATSIM CID of the client that accepted the call.

CallErrorโ€‹

Emitted with the webrtc:call-error event.

{
"callId": "01916f6a-7b3c-7d4e-8f1a-2b3c4d5e6f70",
"reason": "Local connection failure"
}
FieldTypeDescription
callIdstringThe call that experienced the error.
reasonstringHuman-readable error description.

FrontendErrorโ€‹

Emitted with the error event.

{
"title": "Connection failed",
"detail": "Unable to reach the signaling server",
"isNonCritical": true
}
FieldTypeDescription
titlestringError category identifier.
detailstringHuman-readable error description.
isNonCriticalbooleantrue for expected/recoverable errors, false for unexpected failures.
timeoutMsnumber | absentAuto-dismiss timeout in milliseconds. Absent when not applicable.

IncomingCallListEntryโ€‹

Emitted with the signaling:add-incoming-to-call-list event.

{
"callId": "01916f6a-7b3c-7d4e-8f1a-2b3c4d5e6f70",
"source": {
"clientId": "7654321",
"positionId": "LOWW_APP"
}
}
FieldTypeDescription
callIdstringThe incoming call's identifier.
sourceCallSourceThe originator of the call.

Example: Client Session Lifecycleโ€‹

The following sequence illustrates a typical client lifecycle, from connection establishment through to placing a call.

1. Subscribe to eventsโ€‹

Immediately after the WebSocket connection is established, register subscriptions for the required event streams:

โ†’ { "type": "subscribe", "event": "auth:authenticated" }
โ†’ { "type": "subscribe", "event": "auth:unauthenticated" }
โ†’ { "type": "subscribe", "event": "signaling:connected" }
โ†’ { "type": "subscribe", "event": "signaling:disconnected" }
โ†’ { "type": "subscribe", "event": "signaling:client-list" }
โ†’ { "type": "subscribe", "event": "signaling:station-list" }
โ†’ { "type": "subscribe", "event": "signaling:call-invite" }
โ†’ { "type": "subscribe", "event": "signaling:call-end" }
โ†’ { "type": "subscribe", "event": "webrtc:call-connected" }
โ†’ { "type": "subscribe", "event": "webrtc:call-disconnected" }
โ†’ { "type": "subscribe", "event": "store:sync" }
โ†’ { "type": "subscribe", "event": "error" }

2. Bootstrap stateโ€‹

Signal client readiness and retrieve the current application state:

โ†’ { "type": "invoke", "id": "1", "cmd": "app_frontend_ready", "args": {} }
โ† { "type": "response", "id": "1", "ok": true, "data": null }

โ†’ { "type": "invoke", "id": "2", "cmd": "remote_get_session_state", "args": {} }
โ† {
"type": "response",
"id": "2",
"ok": true,
"data": {
"connectionState": "disconnected",
"sessionInfo": null,
"defaultCallSources": [],
"stations": [],
"clients": [],
"clientId": null,
"callConfig": { ... },
"clientPageSettings": { ... },
"capabilities": { ... },
"incomingCalls": [],
"outgoingCall": null
}
}

See SessionStateSnapshot for the full schema.

3. Authenticateโ€‹

Check whether a valid session already exists:

โ†’ { "type": "invoke", "id": "3", "cmd": "auth_check_session", "args": {} }
โ† { "type": "response", "id": "3", "ok": true, "data": { ... } }

If no auth:authenticated event follows, the user must authenticate on the desktop host first (auth_open_oauth_url is desktop-only). Once the OAuth flow completes on the desktop, the server emits:

โ† { "type": "event", "name": "auth:authenticated", "payload": "1234567" }

4. Connect and callโ€‹

โ†’ { "type": "invoke", "id": "4", "cmd": "signaling_connect", "args": { "positionId": "LOVV_CTR" } }
โ† { "type": "response", "id": "4", "ok": true, "data": null }

โ† { "type": "event", "name": "signaling:connected", "payload": { ... } }
โ† { "type": "event", "name": "signaling:station-list", "payload": [ ... ] }
โ† { "type": "event", "name": "signaling:client-list", "payload": [ ... ] }

Initiate a call to another client:

โ†’ { "type": "invoke", "id": "5", "cmd": "signaling_start_call", "args": { "target": "1234569", "source": "LOVV_N1", "prio": false } }
โ† { "type": "response", "id": "5", "ok": true, "data": "019cc8de-50f0-7624-a89c-61ba0b5cb784" }
โ† { "type": "event", "name": "webrtc:call-connected", "payload": "019cc8de-50f0-7624-a89c-61ba0b5cb784" }

5. Keepaliveโ€‹

Maintain the connection with periodic keepalive pings. Either application-level or WebSocket protocol-level pings may be used:

โ†’ { "type": "ping" }
โ† { "type": "pong" }

Alternatively, send a WebSocket Ping frame - the server replies with a Pong frame carrying the same payload. Most WebSocket libraries send these automatically.


Implementation Notesโ€‹

  • Message ordering: The server imposes no ordering constraints on subscribe and invoke messages. Clients may interleave them freely.
  • Concurrency: Multiple invoke requests may be in flight simultaneously. Clients must use distinct id values to correlate responses.
  • Event buffering: The server maintains an internal per-connection event buffer of 256 messages. If a client cannot consume events at the rate they are produced, older events are dropped and a warning is logged server-side.
  • Desktop-only commands: Commands marked as desktop-only are unconditionally rejected over the remote API. Clients should inspect remote_get_session_state โ†’ capabilities to determine platform support before invoking platform-dependent commands.
  • Field naming: Command names use snake_case (audio_get_volumes, signaling_start_call), while command return values and event payloads use camelCase (callId, positionId). This is intentional - the payload schema matches the format used by the default Preact frontend.
  • Static assets: The HTTP server that hosts the WebSocket endpoint also serves the vacs SPA at the root path (/). Unresolved paths fall back to index.html to support client-side routing.

Footnotesโ€‹

  1. Desktop only - these commands are unavailable over the remote API. Unconditionally rejected with a ProblemDetails error response with "type": "urn:vacs:error:remote:desktop-only". โ†ฉ โ†ฉ2 โ†ฉ3 โ†ฉ4 โ†ฉ5 โ†ฉ6 โ†ฉ7 โ†ฉ8 โ†ฉ9 โ†ฉ10 โ†ฉ11 โ†ฉ12