API Reference
VIIPER provides a lightweight TCP API for managing and controlling virtual buses/devices.
It's designed to be trivial to drive from any language that can open a TCP socket and send null-byte-terminated payloads.
Client Libraries Available
Client libraries are available that abstract away any protocol details described below.
For most use cases, you should use one of the provided client libraries rather than implementing the raw protocol yourself:
- Go Client: Reference implementation included in the repository
- Generator Documentation: Information about code generation
- C++ Client Library: Header-only C++20 library (requires external JSON parser)
- C# Client Library: Generated .NET library with async/await support
- TypeScript Client Library: Generated Node.js library with EventEmitter streams
- Rust Client Library: Generated Rust library with sync/async support
The documentation below is provided for reference and for implementing clients in languages not supported officially.
Protocol overview
The TCP API is inspired by the ubiquitous HTTP REST style, but is more lightweight.
If you ever worked with HTTP APIs before, you'll feel right at home.
The exception to this are the device-control and feedback streams, which are raw binary streams specific to each device type.
- Transport: TCP with optional encryption (ChaCha20-Poly1305)
- Default listen address:
:3242(configurable via--api.addr) - Authentication: Required for remote connections, optional for localhost (password-based with HMAC validation)
- Encryption: Automatic for authenticated connections (ChaCha20-Poly1305 with unique session keys)
- Request format: a single ASCII/UTF‑8 line terminated by
\0 - Routing: path followed by optional payload separated by whitespace
(e.g.,bus/list\0orbus/create 5\0) - Payload: optional string that can be a JSON object, numeric value, or plain string depending on the endpoint.
The payload may contain newlines (e.g., pretty-printed JSON) as only the null byte terminates the request. - Success response: a single line containing a JSON payload (or an empty line for commands that have no payload), terminated by connection close
- Error response: a single line JSON object following RFC 7807 Problem Details format with a
statusfield (HTTP-style status code) and other error details, terminated by connection close
Testing the API
For quick testing, you can use tools like netcat (Linux/macOS) or PowerShell scripts (Windows) to send requests and read responses.
Connection timing and auto‑cleanup
After you add a device with bus/{id}/add, you must connect to its streaming endpoint within the configured DeviceHandlerConnectTimeout (default: 5s). If no stream connection is established in time, the device is automatically removed. Likewise, when a stream disconnects, a reconnection timer with the same timeout starts; if the client doesn’t reconnect before it expires, the device is removed.
Authentication Required for Remote Connections
VIIPER requires authentication for all non-localhost connections.
- Localhost clients (
127.0.0.1,::1,localhost): Authentication is optional (but supported) by default - Remote clients: Authentication is required and enforced
On first start, VIIPER generates a random password
and saves it to <USER_CONFIG_DIR>/viiper.key.txt.
Windows: %APPDATA%\VIIPER\viiper.key.txt
Linux (user): ~/.config/github.com/Alia5/viiper/viiper.key.txt
Linux (root/systemd): /etc/viiper/viiper.key.txt
Remote clients must provide this password to establish a connection.
See the Configuration documentation for details on password management and the --api.require-localhost-auth option.
Endpoints
null byte excluded
The \0 (null byte) terminator is excluded from all examples below for readability.
All requests must be terminated with a null byte (unless otherwise noted).
-
Bus Management
Create, list, and remove virtual buses
-
Device Management
Add, list, and remove devices on a bus
-
Device Control / Feedback
Real-time input and feedback streams for devices
-
Error Handling
Error response format and common error codes
Bus Management
ping
ping - Simple identity and version check
Request: ping
Response: { "server": "VIIPER", "version": "1.2.3[-dev-abcd]" }
bus/list
bus/list - List all virtual bus IDs
Request: bus/list
Response: { "buses": [1, 2, ...] }
bus/create [busId]
bus/create - Create a new bus
Request: bus/create or bus/create 5
Payload: Optional numeric bus ID (e.g., 5)
If provided, VIIPER attempts to create the bus with that id; otherwise it picks the next free id.
Response: { "busId": <id> }
bus/remove <busId>
bus/remove - Remove a bus and all devices on it
Request: bus/remove 1
Payload: Numeric bus ID (e.g., 1)
Response: { "busId": <id> }
Device Management
bus/{id}/list
bus/{id}/list - List devices on a bus
Request: bus/1/list
Response:
bus/{id}/add <json_payload>
bus/{id}/add - Add a device to a bus
Request: bus/1/add {"type":"xbox360"}
Payload: JSON object with device creation parameters
{
"type": "<deviceType>",
"idVendor": <optional_vid>,
"idProduct": <optional_pid>,
"deviceSpecific": <optional device specific args>
}
Examples:
- {"type":"xbox360"}
- {"type":"keyboard","idVendor":1234,"idProduct":5678}
- {"type":"xbox360", "deviceSpecific": {"subType": 7}}
Response:
{
"busId": 1,
"devId": "1",
"vid": "0x045e",
"pid": "0x028e",
"type": "xbox360",
"deviceSpecific": {
"subType":7
}
}
Connection timeout
After add, the server starts a connect timer (default 5s). You must open a device stream before the timeout expires, otherwise the device is auto-removed.
Auto-attach
If auto-attach is enabled (default), the server automatically attaches the new device to a local USBIP client on the same host (localhost only). Failures are logged but do not affect the API response.
bus/{id}/remove <deviceId>
bus/{id}/remove - Remove a device from a bus
Request: bus/1/remove 1
Payload: Numeric device ID (e.g., 1 for device 1-1 on the bus)
Response: { "busId": <id>, "devId": "<dev>" }
Device Control / Feedback
Device Control and Feedback requires an initial "handshake" request, afterwards the connection is used as a long-lived (device-specific, binary) bidirectional stream.
Establish control/feedback connection/stream
Path: bus/{busId}/{deviceId}
Handshake: Send the path followed by \0 (null byte)
Example: bus/1/1\0
Type: Long-lived TCP connection
Purpose: Device-specific, bidirectional stream.
Timeout behavior
When a stream ends, a reconnect timer is started.
If the client doesn't reconnect in time, the device is removed.
Device control and feedback is device-specific.
Each device type defines it's own packet formats.
In general the client (your code) sends sends binary input state packets to the VIIPER server and possibly receives binary feedback packets (rumble, keyboard leds, etc.) back.
Refer to the individual device documentation for details on packet formats and behavior.
Error Handling
All errors are inspired by HTTP REST APIs and are returned as single-line JSON objects in the style of RFC 7807 Problem Details.
The connection closes immediately after the error response.
If you have ever worked with HTTP APIs, the errors and status codes will feel familiar.
Error Response Format
Fields:
status(number): HTTP-style status code indicating the error typetitle(string): Short, human-readable summary of the problemdetail(string): Explanation specific to this occurrence
Common Error Codes
Error codes are basically HTTP carbon copies:
| Status | Title | Cause | Example |
|---|---|---|---|
| 400 | Bad Request | Invalid request format, missing payload, or invalid JSON | Missing device type in bus/{id}/add, invalid busId format |
| 404 | Not Found | Resource does not exist | Bus ID not found, device ID not found |
| 409 | Conflict | Resource already exists or cannot be modified | Bus ID already exists, auto-attach failure |
| 500 | Internal Server Error | (Unhandled) Server-side error during operation | Failed to marshal response, device add failure, unknown error |
Example sessions
VIIPER includes convenience scripts for quick testing and automation:
# Source the script to load helper functions
. .\scripts\viiper-api.ps1
# Use the Invoke-ViiperApi function (or 'viiper' alias)
viiper "bus/list"
viiper "bus/create"
viiper "bus/1/add {\"type\":\"xbox360\"}" -Port 3242 -Hostname localhost
The script provides Invoke-ViiperApi (alias: viiper) for sending commands and Connect-ViiperDevice for testing persistent device connections.
# List buses
printf "bus/list\0" | nc localhost 3242
# Create a bus
printf "bus/create\0" | nc localhost 3242
# → {"busId":1}
# Create a bus with specific ID
printf "bus/create 5\0" | nc localhost 3242
# → {"busId":5}
# Add a virtual Xbox 360 controller to bus 1
printf 'bus/1/add {"type":"xbox360"}\0' | nc localhost 3242
# → {"busId":1,"devId":"1","vid":"0x045e","pid":"0x028e","type":"xbox360"}
# List devices on bus 1
printf "bus/1/list\0" | nc localhost 3242
Then, open a second TCP connection for device control to bus/1/1 (the API port, not the USBIP port).
First send the "handshake" bus/1/1\0,
then you'll write device-specific input packets and read device-specific feedback packets.
Any language with raw TCP support works.
Go snippet (raw TCP Socket)
package main
import (
"fmt"
"io"
"net"
)
func main() {
conn, _ := net.Dial("tcp", "localhost:3242")
defer conn.Close()
// Send request with null terminator
fmt.Fprint(conn, "bus/create\x00")
// Read entire response until connection closes
resp, _ := io.ReadAll(conn)
fmt.Println(string(resp)) // {"busId":1}\n
}
For a higher-level experience, see the Go client in /apiclient/.
How this relates to USBIP
The VIIPER API controls which virtual devices exist and exposes a device stream for live input/feedback.
Separately, the USBIP server (default :3241) makes these devices attachable from USBIP clients.
Typical flow:
- Create a bus
- Add a device
- Connect the device stream
- Attach a USBIP client to the device
If auto-attach is enabled step 4 is attempted automatically for the local host; you still must perform step 3 to keep the device alive.