Developer API
KasmVNC has an API to make changes or get data via simple HTTPS API calls.
All API calls require owner credentials.
Bottleneck stats
Returns CPU and Network bottleneck statistics. These metrics attempt to identify where performance issues may be occurring. The network and CPU stats each have two numbers. The first number is the current snapshot in time value and the second is averaged over time. Values range from 0 to 10 with 10 representing no constraints. When the frame rate drops, the bottleneck stats should help identify if the bottleneck is server side CPU or the network. The following is an example of what is returned
/api/get_bottleneck_stats
Example Returned JSON
{
"myusername": {
"71.61.88.0_1627638421.160028::websocket": [ 8.2, 9.6, 10.0, 9.4 ],
"71.61.88.0_1627638469.484285::websocket": [ 8.2, 9.6, 2.6, 8.5 ]
}
}
Because KasmVNC supports multiple users and each user can have multiple connections, the returned JSON contains a dict for each user, which contains an entry for each connection. Each connection has an array with the following contents in order:
CPU, CPU Average, Network, Network Average
In the above example, the second connection for shows that the network is not able to keep up with the frames being sent from the server. It also indicates some CPU constraints, meaning that the CPU is mostly keeping up with the changes to the screen but not at 100%.
Frame Stats
The bottleneck stats are a quick way to determine whether the network or the server-side CPU is causing a bottleneck, however, it does not provide enough fidelity to make coding decisions. The frame stats API call returns timings for all major processes with respect to processing a single frame.
/api/get_frame_stats
client - Which client connection to return frame stats for auto - Return frame stats for the first connection for the owner all - Return frame stats for ALL client connections none - Return no client side statistics 71.61.88.0_1627638421.160028::websocket - Example of providing a specific connection
Example: /api/get_frame_stats?client=auto
Example Returned JSON
{
"frame" : {
"resx": 1680,
"resy": 971,
"changed": 60,
"server_time": 45
},
"server_side" : [
{ "process_name": "Analysis", "time": 2 },
{ "process_name": "Screenshot", "time": 3 },
{ "process_name": "Encoding_total", "time": 25, "videoscaling": 17 },
{ "process_name": "TightJPEGEncoder", "time": 3, "count": 1, "area": 786432 },
{ "process_name": "TightWEBPEncoder", "time": 0, "count": 0, "area": 0 }
],
"client_side" : [
{
"client": "71.61.88.0_1627638469.484285::websocket",
"client_time": 1,
"ping": 54,
"processes" : [
{ "process_name": "scanRenderQ", "time": 0 }
]
}
]
}
| Field | Description |
|---|---|
| frame.resx | Width of frame |
| frame.resy | Height of frame |
| frame.changed | Percent of the frame that had changes |
| frame.server_time | Wall clock time total to process the frame |
| server_side.[].process_name | Name of the process |
| server_side.[].time. | CPU time elapsed total for the process |
| server_side.[].count. | Total times the process executed |
| server_side.[].area. | For encoding processes, the total area of rects processed |
| server_side.[].videoscaling | For encoding_total entry, scaling time in video mode. |
| client_side.client_time. | Total time for client to process frame |
| client_side.ping. | Round trip time to client, divide by two |
Since encoding in jpeg and webp is multi-threaded, the time specified in the process entries for JPEG and WEBP encoding is CPU time not wall clock time. The "Encoding_total" process logs the wall clock time for all encoding and also includes videoscaling time which only applies when in "video mode". Video mode scales image down in size, encodes, sends to client, and the client scales it back up. Video mode keeps aspect ratio. Video mode kicks in when screen change reaches a threshold for a period of time defined.
Get Screenshot
Get a screenshot of the current session. You can pass a height and width that do not match the current aspect ratio, it will return the requested width with the height adjusted to keep the aspect ratio.
/api/get_screenshot
width - defaults to actual remote screen width height - defaults to actual remote screen height quality - JPEG quality, defaults to 7 deduplicate - set to true if you want the API call to return nothing if the screenshot has not changed since the last request
Add User
Add another user to the session.
/api/create_user
name - the username password - the requested password write - set to 'true' if the user should have write permissions (control of mouse and keyboard) read - Should the user have read access (optional) owner - Should the owner be able to manage users
example: /api/create_user?name=fred&password=123&write=true
Create User can also be called as a POST with JSON data for bulk operations.
[
{ "user": "username1", "password": "password123", "write": true, "read": true, "owner": false },
{ "user": "username2", "password": "password123", "write": true, "read": true, "owner": false }
]
Update a User
/api/update_user
name - The target username password - The password to update (optional) write - Set to true if the user should have write permission (control of mouse and keyboard) (optional) read - Should the user have read access (optional) owner - Should the owner be able to manage users
example: /api/update_user?name=fred&password=123&write=true
Update User can also be called as a POST with JSON data for bulk operations.
[
{ "user": "username1", "write": true, "read": true, "owner": false },
{ "user": "username2", "write": true, "read": true, "owner": false }
]
Remove User
Remove a user from the KasmVNC session
/api/remove_user
name - username of the user to remove
example: /api/remove_user?name=fred
Remove User can also be called as a POST with JSON data for bulk operations.
[
{ "user": "username1" },
{ "user": "username2" }
]
Send Full Frame
Send a full frame to all users with at least read permission.
/api/send_full_frame
Clear Clipboard
Clears the KasmVNC and X session clipboard contents.
/api/clear_clipboard
Downloads
Provides a detailed listing of files in the user's downloads directory, in JSON.
/api/downloads
path - optional path relative to the user's downloads directory.
example: /api/downloads?path=a_sub_directory
Example json returned.
{
"files":[
{
"filename":"test.txt",
"date_modified":1727968832,
"date_created":1727968832,
"is_dir":false,
"size":0,
"owner":"kasm-user",
"group":"kasm-user",
"perms":"644"
},
{
"filename":"file2.txt",
"date_modified":1727969010,
"date_created":1727969010,
"is_dir":false,
"size":0,
"owner":"kasm-user",
"group":"kasm-user",
"perms":"644"
},
{
"filename":"directory",
"date_modified":1727969024,
"date_created":1727969024,
"is_dir":true,
"size":0,
"owner":"kasm-user",
"group":"kasm-user",
"perms":"755"
},
{
"filename":"important",
"date_modified":1727969056,
"date_created":1727969056,
"is_dir":true,
"size":0,
"owner":"kasm-user",
"group":"kasm-user",
"perms":"755"
}
]
}
Share Sessions Users
Send a list of users connected to a shared session. The connected_since is the date and time in UTC that the user connected to the session.
example: /api/get_sessions
Example json returned.
{
"users": [
{"username":"username1", "connected_since":"2025-07-16 08:07:39"},
{"username":"username2", "connected_since":"2025-07-16 08:07:39"},
{"username":"username3", "connected_since":"2025-07-16 08:07:39"}
]
}
System Stats
Returns real-time server resource usage including CPU, memory, cgroup CPU limits/throttling, and per-disk I/O statistics. When the server is running inside a cgroup v1 or v2 container, the cgroup and cpu_throttling sections reflect the container's own limits and usage, not the host machine's.
/api/system/stats
Example returned JSON
{
"cpu": {
"usage_percent": 12.4,
"has_cgroup_usage": true,
"cgroup_usage_percent": 45.2,
"cgroup_available_percent": 50.0,
"cgroup_effective_cores": 1.0,
"cgroup_usage_usec": 10951883573,
"cgroup_user_usec": 5962863246,
"cgroup_system_usec": 4989020327
},
"memory": {
"total": 2147483648,
"free": 536870912,
"used": 1610612736,
"cached": 402653184
},
"cgroup": {
"has_mem_limit": true,
"mem_limit": 2147483648,
"has_cpu_limit": true,
"cpu_limit_cores": 1.0,
"has_cpu_weight": true,
"cpu_weight": 512,
"has_cpu_affinity": false,
"cpu_affinity": ""
},
"cpu_throttling": {
"available": true,
"has_throttling": true,
"nr_periods": 534616,
"nr_throttled": 479,
"throttled_usec": 387776426,
"throttled_percent": 0.4
},
"io_stats": {
"sda": {
"bytes_read": 104857600,
"bytes_written": 52428800,
"bytes_read_per_sec": 1048576.0,
"bytes_written_per_sec": 524288.0,
"iowait": 0.3
}
}
}
| Field | Description |
|---|---|
| cpu.usage_percent | Host-wide CPU utilisation as a percentage (0–100); not scoped to the container/cgroup |
| cpu.has_cgroup_usage | Whether cgroup CPU usage could be read (false if the server isn't running under a detected cgroup v1/v2 hierarchy) |
| cpu.cgroup_usage_percent | Cgroup CPU usage since the previous poll, as a percentage of cgroup_effective_cores (0–100) |
| cpu.cgroup_available_percent | Effective CPU capacity available to the cgroup, as a percentage of total host cores (0–100) |
| cpu.cgroup_effective_cores | CPU cores effectively available to the cgroup — derived from its CPU quota if one is set, otherwise its CPU affinity/cpuset, otherwise the host core count |
| cpu.cgroup_usage_usec | Cumulative cgroup CPU time used, in microseconds |
| cpu.cgroup_user_usec | Cumulative cgroup CPU time spent in user mode, in microseconds (always 0 under cgroup v1, which doesn't report the user/system split) |
| cpu.cgroup_system_usec | Cumulative cgroup CPU time spent in kernel mode, in microseconds (always 0 under cgroup v1) |
| memory.total | Total RAM available in bytes — the cgroup memory limit if one is set (cgroup.has_mem_limit is true), otherwise total installed host RAM |
| memory.free | Unallocated RAM in bytes, relative to memory.total |
| memory.used | RAM in active use by processes in bytes |
| memory.cached | RAM used for OS page cache in bytes |
| cgroup.has_mem_limit | Whether a cgroup memory limit is configured (e.g. Docker's --memory) |
| cgroup.mem_limit | Configured cgroup memory limit in bytes; 0 if has_mem_limit is false |
| cgroup.has_cpu_limit | Whether a cgroup CPU quota is configured (e.g. Docker's --cpus) |
| cgroup.cpu_limit_cores | CPU quota expressed as a core count; 0 if has_cpu_limit is false |
| cgroup.has_cpu_weight | Whether a CPU weight/shares value was read. This is almost always true — the underlying file has a default value even when no explicit weight was requested |
| cgroup.cpu_weight | Raw CPU weight (cgroup v2, range 1–10000, default 100) or CPU shares (cgroup v1, default 1024) |
| cgroup.has_cpu_affinity | Whether the cgroup is pinned to a strict subset of the host's CPUs (false if it can use all host cores) |
| cgroup.cpu_affinity | Raw cpuset string, e.g. "0-3,7" |
| cpu_throttling.available | Whether cgroup CPU throttling stats could be read (same condition as cpu.has_cgroup_usage) |
| cpu_throttling.has_throttling | Whether the cgroup has ever recorded a quota-enforcement period or been throttled |
| cpu_throttling.nr_periods | Cumulative count of CPU quota enforcement periods elapsed (only advances if a CPU quota is set) |
| cpu_throttling.nr_throttled | Cumulative count of enforcement periods during which the cgroup was throttled |
| cpu_throttling.throttled_usec | Cumulative time the cgroup spent throttled, in microseconds |
| cpu_throttling.throttled_percent | Percentage of wall-clock time spent throttled since the previous poll (0–100) |
| io_stats.[device].bytes_read | Cumulative bytes read from the device since boot |
| io_stats.[device].bytes_written | Cumulative bytes written to the device since boot |
| io_stats.[device].bytes_read_per_sec | Read throughput in bytes/second (measured over the last interval) |
| io_stats.[device].bytes_written_per_sec | Write throughput in bytes/second (measured over the last interval) |
| io_stats.[device].iowait | System-wide CPU time percentage spent waiting for I/O — the same value is repeated under every device, not measured per disk |
postMessage Events
When the KasmVNC client is embedded inside a Kasm VDI iframe it forwards real-time connection and performance statistics to the parent window via window.postMessage. All messages share the same envelope:
{
action: string, // identifies the event type
value: object // event-specific payload
}
The host application listens with:
const kasmFrame = document.getElementById('kasm-frame');
const KASM_ORIGIN = 'https://your-kasm-instance.example.com';
window.addEventListener('message', (event) => {
// Reject messages from unexpected origins or frames.
if (event.origin !== KASM_ORIGIN || event.source !== kasmFrame.contentWindow) {
return;
}
const { action, value } = event.data;
switch (action) {
case 'network_stats': /* server-measured transport stats */ break;
case 'input_latency': /* client-measured end-to-end latency */ break;
case 'system_stats': /* server resource usage */ break;
case 'bottleneck_stats': /* server bottleneck analysis */ break;
}
});
Replace KASM_ORIGIN with the actual origin of the KasmVNC server (scheme + host + port). Messages are only sent when the client is running inside an iframe (window.self !== window.top). In standalone mode the same data is displayed in the in-page performance overlay instead.
network_stats
Server-measured transport-layer statistics. Delivered approximately every five seconds — the client requests bottleneck, network, and system statistics on a 5000 ms interval when performance statistics are enabled.
{
action: 'network_stats',
value: {
jitter: number, // ms — smoothed RTT variation (RFC 6298 RTTVAR)
rtt: number, // ms — stable baseline round-trip time
bandwidth: number // bytes/second — estimated available bandwidth
}
}
input_latency
Client-measured, sampled input-to-canvas latency, from user input to the resulting frame being committed to canvas. This is not true on-screen (motion-to-photon) latency — the browser's compositing and presentation pipeline that follows the canvas draw is not observable from JavaScript and is not included. Fires after each input event (mouse click, key press) once at least 3 samples have been collected. All values are in milliseconds and computed over a rolling window of the last 50 measurements.
{
action: 'input_latency',
value: {
latest: number, // most recent end-to-end latency sample
average: number, // mean of the window
min: number, // minimum of the window
max: number, // maximum of the window
p50: number, // median
p95: number, // 95th percentile — recommended for quality thresholds
p99: number, // 99th percentile — tail latency
networkAvg: number, // mean network + server processing portion
networkP95: number, // p95 of network + server processing portion
renderAvg: number, // mean client decode and canvas render time
renderP95: number // p95 of client decode and canvas render time
}
}
latest is the most recent individual sample's total latency, while networkAvg, renderAvg, and the other *Avg/*P95 fields are separate rolling-window aggregates, not a decomposition of latest. For any single sample, its total latency is approximately its network/server portion plus its client render portion; the averaged fields describe the window as a whole, not that one sample.
p95 is the recommended field for session quality thresholds — it captures sustained degradation without being distorted by isolated spikes.
system_stats
Server resource usage forwarded from the /api/system/stats endpoint. The server refreshes its cached snapshot every second, but the client forwards it on the same 5000 ms poll interval as network_stats. The value field contains the same JSON structure as the HTTP endpoint.
bottleneck_stats
Server-reported CPU and network utilisation from the server's perspective. Sent on demand when the client requests bottleneck statistics. The value field contains stats (the server JSON object), fps (current rendered frames per second), and droppedFps (frames dropped per second).
Unix Relay
KasmVNC provides a method for direct, bidirectional communication between noVNC and containerized applications by utilizing Unix domain datagram sockets.
KasmVNC
To create a Unix relay, KasmVNC should have network.unix_relay.name and network.unix_relay.path configured:
network:
unix_relay:
name: relay_name
path: relay_path
Here, relay_name is the specified name of the relay, acting as an identifier for the connection. The relay_path, on the other hand, represents the path of the Unix domain socket that will be created automatically. This path provides the address at which the socket will be listening, enabling the bidirectional communication essential for the relay to function.
noVNC
In order to connect to a relay on the noVNC side, you can find an example below. This example demonstrates the use of two key functions:
rfb.subscribeUnixRelayused to listen to data received from the Unix domain socket,rfb.sendUnixRelayDataused to send the data back through the relay.
rfb.subscribeUnixRelay("relay_name", (payload) => {
const buffer = new Uint8Array(Array.from(payload)).buffer;
const data = new DataView(buffer);
// process the incoming data
// send a response
rfb.sendUnixRelayData("relay_name", new Uint8Array([...]));
});
Unix Socket Code Examples
In order to connect to a relay on the containerized application side, you can find the example below. This example demonstrates the creation and binding of a socket, and it ensures that the necessary file permissions are in place to provide two-way communication.
import os
import socket
import time
# create and bind socket
client_socket = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
client_socket_path = f"/tmp/relay-client-{str(time.time())}"
client_socket.bind(client_socket_path)
# allow others to read/write to the socket
os.chmod(client_socket_path, 777)
# send data
server_relay_path = "/tmp/relay"
request = bytearray(1024)
sent = client_socket.sendto(request, 0, server_relay_path)
print(f'sent: {sent}B')
# receive response
response = bytearray(1024)
received, server = client_socket.recvfrom_into(response)
print(f'received: {received}B')
# clean up
client_socket.close()
os.unlink(client_socket_path)
Please note that the created socket file in this example is long-lived. It will remain active as long as the program is running. Care should be taken to manage this resource appropriately in your application, such as cleaning it up when it's no longer needed by unlinking it after closing.