Everything the control panel does is available to programs (home automation, scripts, logging software) as a small JSON API over HTTP or HTTPS. Base address: http://<station>:8080.
AUTHENTICATION
Create an API key on the settings page (SETUP → API KEYS). The key is shown only once. Send it with every request, in either header:
Authorization: Bearer psw_... X-API-Key: psw_...
A wrong or revoked key gets 401. Requests without a key are accepted only where the panel itself would be (this computer, trusted devices, open network access, or a browser that logged in). API keys cannot change the settings.
ENDPOINTS
| Method | Path | What it does |
|---|---|---|
| GET | /api/info | Everything at once: callsign, switch state, every antenna with its label. |
| GET | /api/state | Switch state only. |
| POST | /api/select | Select an antenna. Answers when the switch has confirmed. |
| GET | /api/labels | Antenna labels. |
| PUT | /api/labels | Replace all antenna labels. |
| GET | /api/events | Live updates (Server-Sent Events). |
| GET | /api/log | Recent console messages. |
| PUT | /api/log/traffic | Record raw serial traffic in the console, or stop. |
| POST | /api/model | Change the model stored in the switch. Special permission, see below. |
GET /api/info
{
"callsign": "IZ1ABC",
"state": {
"online": true, // the switch answers
"port": "COM6", // serial port in use
"model": "8A1R", // 4A1R, 6A1R or 8A1R
"positions": 8, // number of antennas
"antenna": 3, // selected antenna (0 = unknown)
"flag": false, // flag reported by the switch (meaning not confirmed)
"error": "", // last problem, empty when all is well
"updated": "2026-10-31T12:00:00+01:00",
"labelsRevision": 4 // goes up when labels are edited
},
"selectedLabel": "Vertical 80/160",
"antennas": [
{ "number": 1, "label": "Yagi 20/15/10", "selected": false },
{ "number": 3, "label": "Vertical 80/160", "selected": true }
],
"labels": { "1": "Yagi 20/15/10", "3": "Vertical 80/160" }
}
POST /api/select
request: { "antenna": 3 }
response: the new state (as in /api/state)
The station sends the command, reads the position back and repeats the command up to three times if the switch did not move, so a 200 answer means the antenna really changed (allow up to about 3 seconds). Errors: 400 antenna that does not exist or switch that did not move, 503 switch offline.
Do not switch while transmitting.
GET / PUT /api/labels
{ "1": "Yagi 20/15/10", "2": "Dipole 40" }
PUT replaces all labels; missing or empty entries are removed. Labels are at most 40 characters. Every open panel shows the change at once.
GET /api/events
A Server-Sent Events stream that never ends. Unnamed events carry the state (as in /api/state) every time it changes, including changes made by hand on the switch; log events carry console messages. A comment line every 15 s keeps it alive.
data: {"online":true,"antenna":3,...}
event: log
data: {"time":"...","level":"info","message":"antenna 3 selected (ack yes, 151ms)"}
GET /api/log, PUT /api/log/traffic
GET → { "entries": [ { "time": "...", "level": "warn", "message": "..." } ], "traffic": false }
PUT { "enabled": true } levels: serial, info, warn, error
POST /api/model
request: { "positions": 8, "confirm": true } // 4, 6 or 8
response: { "model": "8A1R", "state": { ... } }
Rewrites the model stored inside the switch (it survives power cycles). Only for the admin, or API keys created with "can also change the switch model"; "confirm": true is required. With a wrong value, antennas above it can no longer be selected until it is set back. Every change is logged with who made it.
ERRORS
Errors are JSON: { "error": "what went wrong" }, with 400 bad request, 401 no or wrong key, 403 not allowed, 503 switch offline.
EXAMPLES
curl
KEY=psw_...
curl -H "Authorization: Bearer $KEY" http://station:8080/api/info
curl -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"antenna":3}' http://station:8080/api/select
Python
import requests
BASE = "http://station:8080"
HEADERS = {"Authorization": "Bearer psw_..."}
info = requests.get(f"{BASE}/api/info", headers=HEADERS).json()
print(info["state"]["antenna"], info["selectedLabel"])
requests.post(f"{BASE}/api/select", json={"antenna": 3}, headers=HEADERS).raise_for_status()
JavaScript (Node 18+)
const BASE = "http://station:8080";
const headers = { Authorization: "Bearer psw_...", "Content-Type": "application/json" };
const info = await (await fetch(`${BASE}/api/info`, { headers })).json();
await fetch(`${BASE}/api/select`, { method: "POST", headers, body: JSON.stringify({ antenna: 3 }) });
HTTPS
The same API is available over HTTPS (default port 8443). The station uses its own certificate authority: install it on the client, see certificate, or point your HTTP library at papaswitch-ca.pem (e.g. curl --cacert papaswitch-ca.pem, requests.get(..., verify="papaswitch-ca.pem")).