WaveKit exposes a REST API and WebSocket endpoint for control and real-time monitoring.
- REST API:
http://localhost:9000 - WebSocket:
ws://localhost:9000/ws - Audio Stream:
tcp://localhost:8080 - Live Audio Stream:
http://localhost:8081/stream
Quick liveness check.
curl http://localhost:9000/healthResponse (200 OK):
{
"status": "ok",
"uptime": 3600
}Response (503 Service Unavailable):
{
"status": "unhealthy",
"error": "No sources connected"
}Readiness probe for orchestration systems.
curl http://localhost:9000/health/readyResponse (200 OK):
{
"ready": true,
"components": {
"api": "up",
"sources": "up",
"decoders": "up"
}
}Full system status including sources, decoders, audio output, and tuner relay (if enabled).
curl http://localhost:9000/api/statusResponse:
{
"uptime": 3600,
"version": "1.0.0",
"sources": [
{
"id": "sdrpp-main",
"type": "sdrpp-network",
"connected": true,
"bytesReceived": 847000000,
"dataRate": 192000,
"reconnectAttempts": 0
}
],
"decoders": [
{
"id": "dsd-main",
"type": "dsd-fme",
"running": true,
"health": "running",
"pid": 1234,
"uptime": 3590,
"stats": {
"bytesIn": 423000000,
"eventsOut": 847,
"errors": 2
}
}
],
"audio": {
"outputPort": 8080,
"clientsConnected": 1,
"format": "S16LE",
"sampleRate": 48000
},
"tunerRelay": {
"enabled": true,
"listening": true,
"host": "0.0.0.0",
"port": 1234,
"sourceId": "rtl-pi",
"clientsConnected": 1,
"controlPolicy": "exclusive",
"bytesSent": 421000000,
"bytesReceived": 120,
"lastFrequency": 446524920,
"lastSampleRate": 2048000,
"lastCommand": "set-frequency",
"lastCommandAt": "2024-05-21T03:12:01.123Z",
"lastCommandValue": 446524920,
"commandHistoryLimit": 200,
"commandStats": [
{
"id": 1,
"name": "set-frequency",
"count": 6,
"lastValue": 446524920,
"lastSeenAt": "2024-05-21T03:12:01.123Z"
}
],
"commandHistory": [
{
"id": 1,
"name": "set-frequency",
"value": 446524920,
"at": "2024-05-21T03:12:01.123Z",
"clientId": "client-1",
"clientRemote": "192.168.1.50:50522"
}
]
}
}List all configured sources.
curl http://localhost:9000/api/sourcesResponse:
[
{
"id": "sdrpp-main",
"type": "sdrpp-network",
"host": "192.168.1.69",
"port": 5555,
"connected": true,
"bytesReceived": 847000000,
"dataRate": 192000,
"caps": {
"kind": "audio_pcm",
"sampleRate": 48000,
"format": "FLOAT32LE",
"exclusive": false
}
}
]List tuner states for all RTL-TCP sources.
Relay-driven RTL-TCP commands (from SDR++ via the tuner relay) update these
states and will automatically switch control mode to external while the relay
has an active control client.
curl http://localhost:9000/api/tunerResponse:
[
{
"sourceId": "rtl-pi",
"frequency": 144800000,
"sampleRate": 2400000,
"gainMode": "agc",
"gain": 0,
"ppm": 0,
"agcMode": true,
"biasTee": false,
"directSampling": "off",
"offsetTuning": false,
"ifGain": 0,
"tunerIfGain": null,
"testMode": false,
"controlMode": "internal",
"commandCount": 12,
"lastCommandAt": "2024-05-21T03:12:01.123Z"
}
]Get tuner state for a single source.
curl http://localhost:9000/api/tuner/rtl-piSet center frequency.
curl -X POST http://localhost:9000/api/tuner/rtl-pi/frequency \
-H "Content-Type: application/json" -d '{"hz":144800000}'Release control to SDR++ (external) or reclaim (internal).
curl -X POST http://localhost:9000/api/tuner/rtl-pi/control-mode \
-H "Content-Type: application/json" -d '{"mode":"external"}'POST /api/tuner/:sourceId/gain— Set manual gain ({ "tenthsDb": 400 })POST /api/tuner/:sourceId/gain-mode—manualoragcPOST /api/tuner/:sourceId/sample-rate— Set sample ratePOST /api/tuner/:sourceId/ppm— Set PPM correctionPOST /api/tuner/:sourceId/agc— RTL2832 AGC togglePOST /api/tuner/:sourceId/bias-tee— Bias-T power togglePOST /api/tuner/:sourceId/direct-sampling—off/i/qPOST /api/tuner/:sourceId/offset-tuning— Offset tuning togglePOST /api/tuner/:sourceId/if-gain— IF gain valuePOST /api/tuner/:sourceId/tuner-if-gain— IF stage/gain pairPOST /api/tuner/:sourceId/test-mode— Test mode togglePOST /api/tuner/:sourceId/rtl-xtal— RTL XTAL frequencyPOST /api/tuner/:sourceId/tuner-xtal— Tuner XTAL frequencyPOST /api/tuner/:sourceId/tuner-gain-index— Gain index valuePATCH /api/tuner/:sourceId/config— Bulk update
Get RTL-TCP tuner relay status and connection details.
curl http://localhost:9000/api/tuner-relayResponse:
{
"enabled": true,
"listening": true,
"host": "0.0.0.0",
"port": 1234,
"sourceId": "rtl-pi",
"sourceConnected": true,
"sourceKind": "iq",
"sourceFormat": "U8_IQ",
"compatibility": "ok",
"clientsConnected": 1,
"controlClientRemote": "192.168.1.50:50522",
"controlPolicy": "exclusive",
"bytesSent": 421000000,
"bytesReceived": 120,
"lastFrequency": 446524920,
"lastSampleRate": 2048000,
"lastCommand": "set-frequency",
"lastCommandAt": "2024-05-21T03:12:01.123Z",
"lastCommandValue": 446524920,
"commandHistoryLimit": 200,
"commandStats": [
{
"id": 1,
"name": "set-frequency",
"count": 6,
"lastValue": 446524920,
"lastSeenAt": "2024-05-21T03:12:01.123Z"
}
],
"commandHistory": [
{
"id": 1,
"name": "set-frequency",
"value": 446524920,
"at": "2024-05-21T03:12:01.123Z",
"clientId": "client-1",
"clientRemote": "192.168.1.50:50522"
}
]
}Get live demodulator status.
curl http://localhost:9000/api/live-audio/statusResponse:
{
"enabled": true,
"running": true,
"sourceId": "rtl-pi",
"sourceConnected": true,
"sourceIqSampleRate": 2400000,
"config": {
"enabled": true,
"sourceId": "rtl-pi",
"httpPort": 8081,
"modulation": "nfm",
"bandwidth": 12500,
"squelch": 0,
"noiseReduction": "off",
"lowPass": 0,
"highPass": 0,
"gain": 10,
"deEmphasis": false,
"deEmphasisTau": 50,
"audioFormat": "s16le",
"iqDcBlock": true
},
"effectiveSampleRate": 25000,
"decimationFactor": 96,
"httpUrl": "http://localhost:8081/stream",
"clientCount": 1,
"bytesStreamed": 1234567,
"pipelineHealth": "running"
}Start live demodulation.
curl -X POST http://localhost:9000/api/live-audio/startResponse:
{ "success": true }Stop live demodulation.
curl -X POST http://localhost:9000/api/live-audio/stopResponse:
{ "success": true }Update live demodulator configuration (hot-restart pipeline).
curl -X PATCH http://localhost:9000/api/live-audio/config \
-H "Content-Type: application/json" \
-d '{
"modulation": "am",
"bandwidth": 10000,
"gain": 8.0
}'Response:
Returns the updated status (same schema as /api/live-audio/status).
Get recommended presets per modulation.
curl http://localhost:9000/api/live-audio/presetsResponse:
{
"nfm": { "bandwidth": 12500, "deEmphasis": false },
"wfm": { "bandwidth": 150000, "deEmphasis": true, "deEmphasisTau": 50 },
"am": { "bandwidth": 10000, "deEmphasis": false },
"usb": { "bandwidth": 2400, "deEmphasis": false },
"lsb": { "bandwidth": 2400, "deEmphasis": false },
"dsb": { "bandwidth": 6000, "deEmphasis": false },
"cw": { "bandwidth": 500, "deEmphasis": false },
"raw": { "bandwidth": 0, "deEmphasis": false }
}Add a new source.
curl -X POST http://localhost:9000/api/sources \
-H "Content-Type: application/json" \
-d '{
"id": "rtl-pi",
"type": "rtl_tcp",
"host": "192.168.1.100",
"port": 1234,
"caps": {
"kind": "audio_pcm",
"sampleRate": 48000,
"format": "S16LE",
"exclusive": false
}
}'Response (201 Created):
{
"id": "rtl-pi",
"type": "rtl_tcp",
"connected": false,
"message": "Source added, connecting..."
}Remove a source.
curl -X DELETE http://localhost:9000/api/sources/rtl-piResponse (200 OK):
{
"id": "rtl-pi",
"message": "Source removed"
}List all decoders and their status.
curl http://localhost:9000/api/decodersResponse:
[
{
"id": "dsd-main",
"type": "dsd-fme",
"enabled": true,
"running": true,
"health": "running",
"pid": 1234,
"uptime": 3590,
"sourceId": "sdrpp-main",
"stats": {
"bytesIn": 423000000,
"eventsOut": 847,
"errors": 2
},
"restartCount": 0,
"version": "2.0.0"
},
{
"id": "readsb",
"type": "readsb",
"enabled": true,
"running": true,
"health": "running",
"pid": 1235,
"uptime": 3585,
"stats": {
"bytesIn": 0,
"eventsOut": 12847,
"errors": 0
},
"restartCount": 0,
"version": "3.14.1"
}
]Get status of a specific decoder.
curl http://localhost:9000/api/decoders/dsd-mainResponse:
{
"id": "dsd-main",
"type": "dsd-fme",
"enabled": true,
"running": true,
"health": "running",
"pid": 1234,
"uptime": 3590,
"sourceId": "sdrpp-main",
"stats": {
"bytesIn": 423000000,
"eventsOut": 847,
"errors": 2
},
"lastOutput": {
"timestamp": "2026-01-09T14:23:45.123Z",
"decoder": "dsd-main",
"type": "call",
"data": {
"talkgroup": 1234,
"source": 5678,
"mode": "DMR"
}
}
}Start a decoder.
curl -X POST http://localhost:9000/api/decoders/dsd-main/startResponse (200 OK):
{
"id": "dsd-main",
"running": true,
"pid": 1234,
"message": "Decoder started"
}Stop a decoder.
curl -X POST http://localhost:9000/api/decoders/dsd-main/stopResponse (200 OK):
{
"id": "dsd-main",
"running": false,
"message": "Decoder stopped"
}Restart a decoder.
curl -X POST http://localhost:9000/api/decoders/dsd-main/restartResponse (200 OK):
{
"id": "dsd-main",
"running": true,
"pid": 1235,
"message": "Decoder restarted"
}Update decoder configuration.
curl -X PATCH http://localhost:9000/api/decoders/dsd-main \
-H "Content-Type: application/json" \
-d '{
"options": {
"mode": "dmr"
}
}'Response (200 OK):
{
"id": "dsd-main",
"message": "Configuration updated, restart required"
}const ws = new WebSocket("ws://localhost:9000/ws")
ws.onopen = () => {
// Subscribe to channels
ws.send(
JSON.stringify({
type: "subscribe",
channels: [
"decoders",
"sources",
"metrics",
"health",
"fanout",
"live-audio",
"resources",
"tuner",
],
}),
)
}
ws.onmessage = event => {
const msg = JSON.parse(event.data)
console.log(msg.type, msg.data)
}{
"type": "subscribe",
"channels": [
"decoders",
"sources",
"metrics",
"health",
"fanout",
"live-audio",
"resources",
"tuner"
]
}{
"type": "unsubscribe",
"channels": ["metrics"]
}Emitted when a decoder produces output.
{
"type": "decoder:output",
"channel": "decoders",
"data": {
"decoderId": "dsd-main",
"output": {
"timestamp": "2026-01-09T14:23:45.123Z",
"decoder": "dsd-main",
"type": "call",
"data": {
"talkgroup": 1234,
"source": 5678,
"mode": "DMR",
"duration": 12.5
}
}
}
}{
"type": "decoder:started",
"channel": "decoders",
"data": {
"id": "dsd-main",
"pid": 1234
}
}{
"type": "decoder:stopped",
"channel": "decoders",
"data": {
"id": "dsd-main",
"exitCode": 0
}
}{
"type": "decoder:health",
"channel": "health",
"data": {
"id": "dsd-main",
"health": "degraded",
"previousHealth": "running",
"reason": "No output for 30 seconds"
}
}{
"type": "source:connected",
"channel": "sources",
"data": {
"id": "sdrpp-main",
"host": "192.168.1.69",
"port": 5555
}
}{
"type": "source:disconnected",
"channel": "sources",
"data": {
"id": "sdrpp-main",
"error": "Connection reset by peer"
}
}Emitted when source capabilities change dynamically (e.g., sample rate changed via TunerRelay).
{
"type": "source:caps-changed",
"channel": "sources",
"data": {
"sourceId": "rtl-pi",
"caps": {
"kind": "iq",
"sampleRate": 2400000,
"format": "U8_IQ",
"exclusive": false
}
}
}Emitted every ~5 seconds.
{
"type": "metrics",
"channel": "metrics",
"data": {
"timestamp": "2026-01-09T14:23:45.123Z",
"sources": {
"sdrpp-main": {
"bytesReceived": 847000000,
"dataRate": 192000
}
},
"decoders": {
"dsd-main": {
"bytesIn": 423000000,
"eventsOut": 847
}
}
}
}Backpressure status snapshot.
{
"type": "fanout:snapshot",
"channel": "fanout",
"data": {
"timestamp": "2026-01-09T14:23:45.123Z",
"totalBytesWritten": 847000000,
"droppedBytesTotal": 0,
"backpressureActiveCount": 0,
"branches": [
{
"id": "dsd-main",
"bufferedBytes": 1024,
"droppedBytesTotal": 0,
"backpressure": false
}
]
}
}Confirmation of subscription.
{
"type": "subscribed",
"data": {
"channels": ["decoders", "sources", "metrics", "health", "fanout"]
}
}{
"type": "error",
"data": {
"message": "Invalid channel: foo"
}
}Decoded audio is available via TCP on port 8080.
- Encoding: S16LE (16-bit signed, little-endian)
- Sample Rate: 48000 Hz
- Channels: 1 (mono)
# Using sox
nc localhost 8080 | play -t raw -r 48000 -e signed -b 16 -c 1 -
# Using ffplay
nc localhost 8080 | ffplay -f s16le -ar 48000 -ac 1 -nodisp -
# Using VLC
nc localhost 8080 | vlc --demux=rawaud --rawaud-channels=1 --rawaud-samplerate=48000 -
# Record to file
nc localhost 8080 | sox -t raw -r 48000 -e signed -b 16 -c 1 - output.wavAll error responses follow this format:
{
"error": {
"code": "DECODER_NOT_FOUND",
"message": "Decoder 'foo' not found",
"statusCode": 404
}
}| Code | HTTP Status | Description |
|---|---|---|
DECODER_NOT_FOUND |
404 | Decoder ID doesn't exist |
SOURCE_NOT_FOUND |
404 | Source ID doesn't exist |
DECODER_ALREADY_RUNNING |
409 | Decoder is already running |
DECODER_NOT_RUNNING |
409 | Decoder is not running |
INVALID_CONFIG |
400 | Invalid configuration provided |
SOURCE_CONNECTION_ERROR |
503 | Cannot connect to source |
INTERNAL_ERROR |
500 | Internal server error |
The API does not currently implement rate limiting. For production deployments, consider placing a reverse proxy (nginx, Caddy) in front of WaveKit.
CORS is enabled by default, allowing requests from any origin. Configure via:
api:
cors:
enabled: true
origins: ["*"]