PAPASWITCH
HTTP API

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

MethodPathWhat it does
GET/api/infoEverything at once: callsign, switch state, every antenna with its label.
GET/api/stateSwitch state only.
POST/api/selectSelect an antenna. Answers when the switch has confirmed.
GET/api/labelsAntenna labels.
PUT/api/labelsReplace all antenna labels.
GET/api/eventsLive updates (Server-Sent Events).
GET/api/logRecent console messages.
PUT/api/log/trafficRecord raw serial traffic in the console, or stop.
POST/api/modelChange 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")).

PANEL