The external machine API lets outside systems (automation, dashboards, CI) monitor and power-control machines managed by OpenHomeLab — status/stats, start (Wake-on-LAN), stop and restart. It is fully independent from the UI: no master password, no JWT, only scoped ncapi-* tokens.
- Base URL: your server root (Docker compose default host port:
http://localhost:1214) - Auth header:
Authorization: Bearer ncapi-<token>on every request - Content type: JSON (no request body is needed for the machine endpoints)
Tokens are created by an authenticated UI user (master password → JWT). There is no self-service endpoint on the machine API itself.
# 1. Log in with the master password (rate-limited: 5 attempts / 15 min)
curl -s -X POST http://localhost:1214/api/login \
-H 'Content-Type: application/json' \
-d '{"password":"<master-password>"}'
# → { "token": "<jwt>" }
# 2. Create an API token (permissions ⊆ status|start|stop|restart)
curl -s -X POST http://localhost:1214/api/api-tokens \
-H "Authorization: Bearer <jwt>" \
-H 'Content-Type: application/json' \
-d '{
"label": "home-automation",
"permissions": ["status", "restart"],
"device_ids": [1, 3]
}'
# → { "id": 4, "token": "ncapi-…48 hex chars…" }device_ids: [](or omitted) = the token works on all machines; a non-empty list scopes it to those device ids only.- Optional fields:
enabled(defaulttrue),expires_at(epoch ms in the future, ornull= never). - The plaintext token is returned once, at creation/rotation time. It is stored as a SHA-256 hash — treat it like a password.
Token management endpoints (all require the UI JWT):
| Method | Route | Description |
|---|---|---|
| GET | /api/api-tokens |
List tokens (hash, never plaintext) |
| POST | /api/api-tokens |
Create; returns { id, token } |
| PUT | /api/api-tokens/:id |
Update label/permissions/scope/enabled/expiry |
| DELETE | /api/api-tokens/:id |
Delete token + its usage history |
| POST | /api/api-tokens/:id/rotate |
New plaintext token, same id; returns { id, token } |
| GET | /api/api-tokens/stats?range=7d|30d |
Usage counts per token/operation |
Each token carries a subset of: status, start, stop, restart.
| Permission | Grants |
|---|---|
status |
GET /api/machines, GET /api/machines/:id/status |
start |
POST /api/machines/:id/start (Wake-on-LAN) |
stop |
POST /api/machines/:id/stop (shutdown) |
restart |
POST /api/machines/:id/restart |
A missing permission → 403 API token is not allowed to <operation>. A machine outside the token's device_ids scope → 403 Machine is not allowed for this API token.
:id in all paths accepts either the numeric device id or the device name.
Permission: status. Returns one entry per machine in scope, each probed with a real ICMP ping at request time.
curl -s http://localhost:1214/api/machines \
-H "Authorization: Bearer ncapi-…"{
"machines": [
{
"id": 1,
"name": "office-pc",
"ip": "192.168.1.50",
"online": true,
"latency_ms": 2.3,
"agent_online": true,
"last_agent_seen": 1758288000000
},
{
"id": 2,
"name": "nas",
"ip": "192.168.1.42",
"online": true,
"latency_ms": 4.1,
"agent_online": false,
"last_agent_seen": null
}
]
}Permission: status. Same object shape as one list entry (flat, no wrapper):
curl -s http://localhost:1214/api/machines/office-pc/status \
-H "Authorization: Bearer ncapi-…"
# → { "id": 1, "name": "office-pc", "ip": "…", "online": true,
# "latency_ms": 2.3, "agent_online": true, "last_agent_seen": 1758288000000 }Field semantics:
| Field | Meaning |
|---|---|
online / latency_ms |
Live ICMP probe (1 s timeout) executed on your request. latency_ms is null when the host doesn't answer |
agent_online |
Push agent token installed and last metrics push < 90 s old |
last_agent_seen |
Epoch milliseconds of the last agent push, null if never |
Permission: start. Sends WOL magic packets to every known broadcast address. No effect on a machine that is already on; requires a MAC address configured and a network path to the broadcast.
curl -s -X POST http://localhost:1214/api/machines/office-pc/start \
-H "Authorization: Bearer ncapi-…"
# → 200 { "success": true, "broadcastAddresses": 3 }Permission: stop.
Permission: restart.
Stop and restart are hybrid:
- If the machine has an installed agent that pushed metrics < 90 s ago → the action is queued for the agent to execute on the target machine →
{ "success": true, "channel": "agent" }. - Otherwise it falls back to SSH using the machine's assigned profile (
shutdown_command/reboot_command, defaulting toshutdown -h now/reboot) →{ "success": true, "channel": "ssh" }.
curl -s -X POST http://localhost:1214/api/machines/office-pc/restart \
-H "Authorization: Bearer ncapi-…"
# → 200 { "success": true, "channel": "agent" } (or "channel": "ssh")Notes:
200means the action was accepted (queued for the agent) or the SSH command returned exit code 0 — not that the machine has finished rebooting. Poll status afterwards if you need confirmation.- Agent-delivered actions are at-most-once: a power command is never re-sent; if the agent dies mid-execution you must call the endpoint again.
All errors are JSON: { "error": "<message>" }.
| Status | Message | Cause |
|---|---|---|
401 |
Missing API token |
No/empty Authorization: Bearer header |
401 |
Unknown API token |
Token not recognized |
401 |
API token is disabled |
Token enabled: false |
401 |
API token has expired |
expires_at in the past |
403 |
API token is not allowed to <operation> |
Permission missing (e.g. …to start) |
403 |
Machine is not allowed for this API token |
Machine outside the token's device_ids scope |
404 |
Machine not found |
:id matches no device id or name |
400 |
MAC address not found |
start on a machine without a MAC |
400 |
No SSH profile assigned |
stop/restart: no agent, and the machine has no SSH profile |
429 |
Too many requests |
Rate limit: 60 requests / minute per IP across all machine endpoints |
500 |
Failed to send WOL packet |
All WOL broadcasts failed |
500 |
<ssh error message> |
SSH connection/execution failure (stop/restart) |
- Store the token as a secret; rotate with
POST /api/api-tokens/:id/rotateif it leaks (the old token stops working immediately). - Grant only the permissions you need and scope
device_idsto the machines you control. - Handle
429with backoff; don't poll status faster than ~1 rps. - After
stop/restart, verify withGET /api/machines/:id/status— expectonline: falseduring the reboot window. - For reliable power control, install the push agent on the machine (
GET /api/agent/install); without it (or when stale > 90 s) actions depend on an SSH profile.