Skip to content

Manage

API reference

Call FengSui from scripts, dashboards and home automation with a token: how to authenticate, the endpoints and what they return, live events and Prometheus metrics.

FengSui's web console runs on this REST API. Scripts, dashboards and home automation can call the same endpoints with a token: read the signal and its history, refresh now, lock bands, send and read SMS, or follow live events.

For AI agents, use MCP instead: it adds switches, confirmations and tighter limits on top of this API.

Get a token

Create a token in the console under Settings → Tokens & AI. It is shown once, so copy it right away. You can give it a lifetime (1 to 365 days, or none) and revoke it at any time (see Security and privacy).

Every token can read the router's data and have FengSui refresh it now. Anything else needs a permission, ticked when you create the token or later with "Permissions" on an existing one under Settings → Tokens & AI:

PermissionAllows
None tickedRead the signal, history, nearby cells, connected devices, router settings, alerts and the audit log; refresh them now
Read SMS and codesRead threads, contacts and Recently deleted, sync and preview messages; SMS events in the live stream
Change the routerBand and cell locks, unlocking, UPnP / DMZ / firewall, Wi-Fi 7, the antenna, mobile data, redial, restart, resetting the breaker
Manage devices' internet accessBlock or unblock a device's Wi-Fi, the Wi-Fi MAC filter, internet schedules
Send and delete SMS, edit contactsSend, mark read, delete and restore messages; edit and import contacts

Note

The console's switches such as "Let agents change the router" apply to MCP only. The REST API goes by the token's permissions.

No token can change the console's settings (the sign-in password, tokens, the router connection, the switches) or delete messages in Recently deleted for good; only a signed-in console can (403 CONSOLE_ONLY). The endpoints the console uses for itself (/api/auth/*, /api/setup/*, /api/console/*) are closed to tokens and not listed below.

Make a request

Put the token in the Authorization header:

# Read
curl -H "Authorization: Bearer <token>" http://<NAS IP>:18384/api/telemetry

# Change something: POST with a JSON body
curl -X POST \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{"mode":"auto"}' \
  http://<NAS IP>:18384/api/control/antenna
  • Every POST needs Content-Type: application/json; send {} when there are no parameters. A body may be up to 64 KiB (4 MiB for a contacts import).
  • Reading never signs in to the router. A GET answers from FengSui's latest data in memory, so you can call it often. By default FengSui reads the signal every 5 seconds (POLL_INTERVAL_MS) and scans nearby cells and connected devices every 60 seconds (NEIGHBOR_SCAN_INTERVAL_MS); see Configuration.
  • Refreshing and changing sign in to the router. The router allows one admin session at a time, so FengSui queues every request and runs them one by one. Do not call the refresh endpoints in a tight loop.
  • FengSui steps aside while someone is signed in to the router's own page: a refresh or change then answers 409 BROWSER_SESSION_YIELDED and does nothing.
  • When you use a host name (behind a reverse proxy, say), add it to ALLOWED_HOSTS, or requests answer 421 MISDIRECTED_HOST (see Security and privacy).
  • Token requests need no X-Requested-With. Only a browser calling with the console's sign-in cookie needs it, as the cross-site request forgery (CSRF) check.
  • Identifiers are masked by default. With a token, the IMEI keeps its first 6 digits, the IMSI its operator code, numbers, MACs and WAN addresses only a part, and cell identities and device names are hidden (REDACT_SENSITIVE_IN_API=true). The signed-in console sees the real values.

Answers and errors

A success answers 200 with ok: true and the data in fields next to it. A band lock, for example, answers (shortened):

{ "ok": true, "lockState": { "mode": "band_lock", "lteBands": [1, 3], "nrBands": [] } }

A failure answers with its HTTP status and ok: false:

{ "ok": false, "errorCode": "VALIDATION_ERROR", "message": "…" }

Have scripts go by errorCode, never by message: the message may be in Chinese or English, and its wording changes. The error codes a script can meet:

StatuserrorCodeMeaning
400 / 413 / 415BAD_PAYLOADThe body is not a JSON object, is too large, or lacks Content-Type: application/json
400VALIDATION_ERRORA parameter is wrong: a band the router does not support, a missing confirm: true
401UNAUTHORIZEDNo token, or a wrong, expired or revoked one
403READ_ONLY_TOKENThis token may change nothing
403TOKEN_NOT_PERMITTEDThe token lacks the permission; permission names it
403CONSOLE_ONLYOnly a signed-in console may do this
404NOT_FOUNDNo such endpoint (also /metrics while it is off)
404SMS_DISABLEDSMS is turned off (SMS_ENABLED=false)
405METHOD_NOT_ALLOWEDWrong method, such as GET where POST is needed
409BROWSER_SESSION_YIELDEDSomeone is signed in to the router's own page; FengSui stepped aside and did nothing
409PEER_DAEMON_ACTIVEFengSui on another machine manages this router; this one stands by
409CPE_NOT_CONFIGUREDNo router connection yet: finish the setup in the console
409DEVICE_UNVERIFIED_READ_ONLYFengSui does not implement this write on this router; nothing was sent
409DEVICE_WRITE_DISABLEDThis write is turned off under Settings → Device; nothing was sent
409DEVICE_NOT_IDENTIFIEDThe router's model is not known yet; nothing was written
421MISDIRECTED_HOSTThe host name you used is not allowed; add it to ALLOWED_HOSTS
423CIRCUIT_BREAKER_OPENThe router password is wrong or its account is locked, so FengSui stopped signing in; fix it under Settings → Router
429RATE_LIMITEDToo many failed sign-ins; try again after retryAfterSec seconds
429SSE_LIMIT_REACHEDToo many live event connections
500INTERNAL_ERRORSomething went wrong inside FengSui
502CPE_UPSTREAM_ERRORThe router did not answer, or answered with an error
502SMS_SEND_FAILEDThe router took the message but reports that sending failed
503QUEUE_FULLToo many requests queued, or FengSui is stopping

Endpoints

The Permission column names what a token needs beyond reading. The request and answer fields of each endpoint are in the developer notes' API contract (in Chinese).

Reading

MethodPathPermissionWhat it does
GET/api/telemetryAny tokenThe latest signal, carriers, rates, traffic and WAN state, with the lock state and recent alerts
GET/api/timeseriesAny tokenHistory: rates, RSRP and SINR. ?preset=1h (or 6h, 12h, 24h), or ?preset=custom&fromMs=…&toMs=…
GET/api/neighborsAny tokenNearby cells, from the last scan
GET/api/topologyAny tokenConnected devices
GET/api/settingsAny tokenRouter settings: UPnP, DMZ, firewall, Wi-Fi 7, antenna, mobile data
GET/api/access-controlAny tokenThe Wi-Fi MAC filter and internet schedules
GET/api/session/statusAny tokenHow FengSui stands with the router: yielding, breaker, what self-healing is doing
GET/api/device/profileAny tokenThe identified model, firmware and capabilities, and its row of Supported devices (no identifiers)
GET/api/alertsAny tokenThe last 100 alerts, and delivery counts per notification channel
GET/api/auditAny tokenThe audit log: who changed what, and when (?limit=, up to 500)

Refreshing

MethodPathPermissionWhat it does
POST/api/telemetry/refreshAny tokenRead the signal now
POST/api/neighbors/scanAny tokenScan for nearby cells now
POST/api/topology/refreshAny tokenRead the connected devices now
POST/api/settings/refreshAny tokenRead the router settings now
POST/api/access-control/refreshAny tokenRead the MAC filter and schedules now

Router controls

MethodPathPermissionWhat it doesExample body
POST/api/control/band-lockChange the routerLock bands (lteBands, nrBands, "all" for every band; the type you leave out stays as it is){"lteBands":["B1","B3"]}
POST/api/control/cell-lockChange the routerLock a cell; without pci, lock the channel only{"rat":"NR","band":"N78","arfcn":650000,"pci":211}
POST/api/control/unlockChange the routerClear band and cell locks
POST/api/control/rebootChange the routerRestart the router{"confirm":true}
POST/api/control/reconnectChange the routerRedial the mobile connection (no restart){"confirm":true}
POST/api/control/securityChange the routerUPnP, DMZ and firewall (upnpEnabled, dmz, firewall; send only what changes){"upnpEnabled":false}
POST/api/control/wifi7Change the routerWi-Fi 7 multi-link (mloEnable) and dual-band steering (dbhoEnable){"mloEnable":true}
POST/api/control/antennaChange the routerAntenna: auto, internal, external{"mode":"auto"}
POST/api/control/mobile-dataChange the routerTurn mobile data on or off{"enabled":true}
POST/api/session/reset-breakerChange the routerReset a breaker the router's account lock (108007) tripped, and reload .env; for a wrong password, correct it under Settings → Router

Devices' internet access

MethodPathPermissionWhat it doesExample body
POST/api/control/wifi-accessManage devices' internet accessBlock or unblock one device's Wi-Fi (block, unblock){"action":"unblock","macAddress":"02:00:5E:10:00:01","confirm":true}
POST/api/control/wifi-mac-filterManage devices' internet accessThe Wi-Fi MAC filter: off, deny, allow, or add and remove entries{"mode":"off","confirm":true}
POST/api/control/time-ruleManage devices' internet accessInternet schedules: create, update, delete{"action":"delete","id":"1","confirm":true}

SMS

MethodPathPermissionWhat it doesExample body
GET/api/sms/statusRead SMS and codesMessage counts and sync state
GET/api/sms/countriesRead SMS and codesCountry and region calling codes
GET/api/sms/threadsRead SMS and codesThreads (?filter=all, unread, otp, and &search=)
GET/api/sms/threadRead SMS and codesEvery message of a thread (?key= a number or thread key)
GET/api/sms/contactsRead SMS and codesContacts (?search=)
GET/api/sms/trashRead SMS and codesMessages in Recently deleted
POST/api/sms/previewRead SMS and codesPreview a message: segments and encoding; nothing is sent{"phone":"+353830000001","content":"Hello"}
POST/api/sms/syncRead SMS and codesSync messages now (full or incremental){"mode":"incremental"}
POST/api/sms/sendSend and delete SMS, edit contactsSend a message (phone, or up to 5 phones); sent once, never retried{"phone":"+353830000001","content":"Hello"}
POST/api/sms/readSend and delete SMS, edit contactsMark read (uids or threadKey){"threadKey":"+353830000001"}
POST/api/sms/deleteSend and delete SMS, edit contactsDelete messages into Recently deleted{"uids":["<uid>"]}
POST/api/sms/trash/restoreSend and delete SMS, edit contactsPut messages back from Recently deleted{"uids":["<uid>"]}
POST/api/sms/trash/purgeConsole onlyDelete messages in Recently deleted for good
POST/api/sms/contacts/upsertSend and delete SMS, edit contactsAdd or edit a contact{"phone":"+353830000001","name":"Example"}
POST/api/sms/contacts/deleteSend and delete SMS, edit contactsDelete a contact{"id":"<id>"}
POST/api/sms/contacts/import-vcfSend and delete SMS, edit contactsImport vCards (the text of a .vcf file){"vcf":"BEGIN:VCARD…"}

Other

MethodPathPermissionWhat it does
GET/api/healthNo tokenLiveness check: version and uptime
GET/api/eventsAny tokenThe live event stream (see Live events)
GET/metricsAny tokenPrometheus metrics (when turned on, see Prometheus metrics)

The endpoints that change the router follow the console's rules: on a router that was not verified, a write FengSui does not implement answers 409 DEVICE_UNVERIFIED_READ_ONLY, and one you turned off under Settings → Device answers 409 DEVICE_WRITE_DISABLED; neither sends the router anything (see Other models and brands).

Live events

GET /api/events is a Server-Sent Events stream. It opens with a hello (the current state) and then sends whatever changes:

curl -N -H "Authorization: Bearer <token>" http://<NAS IP>:18384/api/events

Each message has an event: (the name) and a data: line of JSON. Every 15 seconds a heartbeat line starting with : keeps the connection open (SSE_HEARTBEAT_MS); clients ignore it. After a reconnect you get a new hello; FengSui does not replay what was sent while you were away.

EventWhen it is sent
helloFirst, on connecting: the current state, signal, locks and SMS counts
speedEach signal read (every 5 seconds by default): download and upload rates
telemetryRight after speed: the full signal and state, as telemetry in GET /api/telemetry
neighborsA scan for nearby cells finished (every 60 seconds by default)
topologyThe connected devices were read (every 60 seconds by default)
settingsThe router settings were read or changed (never on a timer)
access-controlThe MAC filter and schedules were read or changed
session:avoidanceSomeone signed in to or out of the router's own page, so FengSui starts or stops stepping aside
control:appliedA write finished (from anywhere: the console, a token, an agent, self-healing)
alertA new alert
sms:newA new message arrived (one event each) (needs "Read SMS and codes")
sms:sentA message was sent (from anywhere) (needs "Read SMS and codes")
sms:updatedMessages were marked read, deleted, restored or deleted for good (needs "Read SMS and codes")
sms:syncAn SMS sync finished (needs "Read SMS and codes")
sms:statusThe SMS counts or sync state changed (needs "Read SMS and codes")
contacts:updatedThe contacts changed (needs "Read SMS and codes")
  • Connections are limited: SSE_MAX_CLIENTS in all (32 by default) and 6 per address; past that, 429 SSE_LIMIT_REACHED.
  • When a token is revoked or expires, the streams it opened close at once.

Prometheus metrics

Turn them on in the console under Settings → Security (or set METRICS_ENABLED=true in .env), and GET /metrics answers in the Prometheus text format: the signal (RSRP, RSRQ, SINR), carriers and bandwidth, rates, traffic, the WAN state, SMS counts, sessions and the queue, and FengSui's own memory, CPU and event-loop delay. The metrics carry no device identifiers. While they are off, /metrics answers 404.

Any token can read the metrics; no permission is needed. A Prometheus scrape configuration:

scrape_configs:
  - job_name: fengsui
    metrics_path: /metrics
    authorization:
      credentials: <token>
    static_configs:
      - targets: ["<NAS IP>:18384"]

Liveness check

GET /api/health needs no token and answers with the version and uptime, for uptime monitors. The Docker image's own health check uses it too.

Edit this page on GitHub

Esc

Popular pages

Quick startGet startedInstallation and upgradeGet startedConfiguration referenceManageLock bands and cellsUse FengSuiSMSUse FengSuiTroubleshootingHelp