From 70b3e2a84724ffed652101615ea808684dff7dc3 Mon Sep 17 00:00:00 2001 From: 44r0n7 <44r0n7@noreply.localhost> Date: Sat, 18 Apr 2026 16:48:32 +0000 Subject: [PATCH] docs(wiki): add full HTTP API reference --- API.md | 453 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 453 insertions(+) create mode 100644 API.md diff --git a/API.md b/API.md new file mode 100644 index 0000000..e48616b --- /dev/null +++ b/API.md @@ -0,0 +1,453 @@ +# HTTP API + +`tvctl` exposes a loopback HTTP API for local automation and dashboards. + +## Base URL + +- Default: `http://127.0.0.1:7272/v1` +- Configurable with `daemon.http_host` and `daemon.http_port` +- API version prefix is always `/v1` + +## Response Envelope + +Successful responses: + +```json +{ + "ok": true, + "data": {} +} +``` + +Error responses: + +```json +{ + "ok": false, + "error": { + "code": "device_not_found", + "message": "No known device matched 'living-room'.", + "hint": "Run tvctl device list to see known devices." + } +} +``` + +Use `error.code` for automation logic. `message` and `hint` are human-facing. + +## Device Identifiers + +Anywhere `{id}` appears, you can use either: + +- tvctl UUID +- Friendly device name + +Example: + +- `GET /v1/devices/3e2f7e8b-2d5e-4eb5-aef3-b2c4f2b2f17a/state` +- `GET /v1/devices/living-room/state` + +## Endpoints + +### `GET /v1/devices` + +List known devices. + +Example: + +```bash +curl http://127.0.0.1:7272/v1/devices +``` + +Returns `data` as `Device[]`. + +--- + +### `POST /v1/devices/discover` + +Run discovery immediately and merge results into registry. + +Example: + +```bash +curl -X POST http://127.0.0.1:7272/v1/devices/discover +``` + +Returns `data`: + +```json +{ + "devices": [] +} +``` + +--- + +### `GET /v1/devices/{id}` + +Get one known device by UUID or friendly name. + +Example: + +```bash +curl http://127.0.0.1:7272/v1/devices/living-room +``` + +Returns `data` as `Device`. + +--- + +### `DELETE /v1/devices/{id}` + +Remove one device from the registry. + +Example: + +```bash +curl -X DELETE http://127.0.0.1:7272/v1/devices/bedroom +``` + +Returns `data` as removed `Device`. + +--- + +### `GET /v1/devices/{id}/state` + +Fetch live state for one device. + +Example: + +```bash +curl http://127.0.0.1:7272/v1/devices/living-room/state +``` + +Returns `data`: + +```json +{ + "device": {}, + "state": {} +} +``` + +--- + +### `GET /v1/devices/{id}/apps` + +List installed apps for device platform from cache. + +Example: + +```bash +curl http://127.0.0.1:7272/v1/devices/living-room/apps +``` + +Returns `data`: + +```json +{ + "platform": "roku", + "apps": [] +} +``` + +--- + +### `POST /v1/devices/{id}/apps/launch` + +Launch an app by normalized app id, app name, or platform id. + +Request body: + +```json +{ + "app": "jellyfin" +} +``` + +Example: + +```bash +curl -X POST http://127.0.0.1:7272/v1/devices/living-room/apps/launch \ + -H 'Content-Type: application/json' \ + -d '{"app":"jellyfin"}' +``` + +Returns action payload in `data`. + +--- + +### `POST /v1/devices/{id}/apps/stop` + +Stop the currently active app. + +Example: + +```bash +curl -X POST http://127.0.0.1:7272/v1/devices/living-room/apps/stop +``` + +Returns action payload in `data`. + +--- + +### `POST /v1/devices/{id}/apps/refresh` + +Refresh cached app catalog from live device. + +Request body is optional. + +- Without body: merge/refresh cache +- With body: + +```json +{ + "clear": true +} +``` + +`clear=true` clears platform cache before refresh. + +Examples: + +```bash +curl -X POST http://127.0.0.1:7272/v1/devices/living-room/apps/refresh + +curl -X POST http://127.0.0.1:7272/v1/devices/living-room/apps/refresh \ + -H 'Content-Type: application/json' \ + -d '{"clear":true}' +``` + +Returns action payload in `data`. + +--- + +### `POST /v1/devices/{id}/remote/key` + +Send a single normalized key. + +Request body: + +```json +{ + "key": "home" +} +``` + +Example: + +```bash +curl -X POST http://127.0.0.1:7272/v1/devices/living-room/remote/key \ + -H 'Content-Type: application/json' \ + -d '{"key":"volume-up"}' +``` + +Returns action payload in `data`. + +--- + +### `POST /v1/devices/{id}/remote/sequence` + +Send multiple normalized keys with per-key delay. + +Request body: + +```json +{ + "keys": ["home", "home", "up"], + "delay_ms": 200 +} +``` + +`delay_ms` defaults to `200` when omitted. + +Example: + +```bash +curl -X POST http://127.0.0.1:7272/v1/devices/living-room/remote/sequence \ + -H 'Content-Type: application/json' \ + -d '{"keys":["home","home","home","up"],"delay_ms":200}' +``` + +Returns action payload in `data`. + +--- + +### `POST /v1/devices/{id}/dev/install` + +Sideload a development package ZIP (Roku developer mode). + +Content type: `multipart/form-data` + +Form field: + +- `archive`: zip file bytes + +Example: + +```bash +curl -X POST http://127.0.0.1:7272/v1/devices/living-room/dev/install \ + -F archive=@./channel.zip +``` + +Returns action payload in `data`. + +--- + +### `POST /v1/devices/{id}/dev/reload` + +Reload currently sideloaded development package. + +Example: + +```bash +curl -X POST http://127.0.0.1:7272/v1/devices/living-room/dev/reload +``` + +Returns action payload in `data`. + +--- + +### `GET /v1/devices/{id}/dev/logs` + +Fetch recent device developer logs. + +Example: + +```bash +curl http://127.0.0.1:7272/v1/devices/living-room/dev/logs +``` + +Returns `data`: + +```json +{ + "device": {}, + "lines": [] +} +``` + +--- + +### `GET /v1/daemon/status` + +Get daemon health and runtime status. + +Example: + +```bash +curl http://127.0.0.1:7272/v1/daemon/status +``` + +Returns `data`: + +```json +{ + "pid": 12345, + "socket": "/run/user/1000/tvctl.sock", + "http_enabled": true, + "http_host": "127.0.0.1", + "http_port": 7272, + "device_count": 1, + "default_device": "living-room" +} +``` + +--- + +### `GET /v1/config` + +Read effective config from disk. + +Notes: + +- `dev.roku_password` is redacted in API responses. + +Example: + +```bash +curl http://127.0.0.1:7272/v1/config +``` + +Returns `data` as full config object. + +--- + +### `PATCH /v1/config` + +Patch config using flat key/value pairs. + +Rules: + +- Keys are dotted paths (for example `daemon.http_port`) +- Values must be string, number, boolean, or `null` + +Example: + +```bash +curl -X PATCH http://127.0.0.1:7272/v1/config \ + -H 'Content-Type: application/json' \ + -d '{"daemon.cors_enabled":true,"daemon.cors_allowed_origins":"http://127.0.0.1:8080"}' +``` + +Returns same shape as config reload result in `data`. + +--- + +### `POST /v1/config/reload` + +Reload daemon config from disk. + +Example: + +```bash +curl -X POST http://127.0.0.1:7272/v1/config/reload +``` + +Returns `data`: + +```json +{ + "config": {}, + "restart_required": [] +} +``` + +## Key Names + +Common key names: + +- `home`, `back`, `up`, `down`, `left`, `right`, `select` +- `play`, `pause`, `play-pause`, `stop`, `rewind`, `fast-forward`, `replay`, `skip` +- `volume-up`, `volume-down`, `mute` +- `power`, `power-on`, `power-off` +- `channel-up`, `channel-down` +- `input-hdmi1`, `input-hdmi2`, `input-hdmi3`, `input-hdmi4`, `input-av`, `input-tuner` +- `search`, `info`, `options` +- `literal:` for raw adapter literal keys + +## HTTP Status Mapping + +Typical statuses: + +- `200 OK` success +- `400 Bad Request` invalid key, invalid payload, ambiguous app, unsupported patch values +- `404 Not Found` unknown device/default device missing +- `500 Internal Server Error` transport/adapter/internal failures + +## CORS Notes + +Browser clients require CORS config: + +```toml +[daemon] +cors_enabled = true +cors_allowed_origins = ["http://127.0.0.1:8080", "http://localhost:8080"] +``` + +Then reload: + +```bash +tvctl config reload +``` + +Origin must match exactly (`http://127.0.0.1:8080` is not `http://0.0.0.0:8080`).