HTTP API

June 1, 2026 · View on GitHub

The HTTP server is always started. Control the port with --port <n> (default 8080).
Use --port 0 to let the OS assign a free port — the actual port is logged at startup:

INFO: http server listening on :8080

The built-in web UI is served from the same port as an SPA (Single-Page Application).


URL scheme

PrefixPurpose
/she/*System endpoints: scripts, MQTT, Matter, DB, config, WebSocket

Authentication

she supports three authentication modes, configured via auth in config.json (or --auth on the CLI):

ModeDescription
noneNo authentication — all /she/* endpoints are open. Default. Suitable for a private LAN.
passwordSingle-user login. A hashed password is stored in config.json. The web UI shows a login form; successful login sets an HttpOnly session cookie valid for 7 days.
proxyTrust an HTTP header set by an upstream reverse proxy (e.g. nginx + authentik). The header name defaults to X-Remote-User and is configurable via proxyHeader.

Important: Routes under /api/* (user-script endpoints) are intentionally not covered by she-level auth — scripts are responsible for their own access control on those paths.

Auth endpoints (always public)

MethodPathDescription
GET/she/auth/modeReturns { "mode": "none" | "password" | "proxy" }
POST/she/auth/login{ "password": "..." } — sets she_session cookie on success
POST/she/auth/logoutClears the session cookie
POST/she/auth/setupChange auth mode / password / proxyHeader at runtime (see Config tab in web UI)

Setting up password mode

Use the Config → Authentication section in the web UI to set a password and switch to password mode. Changes take effect immediately without a restart.

Alternatively, set the hashed password in config.json directly:

node -e "const b=require('bcryptjs');console.log(b.hashSync('my-password',10))" 

Then in config.json:

{
  "auth": "password",
  "password": "\$2a\$10$..."
}

Setting up proxy mode

Configure nginx (or another proxy) to authenticate requests and forward the username in a header. Set she to bind on 127.0.0.1 so only the proxy can reach it:

{
  "auth": "proxy",
  "proxyHeader": "X-Remote-User",
  "bindAddress": "127.0.0.1"
}

See nginx.conf for a full example with TLS and authentik forward auth.

Unauthorized response

HTTP 401
{ "error": "Unauthorized" }

Scripts — /she/scripts

GET /she/scripts

List all files in the script directory, recursively. Includes .js scripts and any other stored files (markdown, yaml, json, etc.).

Response (HTTP 200):

[
  { "path": "lights.js", "size": 512, "mtime": 1718000000000 },
  { "path": "lib/utils.js", "size": 128, "mtime": 1718000000000 }
]

GET /she/scripts/:path

Read a script file.

Response (HTTP 200):

{ "path": "lights.js", "content": "she.log('hello');" }

PUT /she/scripts/:path

Create or overwrite a file. Any file extension is accepted. The daemon only auto-loads .js files; other file types are stored for manual use.

Request body:

{ "content": "she.log('updated');" }

Response (HTTP 200):

{ "ok": true, "path": "lights.js", "size": 20, "mtime": 1718000000000 }

DELETE /she/scripts/:path

Delete a script file.

Response (HTTP 200): { "ok": true }

POST /she/scripts/:path/rename

Rename or move a script file within the script directory.

Request body:

{ "newPath": "archive/lights.js" }

Response (HTTP 200): { "ok": true, "path": "archive/lights.js" }


MQTT — /she/mqtt

GET /she/mqtt/state

Returns all known MQTT topic states, sorted alphabetically.

Response (HTTP 200):

[
  { "topic": "home/light/hall", "val": 1, "ts": 1718000000000 },
  { "topic": "home/sensor/temp", "val": 21.5, "ts": 1718000000000 }
]

POST /she/mqtt/publish

Publish a message to the MQTT broker.

Request body:

{ "topic": "home/light/hall", "payload": "1", "retain": false, "qos": 0 }

retain and qos are optional (default false and 0).

Response (HTTP 200): { "ok": true }


sheDB — /she/db

sheDB must be enabled via --db-path. All endpoints return HTTP 503 if sheDB is not initialised.

Document IDs may contain slashes (MQTT-topic style, e.g. devices/kitchen/light).

GET /she/db/docs

List all document IDs.

Response (HTTP 200): ["devices/hall/pir", "devices/kitchen/light"]

GET /she/db/docs/:id

Get a document.

Response (HTTP 200): { "name": "Hall PIR", "location": "hall" }

PUT /she/db/docs/:id

Create or overwrite a document (full replace).

Request body: any JSON object
Response (HTTP 200): { "ok": true }

PATCH /she/db/docs/:id

Deep-merge a partial update into an existing document.

Request body: partial JSON object
Response (HTTP 200): { "ok": true }

DELETE /she/db/docs/:id

Delete a document.

Response (HTTP 200): { "ok": true }

GET /she/db/views

List all view IDs.

Response (HTTP 200): ["by-location", "by-type"]

GET /she/db/views/:id

Get a view definition.

Response (HTTP 200):

{
  "filter": "devices/#",
  "map": "function(doc, emit) { emit({ name: doc.name }); }",
  "reduce": null
}

PUT /she/db/views/:id

Create or update a view.

Request body:

{
  "filter": "devices/#",
  "map": "function(doc, emit) { emit({ name: doc.name }); }"
}

Response (HTTP 200): { "ok": true }

DELETE /she/db/views/:id

Delete a view.

Response (HTTP 200): { "ok": true }

GET /she/db/views/:id/result

Execute a view and return its results.

Response (HTTP 200): [{ "name": "Hall PIR" }, { "name": "Kitchen Light" }]


Matter — /she/matter

Matter must be enabled via --matter-storage. All endpoints return HTTP 503 if the Matter controller is not initialised.

GET /she/matter/devices

List all paired Matter nodes.

Response (HTTP 200):

[
  { "nodeId": "1", "name": "Bulb A" }
]

POST /she/matter/commission

Commission a new Matter device.

Request body (manual code):

{ "passcode": 20202021, "discriminator": 3840 }

Request body (QR pairing code):

{ "pairingCode": "MT:Y.K9042C00KA0648G00" }

Response (HTTP 200): { "nodeId": "2" }

GET /she/matter/devices/:nodeId

Get details for a paired node.

Response (HTTP 200):

{ "nodeId": "1", "name": "Bulb A", "endpoints": [...] }

DELETE /she/matter/devices/:nodeId

Unpair a Matter device.

Response (HTTP 200): { "ok": true }

POST /she/matter/devices/:nodeId/command

Invoke a cluster command on a device endpoint.

Request body:

{
  "endpointId": 1,
  "clusterName": "onOff",
  "command": "toggle",
  "args": {}
}

Response (HTTP 200): { "result": null }


Config — /she/config

GET /she/config

Returns the currently active config file as JSON. Returns {} if no config file exists yet.

Response (HTTP 200):

{
  "url": "mqtt://192.168.1.10",
  "dir": "/opt/scripts",
  "name": "logic",
  "port": 8080
}

PUT /she/config

Writes a new config file. All CLI option keys are accepted (camelCase).

Request body (JSON):

{
  "url": "mqtt://192.168.1.10",
  "dir": "/opt/scripts",
  "verbosity": "debug",
  "port": 8080,
  "auth": "none"
}

Response (HTTP 200):

{ "ok": true, "restartRequired": true, "configPath": "/home/user/.she/config.json" }

A daemon restart is required for the new config to take effect.


Daemon — /she/status, /she/restart

GET /she/status

Returns a snapshot of runtime counters.

Response (HTTP 200):

{ "scripts": 3, "topics": 142 }
FieldDescription
scriptsNumber of user scripts currently loaded
topicsNumber of MQTT topics tracked in the state store

POST /she/restart

Sends a graceful shutdown signal. The process exits with code 0 so the process manager (systemd, Docker restart policy, etc.) restarts it automatically.

Response (HTTP 200):

{ "ok": true }

The connection will drop immediately after the response. The daemon is typically back within a second or two.


WebSocket — ws://host/she/ws

Connect to the WebSocket endpoint for a live stream of logs, MQTT state changes, and sheDB events.

In password mode the WebSocket connection is authenticated via the same session cookie the browser sends automatically. No extra token parameter is needed.

Messages from server

All messages are JSON.

typeFieldsDescription
pingKeepalive; no reply needed
loglevel, msg, tsStructured log line from the daemon
mqtttopic, val, tsMQTT topic state changed
db:idsidsFull list of sheDB document IDs (sent on connect and on any change)
db:changeid, docA sheDB document was created, updated, or deleted (doc is null on delete)

Example

const ws = new WebSocket('ws://localhost:8080/she/ws');
ws.onmessage = (e) => {
    const msg = JSON.parse(e.data);
    if (msg.type === 'mqtt') console.log(msg.topic, '->', msg.val);
    if (msg.type === 'log')  console.log('[' + msg.level + ']', msg.msg);
};

curl examples

# List scripts
curl http://localhost:8080/she/scripts

# Read a script
curl http://localhost:8080/she/scripts/lights.js

# Write a script
curl -X PUT -H "Content-Type: application/json" \
     -d '{"content":"she.log(\"hello\");"}' \
     http://localhost:8080/she/scripts/hello.js

# Get MQTT state
curl http://localhost:8080/she/mqtt/state

# Publish MQTT message
curl -X POST -H "Content-Type: application/json" \
     -d '{"topic":"home/light/hall","payload":"1","retain":true}' \
     http://localhost:8080/she/mqtt/publish

# List sheDB documents
curl http://localhost:8080/she/db/docs

# Read a document
curl http://localhost:8080/she/db/docs/devices/hall/pir

# List paired Matter devices
curl http://localhost:8080/she/matter/devices

# Read config (no auth / none mode)
curl http://localhost:8080/she/config

# Write config
curl -X PUT -H "Content-Type: application/json" \
     -d '{"url":"mqtt://newbroker","dir":"/opt/scripts"}' \
     http://localhost:8080/she/config

# Login and keep the session cookie
curl -c cookies.txt -X POST -H "Content-Type: application/json" \
     -d '{"password":"my-password"}' \
     http://localhost:8080/she/auth/login

# Use the session cookie for subsequent requests
curl -b cookies.txt http://localhost:8080/she/scripts