Clone
1
API
44r0n7 edited this page 2026-04-18 16:48:32 +00:00

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:

{
  "ok": true,
  "data": {}
}

Error responses:

{
  "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:

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:

curl -X POST http://127.0.0.1:7272/v1/devices/discover

Returns data:

{
  "devices": []
}

GET /v1/devices/{id}

Get one known device by UUID or friendly name.

Example:

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:

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:

curl http://127.0.0.1:7272/v1/devices/living-room/state

Returns data:

{
  "device": {},
  "state": {}
}

GET /v1/devices/{id}/apps

List installed apps for device platform from cache.

Example:

curl http://127.0.0.1:7272/v1/devices/living-room/apps

Returns data:

{
  "platform": "roku",
  "apps": []
}

POST /v1/devices/{id}/apps/launch

Launch an app by normalized app id, app name, or platform id.

Request body:

{
  "app": "jellyfin"
}

Example:

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:

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:
{
  "clear": true
}

clear=true clears platform cache before refresh.

Examples:

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:

{
  "key": "home"
}

Example:

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:

{
  "keys": ["home", "home", "up"],
  "delay_ms": 200
}

delay_ms defaults to 200 when omitted.

Example:

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:

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:

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:

curl http://127.0.0.1:7272/v1/devices/living-room/dev/logs

Returns data:

{
  "device": {},
  "lines": []
}

GET /v1/daemon/status

Get daemon health and runtime status.

Example:

curl http://127.0.0.1:7272/v1/daemon/status

Returns data:

{
  "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:

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:

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:

curl -X POST http://127.0.0.1:7272/v1/config/reload

Returns data:

{
  "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:<text> 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:

[daemon]
cors_enabled = true
cors_allowed_origins = ["http://127.0.0.1:8080", "http://localhost:8080"]

Then reload:

tvctl config reload

Origin must match exactly (http://127.0.0.1:8080 is not http://0.0.0.0:8080).