The control API
Eight routes on the HTTP port. Start and stop capture from anywhere with the token, choose the engine, ask what it is doing, and find out whether it can hear.
On http://127.0.0.1:7357. JSON out; only POST /engine takes a body in.
Every route is behind the token — Authentication — and the
reference has each one’s exact shape, generated from
the same file CI tests the service against.
| route | does |
|---|---|
GET / |
discovery: name, version, auth scheme, ports |
GET /events |
the stream — The protocol |
GET /status |
current state |
POST /start |
begin capture |
POST /stop |
end capture |
GET /engine |
the engine, and which ones this Mac can run — Engine selection |
POST /engine |
switch engine; a stop and a start if listening |
POST /token/rotate |
new token; the old one is dead after the response |
Discovery
GET / is the first call a client makes.
{ "name": "Sonocles", "version": "0.1.3", "auth": "bearer", "ports": { "http": 7357, "ws": 7358 } }version is what the bundle was stamped with, or dev from the CLI, which
has no bundle. ws is absent when the WebSocket transport was not started —
absent, not 0, as everywhere else here. auth is always bearer; it is
there so a client written against a future scheme can tell which one it has
reached rather than guessing from a 401.
Status
GET /status, and the answer to POST /start and POST /stop:
{ "state": "listening", "listening": true, "engine": "Parakeet EOU 120M (160 ms)", "engineId": "fluid160", "clients": 1, "uptime": 41.2, "levelDb": -19.4 }state is idle · starting · listening. Three states rather than a
boolean because POST /start returns before capture is up — models load,
macOS may ask for the microphone — and answering listening: false to a
request that just succeeded reads as a failure. Poll until listening.
levelDb is the peak input level in dBFS, and it is absent when not
capturing — absent meaning unmeasured, never zero. It exists because “is it
hearing anything” is otherwise unanswerable from outside the process.
Signal with no text is a working microphone in a quiet room, or a room with
something in it that is not speech; no signal at all is a different problem
entirely. Establishing that thirty seconds of piano at −9 dBFS peak produced
zero words required starting a second sidecar purely to watch a meter,
which is a silly thing to need.
engine is the engine’s own name once running, else the configured
choice’s label — for a display. engineId is the slug the control routes
speak, so a client polling /status knows what it is looking at without a
second call; Engine selection has the routes.
clients counts connections across both transports. uptime is seconds
since the sockets came up, not since capture started.
While preparing
On the first start, and only then, two more fields appear while the models are fetched and compiled:
{ "state": "starting", "listening": false, "engine": "Parakeet 160 ms", "engineId": "fluid160", "clients": 1, "uptime": 3.1, "preparing": "Downloading model — 42% (file 2 of 5)", "preparingFraction": 0.42 }preparing is a line fit for a status field. preparingFraction is
present while downloading and absent while compiling, which has no
measurable progress and is not given an invented one. Both vanish once the
models are resident.
Start and stop
POST /start and POST /stop answer at once with the state after the
request. Both are idempotent: start while starting or listening changes
nothing, stop on an idle sidecar answers idle and does nothing. The
sockets are untouched by either; a consumer attached to /events across a
stop and a start sees a gap in frames and nothing else.
From a shell
TOKEN="$(cat ~/Library/Application\ Support/Sonocles/token)"curl -s -H "Authorization: Bearer $TOKEN" http://127.0.0.1:7357/curl -s -H "Authorization: Bearer $TOKEN" -X POST http://127.0.0.1:7357/startcurl -s -H "Authorization: Bearer $TOKEN" http://127.0.0.1:7357/statuscurl -s -H "Authorization: Bearer $TOKEN" -X POST http://127.0.0.1:7357/stopOr, from the repository, make discover, make start, make status,
make stop, make engine, make rotate and make events: the Makefile
reads the token file. PORT= overrides the port for a CLI started with --http.
From a browser page
The responses carry Access-Control-Allow-Origin: * and the preflight is
answered without the token, so a page on any origin can drive the control
API — provided it has the token, which a page cannot read from disk. Paste
it from the popover’s Copy button into the page’s settings, and the page is
paired the same as an app. That is how Pteroprompter, which is a page,
reaches /events.