Download OpenAPI specification:
The HTTP API of an Ultimatrix LED clock (ESP32, 64x64 HUB75 panel). It is what the clock's own settings page
(/) uses, so everything the page can do is available here. Not every route exists in every build: faces
are compiled in or out (the Pac-Man, Pong, Invaders, NFL and game-controller routes are the main ones),
and the WLED routes are not in the bridge image.
Calling it
.local name. There is no TLS.application/x-www-form-urlencoded) or as query-string
parameters; both work. The WLED, canvas-frame and timer routes take a JSON body instead (marked below).1/0, or by the field being present (checkbox style); each
field says which.Access rules (all routes)
403 for clients outside the allowed network (setting accessScope) and for public host names (a name
under a real top-level domain) unless anyHost is set. Open the clock by IP or .local.413. A POST carrying an Origin header for another host gets 403
(a web page on another site cannot drive the clock). Requests without Origin (curl, Home Assistant,
the WLED app) pass.X-Token header (from POST /api/auth) and answer 401
with {"message":"Enter the clock PIN.","pin":true} without it. These stay open: /api/auth,
/api/ha/detect, /json, /json/state, /dlna/*, /api/wled*, /api/canvas/*.Verification. The routes, parameters, ranges and status codes come from reading the firmware
(firmware/src/main.cpp, lib/wled/*, lib/core/*). Nothing here was machine-checked against a running
clock except /api/dim-colour, /api/hue-test, /api/screen, /api/display and /api/settings.
Large response bodies (settings, Home Assistant configuration, sports schedule) list their fields but
not every nested detail.
Everything the page shows - current face, network, free storage, light sensor and all settings.
{- "mode": "string",
- "modeId": "string",
- "timeSynced": true,
- "lightReading": 0,
- "nightActive": true,
- "nightWhy": "string",
- "nvsFree": 0,
- "pinSet": true,
- "sunTimes": { },
- "network": {
- "ssid": "string",
- "ip": "string",
- "hostname": "string"
}, - "welcomeHidden": true,
- "welcomeMode": true,
- "settings": {
- "accessScope": 0,
- "anyHost": true,
- "mdnsUseName": true,
- "clockName": "string",
- "brightness": 1,
- "rotation": 0,
- "use24h": 0,
- "ntpServer": "string",
- "autoBrightness": true,
- "ldrPin": 32,
- "minimumBrightness": 1,
- "ldrDark": 50,
- "ldrLight": 2500,
- "nightEnabled": true,
- "nightStart": "string",
- "nightEnd": "string",
- "nightBrightness": 0,
- "nightSun": true,
- "nightClock": false,
- "timezone": "string",
- "autoTimezone": true,
- "matchSecs": 3,
- "resultMins": 0,
- "timeStyle": 0,
- "dither": true,
- "swapBlueGreen": true,
- "telnet": true,
- "hiddenWifi": false,
- "autoUpdate": true,
- "birthdays": "string",
- "sportsOrder": "string",
- "sportsHome": "string",
- "sportsLead": 0,
- "sportsOthers": true,
- "sportsRacing": true,
- "sportsFav": true,
- "sportsMax": 0,
- "sportsHold": 0,
- "sportsBridge": 0,
- "fbLeagues": "string",
- "fbTeams": "string",
- "f1Drivers": "string",
- "showRankings": true,
- "footballOthers": true,
- "ballRoll": true,
- "numberFont": 0,
- "wordLanguage": 0,
- "wordAnimation": 0,
- "wordDate": 0,
- "nbaTeams": "string",
- "nbaIdle": 0,
- "nbaTables": true,
- "nbaLiveOnly": true,
- "nbaExtra": true,
- "mlbTeams": "string",
- "mlbIdle": 0,
- "mlbTables": true,
- "mlbLiveOnly": true,
- "mlbExtra": true,
- "mlbHours": 0,
- "mlbMinutes": 0,
- "nflTeams": "string",
- "nflIdle": 0,
- "nflTables": true,
- "nflLiveOnly": true,
- "nflExtra": true,
- "nflHours": 0,
- "nflMinutes": 0,
- "tetrisStyle": 0,
- "tetrisLevel": 0,
- "pongBackdrop": 0,
- "pongSpeed": 0,
- "pongLevel": 0,
- "pongDate": 0,
- "pongDay": 0,
- "arcHours": 0,
- "arcMinutes": 0,
- "arcScore": 0,
- "color": 0,
- "dotColor": 0,
- "btPad": true,
- "pacmanClockColor": 0,
- "clockMinutesColor": 0,
- "clockShade": 0,
- "marioWorld": 0,
- "f1Flags": true,
- "f1Chequers": true
}
}Send only the fields you want to change in the same form the page uses. A field that is a checkbox
counts as on when present and off when absent, so a client that sends a partial form must send
every checkbox it wants to keep on (for example autoUpdate, nightClock). Numeric
fields are validated against the ranges below; a bad value answers 400 with a message.
| brightness | integer [ 1 .. 255 ] Panel brightness, 1 to 255 |
| minimumBrightness | integer [ 1 .. 255 ] Lowest brightness for the light sensor, 1 to 255 |
| f1Chequers | string Present: show the chequered background |
| nightClock | string Present: the red night clock may replace the face |
| property name* additional property | any |
{- "message": "string"
}Same handler as POST /api/settings.
| brightness | integer [ 1 .. 255 ] Panel brightness, 1 to 255 |
| minimumBrightness | integer [ 1 .. 255 ] Lowest brightness for the light sensor, 1 to 255 |
| f1Chequers | string Present: show the chequered background |
| nightClock | string Present: the red night clock may replace the face |
| property name* additional property | any |
{- "message": "string"
}How the driver shows very dim colours. A HUB75 panel cannot show every shade at low brightness, so
a dim colour can round to black in one channel and change hue (a dim orange turns red, a dim grey turns pink).
Three optional corrections exist: the hue guard (guard, on by default) lifts pixels that would round to
black while keeping their hue; the search (search, off by default) looks for the closest reachable colour;
and the weights (wg to bb) tell the search how strong the red, green and blue LEDs of your panel are.
The weights depend on the panel; firmware/tools/colour-calibrate.py finds them. The calibration dialog in
Settings uses floor and night. /api/hue-test shows test patches while you tune.
{- "search": 0,
- "guard": 0,
- "restore": 0,
- "brightness": 0,
- "extra": 0,
- "limit": 0,
- "guardMin": 0,
- "floor": 0,
- "night": 0,
- "nightOff": true,
- "wg": 0,
- "wb": 0,
- "gg": 0,
- "gb": 0,
- "og": 0,
- "ob": 0,
- "bg": 0,
- "bb": 0
}Send any of the fields. By default the result is stored in flash. With save=0 it applies only
until the next restart. reset=1 restores the defaults but keeps the calibrated floor and the
night level. The answer is the new state.
| search | integer Default: 0 Enum: 0 1 Off by default. 1 turns on the dim-colour search: for very dim pixels it tries nearby combinations of the red, green and blue light levels and picks the one that looks closest to the wanted colour, using the weights below to correct for how the LEDs differ in strength |
| guard | integer Default: 1 Enum: 0 1 Hue guard; lifts pixels that would round to black, keeping the hue. On by default. Only acts below the pulse-width floor (about panel brightness 137/255), on pixels whose channels all round to level 1 or less |
| restore | integer Default: 1 Enum: 0 1 After the weights have shifted a dim colour, scale it back so it is about as bright as the original. Only matters when |
| brightness | number [ 0.01 .. 9.99 ] Default: 0.06 How much the search cares about getting the brightness right compared with the hue. Higher keeps brightness, lower favours the exact colour |
| extra | integer [ 1 .. 8 ] Default: 2 How many light levels above the wanted one the search may use for a channel when it looks for a better match |
| limit | integer [ 4 .. 40 ] Default: 8 The search only runs for pixels whose brightest channel is at or below this many plane weights (the panel shows light in bit planes; a higher value covers brighter pixels) |
| guardMin | number [ 0 .. 3.9 ] Default: 0 Lowest light, in plane weights, the hue guard still lifts. 0 (default) lifts every non-black pixel; a higher value leaves very dark pixels black. Stored in 1/64 steps |
| floor | integer [ 0 .. 200 ] Default: 0 Lowest panel brightness (0-255) found by the colour calibration, where all colours still show. The Minimum brightness slider stretches over the range from here upwards. 0 = no calibration |
| night | integer [ 0 .. 255 ] Default: 31 Panel brightness (0-254) used in night mode; 31 is about 12 %. 0 = panel dark |
| wg | number [ 0.1 .. 5 ] Default: 1 Green weight for reds and ambers (red leads). Above 1 adds green to a dim red/amber/orange; 1 = neutral |
| wb | number [ 0.1 .. 5 ] Default: 1 Blue weight for reds and ambers (red leads). Above 1 adds blue; 1 = neutral |
| gg | number [ 0.1 .. 5 ] Default: 1 Green weight for greys and whites (no saturation). Blends towards |
| gb | number [ 0.1 .. 5 ] Default: 1 Blue weight for greys and whites. Blends towards |
| og | number [ 0.1 .. 5 ] Default: 1 Green weight when green leads (green, teal and their pastels); below 1 thins out the green that the red LED would otherwise dominate; 1 = neutral |
| ob | number [ 0.1 .. 5 ] Default: 1 Blue weight when green leads (green, teal and their pastels); 1 = neutral |
| bg | number [ 0.1 .. 5 ] Default: 1 Green weight when blue leads (blue, cyan and their pastels); 1 = neutral |
| bb | number [ 0.1 .. 5 ] Default: 1 Blue weight when blue leads (blue, cyan and their pastels); below 1 takes the violet out of a blue; 1 = neutral |
| save | integer Default: 1 Enum: 0 1 0 = apply until restart only |
| reset | integer Enum: 0 1 1 = restore defaults first |
{- "search": 0,
- "guard": 0,
- "restore": 0,
- "brightness": 0,
- "extra": 0,
- "limit": 0,
- "guardMin": 0,
- "floor": 0,
- "night": 0,
- "nightOff": true,
- "wg": 0,
- "wb": 0,
- "gg": 0,
- "gb": 0,
- "og": 0,
- "ob": 0,
- "bg": 0,
- "bb": 0
}Shows a grid of swatches on the panel at a chosen brightness: 8 hues x 8 columns of 100, 70, 50, 35,
25, 18, 12 and 8 % of full colour (16 hue rows with wide=1). It ends after secs (at most 600),
or at once with b=0. Optional tuning arguments change the driver live and are not stored.
Read the result back with GET /api/screen.
| b | integer [ 0 .. 255 ] Panel brightness; 0 stops the test |
| secs | integer [ 0 .. 600 ] How long |
| deep | integer Enum: 0 1 Allow the lowest brightness to narrow the output window |
| wide | integer Enum: 0 1 16 hue rows of 4 px instead of 8 of 8 px |
| ramp | integer [ 0 .. 255 ] 0 = the usual percentages; otherwise the last column is this many of 255 and each earlier one is 1.3x brighter |
| gd | number Hue guard drop share (0.1-1): at the lowest level the guard sets a channel back to 0 when it carries less than this share of the strongest channel's light. 0 = off |
| gc | number Hue guard carry share (0.1-1, default 0.5): the guard lifts a channel left at 0 when it carries at least this share of the strongest channel's light |
| gm | number Lowest light, in plane weights, the hue guard still lifts (same as |
| off | integer [ 0 .. 7 ] Bits: 1 no search, 2 no hue guard, 4 no brightness restore |
| lb | number Same as |
| kx | integer Same as |
| ll | integer Same as |
| wg | number Green weight for reds and ambers (red leads). Above 1 adds green to a dim red/amber/orange; 1 = neutral |
| wb | number Blue weight for reds and ambers (red leads). Above 1 adds blue; 1 = neutral |
| gg | number Green weight for greys and whites (no saturation). Blends towards |
| gb | number Blue weight for greys and whites. Blends towards |
| og | number Green weight when green leads (green, teal and their pastels); below 1 thins out the green that the red LED would otherwise dominate; 1 = neutral |
| ob | number Blue weight when green leads (green, teal and their pastels); 1 = neutral |
| bg | number Green weight when blue leads (blue, cyan and their pastels); 1 = neutral |
| bb | number Blue weight when blue leads (blue, cyan and their pastels); below 1 takes the violet out of a blue; 1 = neutral |
| fl | integer Enum: 0 1 Scale pixels dimmer than the dimmest visible level up to it |
| gamma | number Power-law curve instead of CIE in the dim range; 0 = CIE |
| minb | integer [ 0 .. 255 ] Brightness floor in the driver |
Shows the clock at a night brightness for a while. With the night clock on it is the red night clock; with it off it is the normal face at that brightness.
| b | integer [ 0 .. 255 ] Panel brightness; 0 stops |
| secs | integer [ 1 .. 600 ] Default: 90 How long the night brightness preview stays on, in seconds (1 to 600) |
| off | integer Enum: 0 1 With 1, |
An empty pin removes it. Otherwise 4 to 8 digits.
| pin | string The new admin PIN, 4 to 8 digits. An empty value removes the PIN |
{- "message": "string",
- "pinSet": true,
- "token": "string"
}{- "version": "string",
- "message": "string",
- "busy": true,
- "ready": true,
- "failed": true,
- "received": 0,
- "total": 0,
- "lowMemory": true,
- "available": true,
- "latest": "string",
- "checkError": "string",
- "checking": true,
- "checkedAt": 0,
- "server": "string",
- "freeHeap": 0,
- "minimumHeap": 0
}With mode set to a prepared face the clock switches to it from the spare slot and restarts.
Otherwise it downloads and installs the update (for that mode, or the current face).
| mode | string A face id |
| force | integer Present: update even while a live match or session is running |
{- "message": "string"
}An http:// or https:// address; empty means GitHub.
| url | string Address of the update server the clock checks for new firmware |
{- "message": "string"
}| mode required | string Face id to download into the spare memory, so switching to it later takes seconds |
{- "message": "string"
}| enabled | string Enum: "0" "1" MQTT link to Home Assistant on or off |
| screenCamera | string Enum: "0" "1" Publish the panel picture as a Home Assistant camera whenever the screen changes |
| host | string IP address or host name of the broker |
| port | integer [ 1 .. 65535 ] Default: 1883 Broker port (default 1883) |
| user | string Broker user name. Empty = no login |
| password | string Broker password. Write-only; leave empty to keep the stored one |
| passwordClear | string Value: "1" Remove the stored password |
{- "message": "string"
}Known form fields: host, port, tls, token (write-only), clearToken, pages, phono, noinfo, coverFirst (1: a radio station shows the song's album art when there is one, the station logo otherwise; 0, the default: the station logo first),
thin, weeks, order, pageSecs, export, gridImportPositive, batteryDischargePositive,
weather, doorbell, doorbellFlags, washerEntity, washerFlags, and the tile, icon, energy and
media-player entity fields the page sends. Invalid values answer 400; a full settings storage 500.
| property name* additional property | any |
{- "message": "string"
}| dir | integer Enum: -1 0 1 2 3 4 5 0 up, 1 right, 2 down, 3 left, 4 fire or drop, -1 back to the computer; what each does is up to the face (Tetris: 0 turns the piece, 2 moves it down one row, 4 drops it; Mario and Luigi: 1 walks right, 5 runs right, 3 walks left, 2 stands still, 0 or 4 jumps) |
| enabled | string Enum: "0" "1" WLED overlay on or off: accept WLED apps and realtime protocols on the panel |
| timeoutMin | integer [ 0 .. 255 ] Idle minutes before the WLED light is switched off. 0 = never |
| ignoreLive | string Enum: "0" "1" 1 = WLED commands and streams are ignored while a favourite match or an F1/MotoGP session is live |
| bootEffect | integer Effect id |
{- "message": "string"
}Open without a PIN. Body is a WLED state object. Answers {"success":true}.
| property name* additional property | any |
{ }{- "success": true
}Query parameters as in the old WLED API: T (0 off, 1 on, 2 toggle), A (brightness), FX, SX,
IX, FP (palette), colour channels R/G/B (and the second/third colour), TT (transition, ms),
PL (preset), NL (nightlight minutes). Refuses Sec-Fetch-Site: cross-site. Answers XML.
| slot | integer Saved picture slot to write |
| name | string Name shown in the Saved picture list |
| delay | integer [ 20 .. 60000 ] Default: 100 Frame delay in ms |
| append | string Present: add a frame to the slot |
| ram | string Present: save the imported animation held in RAM |
With estimate it only answers {bytes, free, frames, fits}.
| slot | integer Flash slot for the played GIF |
| name | string Name shown in the Saved picture list |
| estimate | string Present: only report whether it fits |
The clock signs the browser page in to the GIF search Worker. 202 {"pending":true} while it
does so; 200 {id, token, ttl} when done. With force the old token is discarded.
| force | string
|
{- "id": "string",
- "token": "string",
- "ttl": 0
}