Skip to content

Device state

GET /api/v1/device is the read-only status snapshot of AWTRIX: firmware version, identity, network, memory, display state, sensors, battery and the indicator slots. It is the endpoint the web UI dashboard polls, and the one to poll from an automation.

Every value is live: nothing here is persisted, there is no caching and no ETag, and everything resets on reboot.

Endpoint

Method GET only
Path /api/v1/device
Request body none
Response 200 application/json - a single object
Auth HTTP Basic, whenever authEnabled is set - in AP mode too. See Authentication
curl http://<awtrix-ip>/api/v1/device

With auth enabled:

curl -u admin:secret http://<awtrix-ip>/api/v1/device

Any method other than GET on this path is rejected with 405 methodNotAllowed:

curl -i -X POST http://<awtrix-ip>/api/v1/device
# HTTP/1.1 405 Method Not Allowed
# {"error":{"code":"methodNotAllowed","message":"allowed method(s): GET"}}

Response shape

Keys are emitted in a fixed order. 21 fields are always present; up to 11 more appear only when the hardware supports them.

{
  "version": "1.0.12",
  "uid": "a4cf12ab34cd",
  "boardType": "awtrixng",
  "soc": "esp32",
  "ipAddress": "192.168.1.42",
  "hostname": "awtrixng-ab34cd",
  "wifiRssi": -58,
  "uptimeSeconds": 4213,
  "freeHeapBytes": 118234,
  "minFreeHeapBytes": 91560,
  "largestFreeBlockBytes": 63488,
  "scriptingRunning": true,
  "scriptHeapPool": "internal",
  "scriptHeapBudgetBytes": 98304,
  "resetReason": "software",
  "fps": 41,
  "brightness": 120,
  "lightLevel": 29.3,
  "ldrRaw": 1200,
  "batteryPercent": 88,
  "batteryVoltage": 4.1,
  "batteryPinMillivolts": 2290,
  "lowBattery": false,
  "temperature": 21.5,
  "humidity": 42,
  "pressureHpa": 1013.2,
  "matrixPower": true,
  "currentApp": "Time",
  "indicators": [
    {"on": false, "color": "#000000", "blinkMs": 0, "fadeMs": 0},
    {"on": false, "color": "#000000", "blinkMs": 0, "fadeMs": 0},
    {"on": false, "color": "#000000", "blinkMs": 0, "fadeMs": 0}
  ],
  "messageCount": 0,
  "wifi": {
    "enabled": true,
    "state": "connected",
    "host": "MyNetwork",
    "endpoint": "192.168.1.31",
    "attempts": 0,
    "retryInMs": 0,
    "connects": 1,
    "error": null,
    "lastError": null
  },
  "mqtt": {
    "enabled": true,
    "state": "connected",
    "host": "broker.local",
    "endpoint": "192.168.1.42:1883",
    "attempts": 0,
    "retryInMs": 0,
    "connects": 1,
    "error": null,
    "lastError": null
  }
}

Do not assume a field exists

The conditional fields - psramTotalBytes, psramFreeBytes, lightLevel, ldrRaw, batteryPercent, batteryVoltage, batteryPinMillivolts, lowBattery, temperature, humidity and pressureHpa - are omitted entirely, not null and not zero, when the hardware is not there. Check for the key before you read it.

Always-present fields

These 22 keys are in every response, on every board, in every state.

Key Type Range / format Units Meaning
version string - - Running firmware version. Same value as GET /api/v1/version.
uid string 12 lowercase hex chars - Device identity: the WiFi MAC address, lowercased, colons stripped. Stable across reboots and reflashes. Also the default MQTT topic prefix and MQTT client id.
boardType string constant "awtrixng" - A fixed constant in the device firmware - it does not vary with your GPIO configuration. The simulator reports "simulator" instead.
soc string esp32, esp32s3 - The chip this image was built for. Branch on this only to tell the two firmware images apart; for pin rules read gpio in GET /api/v1/capabilities.
ipAddress string dotted quad - The station-mode IP address. In AP (provisioning) mode this is not the address you reached AWTRIX on.
hostname string 1 … 32 chars - The name AWTRIX answers to on the network and publishes over mDNS. Read the configured value from GET /api/v1/system; that one is empty when the name is derived from the MAC (awtrixng- plus the last six hex digits of uid), which is why the two fields disagree on a device that was never named by hand.
wifiRssi integer typically −30 (excellent) to −90 (unusable) dBm Current signal strength of the station connection.
uptimeSeconds integer 0 … seconds Whole seconds since boot. Resets on every reboot, including the reboot after an OTA update or a settings reset.
resetReason string poweron, external, software, panic, interruptWatchdog, taskWatchdog, watchdog, deepSleep, brownout, sdio, unknown - Why AWTRIX last came up. Constant for the whole session.
freeHeapBytes integer 0 … bytes Free internal heap right now. Useful as a leak canary; it fluctuates constantly with rendering and networking.
minFreeHeapBytes integer 0 … bytes The low-water mark: the least free heap seen since boot. Unlike freeHeapBytes it only ever falls, so it survives the spike you were not polling during. A value creeping towards zero is a leak; a stable one is not. Resets on reboot.
largestFreeBlockBytes integer 0 … bytes The largest single contiguous block free right now, in the same internal pool as freeHeapBytes. Always ≤ freeHeapBytes, and the gap between them is fragmentation. Installing a script or opening an HTTPS stream needs its memory in one piece, so plenty of total free spread over small blocks is still refused. Watch this, not freeHeapBytes, to understand a "not enough memory" refusal.
scriptingRunning boolean - - Whether scripts are running at all. false when scriptingEnabled is off - installed scripts stay listed and editable, but none of them executes.
scriptHeapPool string internal, psram - Which pool the Berry VM allocates from. Where PSRAM is usable the script heap lives there, so installing a script barely moves freeHeapBytes.
scriptHeapBudgetBytes integer 0 … bytes How large the shared Berry heap may grow before further installs are refused. 98304 on internal RAM; on PSRAM about half the free pool, so roughly 4 MB on an 8 MB module. Read it back rather than assuming.
fps integer 0 … frames/second Measured render rate, not a target: the frames actually shown, recounted once per second. Expect it to move around.
brightness integer 0 … 255 - The effective panel brightness currently driving the LEDs, not an echo of settings.brightness. With autoBrightness off it is settings.brightness clamped to 0…255 and the two agree. With autoBrightness on it is derived from lightLevel along the gamma curve and mapped into the minBrightnessmaxBrightness window, and settings.brightness is ignored. Mirrored as brightness in GET /api/v1/display; read GET /api/v1/settings for the configured value.
matrixPower boolean - - true when the panel is on. Mirrors power in GET /api/v1/display. Set it via PATCH /api/v1/display.
currentApp string app name, or "" - The app currently selected in the rotation, e.g. "Time", "Date", or one of your own apps' names. Empty string when the rotation holds no apps. A notification covering the panel does not change it, and during a transition it names the app being left until the transition commits.
indicators array exactly 3 objects - State of indicator slots 1, 2 and 3, in that order. See Indicators.
messageCount integer 0 … count Inbound MQTT command messages since boot - anything arriving under the topic prefix of your AWTRIX, including a script's own subscription if that topic sits under the prefix. The /result messages AWTRIX publishes back are not counted, and HTTP requests are never counted. Resets to 0 on reboot.
wifi object - - Whether AWTRIX is on your network, and if not, why. See Connection status.
mqtt object - - Whether AWTRIX is talking to your broker, and if not, why. See Connection status.

PSRAM fields (conditional)

Present only when the board has external PSRAM. Both fields appear together, or not at all.

Key Type Range Units Meaning
psramTotalBytes integer 0 … bytes External PSRAM on the module.
psramFreeBytes integer 0 … bytes Free PSRAM. Do not add this to freeHeapBytes: the two heaps are not interchangeable, and it is the internal one that runs out first.

Light sensor fields (conditional)

Present only when the board has a light sensor pin - that is, when pinLdr in the system configuration is >= 0. The default (ESP32 wiring) is GPIO 35, so on a stock device both fields are present. Set pinLdr to -1 and both disappear from the response.

Key Type Range Units Meaning
lightLevel number 0.0 … 100.0, one decimal percent (relative) Ambient light as a relative percentage, not lux.
ldrRaw integer 0 … 4095 - The raw light-sensor reading behind lightLevel: 0 in the dark, 4095 in bright light. This is the number to use when calibrating ldrFactor.

lightLevel rises linearly with ldrRaw, so calibrate it there: ldrFactor sets what counts as full light, and ldrOnGround accounts for a sensor wired the other way round. ldrGamma shapes only the step from lightLevel to brightness; it never changes lightLevel, which is reported whether or not autoBrightness is switched on.

Battery fields (conditional)

Present only when the board has a battery pin - that is, when pinBattery in the system configuration is >= 0. The default (ESP32 wiring) is GPIO 34, so on a stock device these four fields are present. Set pinBattery to -1 and all four disappear from the response.

The first three come from the same reading: pin millivolts → cell volts → percent; lowBattery is a flag derived from that percent.

Key Type Range Units Meaning
batteryPinMillivolts integer 0 … 65535 mV Voltage measured at the ESP32 pin, before the resistor divider - not the battery voltage. Median-filtered over the last 5 samples. This is the raw input to the divider maths, and the number you calibrate batteryDividerRatio against.
batteryVoltage number 0.0 … , two decimals V The reconstructed cell voltage: batteryPinMillivolts / 1000 × batteryDividerRatio. A ratio <= 0 falls back to the built-in default of 1.79. This value lets you track a cell's ageing directly, independent of the percentage.
batteryPercent integer 0 … 100 percent State of charge, read off a resting Li-Ion discharge curve using batteryVoltage. Voltages between two points on the curve are blended; anything past either end stops at 100 % or 0 %. 100 % at ≥ 4.20 V, 0 % at ≤ 3.27 V.
lowBattery boolean - - true when batteryPercent has fallen below the lowBatteryThreshold in the system configuration. Always false when lowBatteryThreshold is 0 (the default), which disables the check. Also shown as a Home Assistant "Low battery" binary sensor.

Calibrate the divider, not the percentage. On a stock Ulanzi TC001 a full cell reads roughly 2347 mV at the pin, which the default ratio of 1.79 turns into ≈ 4.20 V at the cell. If your batteryVoltage looks wrong, read batteryPinMillivolts on a known-full cell and set batteryDividerRatio = 4.2 / (batteryPinMillivolts / 1000). See Power & battery.

batteryPercent is a charge estimate from voltage alone. There is no fuel gauge in the hardware, so it cannot give you a runtime estimate, and it will move under load as the cell sags.

Environment fields (conditional)

Present only when an I²C environment sensor was detected at boot. Detection happens once during startup, in probe order: BME280, then BMP280, then HTU21DF, then SHT31. If none answers - or if pinI2cSda / pinI2cScl is -1, which disables I²C entirely - all three keys are omitted, and plugging a sensor in later does nothing until you reboot.

Which keys appear is gated by what the detected sensor can measure. A field is never emitted with a phantom 0.

Sensor temperature humidity pressureHpa
BME280
BMP280 -
HTU21DF -
SHT31 -
Key Type Range Units Meaning
temperature number sensor-dependent, one decimal °C Sensor temperature plus the configured tempOffset. Always Celsius here, regardless of the useCelsius setting - that setting only affects what the panel draws.
humidity number sensor-dependent, one decimal percent RH Sensor relative humidity plus the configured humOffset. Omitted on temperature-only sensors.
pressureHpa number sensor-dependent, one decimal hPa Barometric pressure. Present only on a barometer (BME280 / BMP280).

Indicators

indicators is always an array of exactly 3 objects, one per slot, index 0 → indicator 1, index 2 → indicator 3. It reflects whatever was last written via PUT /api/v1/indicators/{id} (or over MQTT / Home Assistant), and it matches what is on screen: the slots paint as pixels down the panel's right edge, slot 1 at the top and slot 3 at the bottom.

Key Type Range Units Meaning
on boolean - - Whether the slot is switched on.
color string "#RRGGBB" - The slot's colour, uppercase hex. Defaults to #000000.
blinkMs integer 0 … 65535 milliseconds Blink interval. 0 = solid.
fadeMs integer 0 … 65535 milliseconds Fade interval. 0 = no fade.

Connection dots

The right edge belongs to your indicators. The left edge belongs to AWTRIX, which lights a single pulsing pixel there while a connection it should have is missing:

Where Colour Meaning
Top-left corner red AWTRIX is not on your WiFi network.
Bottom-left corner yellow AWTRIX is on the network but not talking to your MQTT broker.

The dots appear on their own, cannot be switched off, and disappear the moment the connection is back.

Only one lights at a time. Without WiFi there is no MQTT either, so a network outage shows the red dot alone - a lit dot always points at one thing to fix. A device with no broker configured never shows the yellow dot at all.

You will not see the dots while the panel is off, on the setup screen, in mood light, or while an Art-Net stream is running. For the reason behind an outage, read wifi and mqtt below.

Connection status

wifi and mqtt report whether AWTRIX reached your network and your broker, and if not, why. Both objects have the same shape, and mqtt drives the Connection line on the web UI's MQTT tab. AWTRIX also shows an outage on the panel itself - see Connection dots.

"mqtt": {
  "enabled": true,
  "state": "offline",
  "host": "broker.local",
  "endpoint": "",
  "attempts": 4,
  "retryInMs": 40000,
  "connects": 0,
  "error": "hostNotFound",
  "lastError": "hostNotFound"
}
Key Type Meaning for wifi Meaning for mqtt
enabled boolean A network name is stored. Mirrors mqttEnabled.
state string disabled, offline, connecting or connected. Same four values.
host string The network name (SSID) you joined. The broker host you configured, as entered.
endpoint string The IP address AWTRIX holds on that network. Empty while not joined. The address and port actually in use, once the host has been resolved. Empty before that - so an empty endpoint on a named broker means the name has not resolved yet.
attempts integer Consecutive failed attempts. 0 while connected. Same.
retryInMs integer Milliseconds until the next attempt. 0 while connected, and while an attempt is running. Same.
connects integer Successful connections since boot. A number that keeps climbing is a link that keeps dropping - for wifi, usually a router or a range problem. Same.
error string / null Why it is not up right now. null when it is. Same.
lastError string / null The last reason this link went down, kept after it recovers. null until something goes wrong. Same.

Why wifi has a lastError

While WiFi is down nothing can reach this API to ask why - the panel's red dot is the only live signal you get. lastError is what you read afterwards: it survives the reconnect, so "lastError": "lost" on a device reporting "state": "connected" means the link dropped and came back while you were not looking. Together with connects, that is the whole picture of an outage you missed.

The one case where wifi.error is readable live is a device that never got onto your network at boot: it falls back to its own setup network, and the API is reachable there.

What each error means

error What it means What to do
noWifi (mqtt only) AWTRIX is not on the network. Fix the WiFi first; MQTT cannot be reached without it. You will only ever see this value on a device you reached some other way - if the WiFi is down, so is this API.
hostNotFound For wifi: the network you configured was not on the air. For mqtt: the broker name did not resolve. For WiFi, check the network name and that the router is up. For MQTT, check the spelling - a .local name needs a responder answering for it on the same network, so if in doubt enter the broker's IP address instead.
refused (mqtt only) Nothing accepted a connection at that address and port. Check the port, and that the broker is running and reachable from the network AWTRIX is on.
badCredentials The password was rejected - by the router for wifi, by the broker for mqtt. Re-enter the WiFi password, or mqttUser and mqttPass.
rejected (mqtt only) The broker refused the client for some other reason. Check the broker's own log.
timeout For wifi: the network did not answer in time while AWTRIX was starting up. For mqtt: something answered at that address but never completed an MQTT handshake. For WiFi, usually range or a router that was still booting; AWTRIX keeps trying. For MQTT, usually the wrong port - something that is not a broker is listening there.
lost The connection was up and dropped. For WiFi, usually range or a router restart. For MQTT, often the same, or a broker restart. AWTRIX reconnects on its own.

Retries back off

A failed connection is retried after 5 seconds, then 10, 20, 40, and at most every 60 seconds, each delay shortened by up to 20 % so a rebooting broker does not get every device knocking in lockstep. A successful connection resets the schedule. retryInMs counts down to the next attempt.

The broker's address is looked up once and kept for as long as it works. It is looked up again after three consecutive failures against the same address, and after AWTRIX rejoins WiFi.

Also published over MQTT

The same JSON document is published, retained, to <prefix>/state/device. Because it is retained, a subscriber gets the last snapshot immediately on connect, which is cheaper than HTTP polling. See MQTT topics for the publish cadence and the rest of the state topics.

How often the values update

The values in this response are refreshed on their own schedules, not at request time. Polling faster than these intervals returns repeated values.

Field(s) Refresh interval
ldrRaw, lightLevel, brightness every 100 ms, median-filtered over 5 samples
batteryPinMillivolts, batteryVoltage, batteryPercent, lowBattery every 2 s, median-filtered over 5 samples
temperature, humidity, pressureHpa every 2 s
fps recomputed once per second
uptimeSeconds, freeHeapBytes, minFreeHeapBytes, largestFreeBlockBytes, ipAddress, wifiRssi read at request time
resetReason, hostname fixed at boot

In the simulator

The native simulator serves GET /api/v1/device with the same key set, with a few differences worth knowing if you develop against it: boardType is "simulator", and the battery fields (including lowBattery) plus temperature and humidity are always present, because the simulated board always reports a battery and a temperature+humidity sensor. The simulated sensor is not a barometer, so pressureHpa is absent in the simulator - the one environment field you cannot exercise there. Do not use the simulator to test how your client handles missing fields - point it at a device with pinBattery set to -1 instead. See Simulator.

  • HTTP API v1 - conventions, auth, and the full route list
  • System configuration - pinBattery, pinLdr, batteryDividerRatio, lowBatteryThreshold, ldrFactor, ldrGamma, minBrightness, maxBrightness, tempOffset, humOffset
  • Settings - brightness, autoBrightness and the rest of the user settings
  • Brightness & sensors - calibrating the LDR
  • Power & battery - calibrating the divider ratio
  • Errors - what a failing request answers