A REST API bridge for NAD receivers with telnet control. It connects to your receiver over telnet and exposes an HTTP API for power, volume, source, and other settings.
Built for Home Assistant and other home automation systems. Tested on the NAD T778 but should work with any NAD receiver that supports telnet control.
- Create a
config.ednwith your device info:
{:nad-devices [{:name "nad-t778" ;; a url friendly name
:host "10.9.4.12"
:port 23}]
:http {:port 8002}}- Run the server:
# if you have nix
nix run github:ramblurr/nad-api
# or from a local checkout
clojure -M -m ol.nad-api- On startup, reads device configuration from
config.edn - Connects to each NAD receiver via telnet (port 23)
- Sends the introspection command (
?) to discover supported commands per device - Generates REST API routes for each device and its supported commands
- Exposes a web server on port 8002
GET /api- List all configured devicesGET /api/{device}- Device info and supported commandsGET /api/{device}/{command}- Query current valuePOST /api/{device}/{command}- Set or modify value
This was created primarily to expose my NAD receiver to Home Assistant.
HA has two pre-existing integrations that could be used, but in my experience neither work wrll.
- Telnet - I could not get the
value_template/command_stateworking with my NAD. Furthermore this integration uses the deprecated telnetlib python module, so it's probably going to be removed. - NAD - Does not work reliably. The entity goes offline for days at a time.
Notably I do not need a media_player entity for my NAD receiver, the Bluesound and Roon integrations take care of that. I need controls for power (main zone and zone 2) and volume, etc.
And I need it to work reliably!
I don't want to create or maintain a new custom integration. Rather we lean on the following builtin HA integrations:
More information at docs/home-assistant.md
curl http://localhost:8002/apiResponse:
{
"devices": {
"nad-t778": {
"host": "10.9.4.12",
"port": 23,
"model": "T778"
}
}
}curl http://localhost:8002/api/nad-t778Response:
{
"device": {
"name": "nad-t778",
"host": "10.9.4.12",
"port": 23,
"model": "T778"
},
"supportedCommands": {
"Main.Mute": {
"operators": ["?", "=", "+", "-"]
},
"Main.Power": {
"operators": ["?", "=", "+", "-"]
},
"Main.Source": {
"operators": ["?", "=", "+", "-"]
},
"Main.Volume": {
"operators": ["?", "=", "+", "-"]
}
}
}curl http://localhost:8002/api/nad-t778/Main.PowerResponse:
{"command": "Main.Power", "value": "On"}# Turn on
curl -X POST http://localhost:8002/api/nad-t778/Main.Power \
-H "Content-Type: application/json" \
-d '{"operator": "=", "value": "On"}'
# Turn off
curl -X POST http://localhost:8002/api/nad-t778/Main.Power \
-H "Content-Type: application/json" \
-d '{"operator": "=", "value": "Off"}'Response:
{"command": "Main.Power", "operator": "=", "value": "Off"}curl http://localhost:8002/api/nad-t778/Main.VolumeResponse:
{"command": "Main.Volume", "value": "-48"}# Set to specific dB level
curl -X POST http://localhost:8002/api/nad-t778/Main.Volume \
-H "Content-Type: application/json" \
-d '{"operator": "=", "value": "-40"}'
# Increment
curl -X POST http://localhost:8002/api/nad-t778/Main.Volume \
-H "Content-Type: application/json" \
-d '{"operator": "+"}'
# Decrement
curl -X POST http://localhost:8002/api/nad-t778/Main.Volume \
-H "Content-Type: application/json" \
-d '{"operator": "-"}'# Query current source
curl http://localhost:8002/api/nad-t778/Main.Source
# Set source by index
curl -X POST http://localhost:8002/api/nad-t778/Main.Source \
-H "Content-Type: application/json" \
-d '{"operator": "=", "value": "3"}'# Query mute state
curl http://localhost:8002/api/nad-t778/Main.Mute
# Mute
curl -X POST http://localhost:8002/api/nad-t778/Main.Mute \
-H "Content-Type: application/json" \
-d '{"operator": "=", "value": "On"}'
# Unmute
curl -X POST http://localhost:8002/api/nad-t778/Main.Mute \
-H "Content-Type: application/json" \
-d '{"operator": "=", "value": "Off"}'Force a device to disconnect and reconnect its telnet session:
curl -X POST http://localhost:8002/api/nad-t778/reconnectResponse:
{"device": "nad-t778", "status": "reconnected", "host": "10.9.4.12", "port": 23, "model": "T778"}| Operator | Purpose | Requires Value |
|---|---|---|
? |
Query (GET only) | No |
= |
Set specific value | Yes |
+ |
Increment / Toggle on | No |
- |
Decrement / Toggle off | No |
| HTTP Status | Error | Description |
|---|---|---|
| 400 | missing-operator | POST body missing operator field |
| 400 | invalid-operator | Operator not valid for this command |
| 400 | missing-value | Operator = requires a value field |
| 404 | not-found | Command not supported by device |
| 503 | connection-error | Cannot communicate with device |
| 504 | timeout | Device did not respond |
Edit config.edn to configure your devices:
{:nad-devices [{:name "nad-t778"
:host "10.9.4.12"
:port 23}
;; Add more devices as needed
{:name "nad-living-room"
:host "10.9.4.13"
:port 23}]
:http {:port 8002
:ip "0.0.0.0"}}Each device gets its own API namespace at /api/{device-name}/.
| Key | Required | Default | Description |
|---|---|---|---|
:name |
Yes | - | Device name used in API URLs (e.g., nad-t778) |
:host |
Yes | - | IP address or hostname of the NAD receiver |
:port |
No | 23 | Telnet port (default 23) |
:timeout-ms |
No | 2000 | Connection and read timeout in milliseconds |
| Key | Default | Description |
|---|---|---|
:port |
8002 | HTTP server port |
:ip |
"0.0.0.0" | IP address to bind to |
Additional options from http-kit's run-server are also supported.
Build a native image for fast startup:
# Using Nix
nix build .#packages.x86_64-linux.default
# Or using Babashka
bb buildThe binary is output to ./result/bin/nad-api.
# Run the native image
./result/bin/nad-api
# Or start from source
clj -M -m ol.nad-api
# Or from the REPL
(require '[ol.nad-api :as api])
(api/start)Copyright © 2025 Casey Link unnamedrambler@gmail.com
Distributed under the EUPL-1.2.