The wfview web server exposes a REST API over HTTP for scripting and integration with external tools. All radio control operations available via WebSocket are also available via REST.
Format: JSON (Content-Type: application/json)
Authentication: None (Phase 4)
CORS: Access-Control-Allow-Origin: * on all responses
wfview runs two HTTP listeners simultaneously when SSL is available:
| Port | Protocol | Best for |
|---|---|---|
| 8080 | HTTPS (self-signed) | Browser — required for mic/audio (isSecureContext) |
| 8081 | HTTP (plain) | Scripts, curl, microcontrollers, home automation |
Base URL (plain HTTP): http://<host>:8081/api/v1/radio
Base URL (HTTPS): https://<host>:8080/api/v1/radio (add -k to curl for self-signed cert)
When SSL is not available on the host system, port 8080 is plain HTTP and port 8081 is the WebSocket port (no separate REST HTTP server in that case — use port 8080).
Microcontrollers (ESP32, Pico W, Arduino): use port 8081 with plain HTTP. No TLS stack needed.
All responses are JSON objects.
Success (GET): 200 OK with requested data fields.
Success (write): 202 Accepted — the command was queued. Writes are fire-and-forget via the cachingQueue; the radio will process them asynchronously.
{"status": "accepted"}Error: appropriate 4xx/5xx status code with:
{"error": "description"}Status codes:
| Code | Meaning |
|---|---|
200 |
OK — GET success |
202 |
Accepted — PUT/POST/DELETE queued |
400 |
Bad Request — malformed JSON or missing required field |
404 |
Not Found — unknown API path |
405 |
Method Not Allowed |
503 |
Service Unavailable — rig not connected |
Full combined info + status.
curl -s http://localhost:8081/api/v1/radio | jq .Response:
{
"info": {
"connected": true,
"model": "IC-7300",
"version": "0.2.4",
"hasTransmit": true,
"hasSpectrum": true,
"modes": ["LSB", "USB", "AM", "FM", "CW", "CW-R", "RTTY", "RTTY-R"],
"audioAvailable": true,
"audioSampleRate": 48000,
"txAudioAvailable": true,
"preamps": [{"num": 1, "name": "Preamp 1"}, {"num": 2, "name": "Preamp 2"}],
"filters": [{"num": 1, "name": "FIL1"}, {"num": 2, "name": "FIL2"}, {"num": 3, "name": "FIL3"}],
"spans": [{"reg": 1, "name": "±2.5kHz", "freq": 5000}]
},
"status": {
"frequency": 14200000,
"vfoAFrequency": 14200000,
"vfoBFrequency": 7074000,
"mode": "USB",
"filter": 1,
"transmitting": false,
"sMeter": 30.0,
"powerMeter": 0.0,
"swrMeter": 1.0,
"afGain": 200,
"rfGain": 255,
"rfPower": 128,
"squelch": 0,
"split": false,
"tuner": 0,
"preamp": 1,
"autoNotch": false,
"nb": false,
"nr": false,
"filterWidth": 0
}
}Rig capabilities and server info. Available even when rig is not connected (connected: false).
curl -s http://localhost:8081/api/v1/radio/info | jq .Response: same as info object above.
All current radio state fields.
curl -s http://localhost:8081/api/v1/radio/status | jq .Response: same as status object above. Returns 503 if rig not connected.
curl -s http://localhost:8081/api/v1/radio/frequency | jq .Response:
{"hz": 14200000, "mhz": 14.2}Request body:
{"hz": 14200000}curl -s -X PUT http://localhost:8081/api/v1/radio/frequency \
-H 'Content-Type: application/json' \
-d '{"hz": 14200000}' | jq .curl -s http://localhost:8081/api/v1/radio/mode | jq .Response:
{"mode": "USB", "filter": 1}Request body:
{"mode": "USB", "filter": 1}filter is optional (1=FIL1, 2=FIL2, 3=FIL3). Omitting it keeps the current filter.
curl -s -X PUT http://localhost:8081/api/v1/radio/mode \
-H 'Content-Type: application/json' \
-d '{"mode": "CW", "filter": 2}' | jq .Valid mode names come from the modes array in /info (radio-dependent).
curl -s http://localhost:8081/api/v1/radio/vfo | jq .Response:
{"vfoA": 14200000, "vfoB": 7074000}Select active VFO:
{"active": "A"}or "B".
Swap VFO A↔B frequencies:
{"action": "swap"}Equalize (copy active VFO to inactive):
{"action": "equalize"}# Select VFO B
curl -s -X PUT http://localhost:8081/api/v1/radio/vfo \
-H 'Content-Type: application/json' \
-d '{"active": "B"}' | jq .
# Swap VFOs
curl -s -X PUT http://localhost:8081/api/v1/radio/vfo \
-H 'Content-Type: application/json' \
-d '{"action": "swap"}' | jq .curl -s http://localhost:8081/api/v1/radio/ptt | jq .Response:
{"transmitting": false}Request body:
{"transmitting": true}# PTT on
curl -s -X PUT http://localhost:8081/api/v1/radio/ptt \
-H 'Content-Type: application/json' \
-d '{"transmitting": true}' | jq .
# PTT off
curl -s -X PUT http://localhost:8081/api/v1/radio/ptt \
-H 'Content-Type: application/json' \
-d '{"transmitting": false}' | jq .Read-only. S-meter, TX power, and SWR.
curl -s http://localhost:8081/api/v1/radio/meters | jq .Response:
{"sMeter": 54.0, "powerMeter": 0.0, "swrMeter": 1.0}Calibration notes:
sMeter: 0 = S9; each S-unit = 6 units (so S8 ≈ -6, S7 ≈ -12, etc.)swrMeter: ratio 1.0–6.0powerMeter: radio-dependent scaling
curl -s http://localhost:8081/api/v1/radio/gains | jq .Response:
{"afGain": 200, "rfGain": 255, "rfPower": 128, "squelch": 0}All fields are optional. Only provided fields are updated.
| Field | Range | Description |
|---|---|---|
afGain |
0–255 | Audio frequency gain |
rfGain |
0–255 | RF input sensitivity |
rfPower |
0–255 | TX power output |
squelch |
0–255 | Squelch threshold |
curl -s -X PUT http://localhost:8081/api/v1/radio/gains \
-H 'Content-Type: application/json' \
-d '{"afGain": 180, "rfPower": 100}' | jq .Receiver DSP settings.
curl -s http://localhost:8081/api/v1/radio/rx | jq .Response:
{
"preamp": 1,
"attenuator": 0,
"nb": false,
"nr": false,
"agc": 2,
"autoNotch": false,
"filterWidth": 0
}All fields optional. Only provided fields are updated.
| Field | Type | Description |
|---|---|---|
preamp |
int (0–255) | Preamp selection |
attenuator |
int (0–255) | Attenuator level |
nb |
bool | Noise Blanker on/off |
nr |
bool | Noise Reduction on/off |
agc |
int (0–255) | AGC mode |
autoNotch |
bool | Auto Notch on/off |
filterWidth |
int (0–10000) | IF filter width in Hz |
curl -s -X PUT http://localhost:8081/api/v1/radio/rx \
-H 'Content-Type: application/json' \
-d '{"nb": true, "nr": true}' | jq .Transmitter settings.
curl -s http://localhost:8081/api/v1/radio/tx | jq .Response:
{"split": false, "tuner": 0, "compressor": false, "monitor": false}tuner: 0=off, 1=on, 2=start-tuning.
compressorandmonitormay be absent if the rig has not reported them.
All fields optional.
| Field | Type | Description |
|---|---|---|
split |
bool | Split mode on/off |
tuner |
int (0–2) | 0=off, 1=on, 2=start tuning |
compressor |
bool | Speech compressor on/off |
monitor |
bool | TX monitor (sidetone) on/off |
curl -s -X PUT http://localhost:8081/api/v1/radio/tx \
-H 'Content-Type: application/json' \
-d '{"split": true}' | jq .Send CW text.
Request body:
{"text": "CQ DE K1AB K", "wpm": 20}wpm is optional (range 6–48). If omitted, current radio speed is used.
curl -s -X POST http://localhost:8081/api/v1/radio/cw \
-H 'Content-Type: application/json' \
-d '{"text": "CQ DE K1AB K", "wpm": 20}' | jq .Stop CW transmission.
curl -s -X DELETE http://localhost:8081/api/v1/radio/cw | jq .All endpoints respond to OPTIONS with CORS headers, enabling use from browser JavaScript on any origin:
curl -s -X OPTIONS http://localhost:8081/api/v1/radio/frequency \
-H 'Origin: http://example.com' \
-H 'Access-Control-Request-Method: PUT' -v 2>&1 | grep -i "access-control"# Monitor S-meter in a loop
while true; do
curl -s http://localhost:8081/api/v1/radio/meters | jq '.sMeter'
sleep 1
done
# QSY to 40m FT8
curl -s -X PUT http://localhost:8081/api/v1/radio/frequency \
-H 'Content-Type: application/json' \
-d '{"hz": 7074000}'
curl -s -X PUT http://localhost:8081/api/v1/radio/mode \
-H 'Content-Type: application/json' \
-d '{"mode": "USB-D"}'
# Check if transmitting
curl -s http://localhost:8081/api/v1/radio/ptt | jq '.transmitting'
# Reduce power to 50W (assuming 200 ≈ 100W for IC-7300)
curl -s -X PUT http://localhost:8081/api/v1/radio/gains \
-H 'Content-Type: application/json' \
-d '{"rfPower": 100}'