How a client renders any device
A client renders a device from the capability descriptor it receives, not from a fixed per-model layout. The server describes each thing as a set of capabilities, and each capability is a list of controls bound to a closed set of primitives: toggle, slider, stepper, color, color_temp, setpoint, select, momentary, readout, text. A new device category can appear through catalogue data alone, but a new primitive requires an app change. The client also keeps the house working when the cloud is down, and a household sees less than staff; the page should reflect what the household can know, not internal admin views. A DEV key works in a sandbox, and a LIVE key works only for its own connector.
What the client gets
Section titled “What the client gets”The server sends a thing with capabilities, state, settings, icon, category, and liveness information. The client renders whatever it is sent, as long as the capability descriptor uses known primitives and valid control metadata. Each control has a key, label, unit, bounds, options, an icon token, and read/write flags. The same thing may appear in a room, in a property overview, or in a device list, but it is still the same thing shape.
The client treats a thing as one of two kinds: device or endpoint. That kind decides which command route is used. A room’s list and a property overview can show both together.
Settings and state
Section titled “Settings and state”| Where it lives | Written through | Control binding | |
|---|---|---|---|
| Settings (desired configuration) | DeviceSettings.byCapability | PATCH /devices/:id/settings | settings |
| State (what the thing reports now) | DeviceStatusReport.payload, Endpoint.state | POST …/commands changes it; a connector or the device reports it | state |
A settings-bound control names a key that exists in that capability’s settings schema, and it must obey the same bounds. A write descriptor is checked against the schema before it can be accepted. The important rule for developers is that settings and state are separate documents: one is what the user wants, the other is what the thing reports.
How a thing is identified and rendered
Section titled “How a thing is identified and rendered”A thing has an id, kind, category, icon, capabilities, state keyed by capabilityId.controlKey, stateUpdatedAt, liveness, and settings. The client uses that descriptor to decide what to draw and which controls are interactive. If a thing belongs to a room or property overview, the same descriptor still drives the rendering.
Liveness for an endpoint is null until a connector has polled it. The client must not show null as offline. That value means “not checked”.
Routes a client uses
Section titled “Routes a client uses”| Route | What |
|---|---|
GET /capabilities | the catalogue, { schemaVersion, items } |
GET /properties/:id/overview | every room with things and its resolved tile view, house (the All-the-house tile’s actions and aggregates), and unassigned |
POST /properties/:id/refresh | ask each connector for current state, store it, answer the overview plus failed per thing |
GET /rooms/:id | raw members plus things and the tile view, including actionValues |
GET /devices, GET /devices/:id | each row carries thing |
GET /endpoints/:id, POST /endpoints/:id/refresh | one endpoint as a thing |
POST /devices/:id/commands, POST /endpoints/:id/commands | `{ command: “set” |
POST /rooms/:id/actions, POST /properties/:id/actions | a room action fanned out to every member reporting the capability |
A command is validated against the thing’s own descriptors before it is accepted. What is stored after a command is what the connector read back, never what was asked.
What happens when something is unknown
Section titled “What happens when something is unknown”The client degrades in predictable ways:
- A primitive the client does not know renders as a labelled read-only row.
- An icon token the client does not know falls back to the category icon.
- An enum value the client does not know is dropped.
- A control whose primitive is only a plain string on the client is still checked against the server-side catalogue.
- An unknown value is treated as unknown, not as off.
That last point matters for toggles. If a toggle’s state is undefined, the client draws it as unknown and the first press sends true, not the opposite of a value that has not been read yet.
When a control appears
Section titled “When a control appears”A control with requires is shown only if the thing’s features name that feature. If the thing does not provide the feature, the control is filtered out. The client should not show a control that the underlying service does not provide.
Reading a network thing
Section titled “Reading a network thing”network is a read-only capability. It applies to routers and access points that the household sees in the list. These things carry category hub, icon router, and four readouts with no writes. The visible data is the network information the console knows, such as online status, connected devices, address, and model.
Make and model
Section titled “Make and model”A thing’s Details show “Make and model” from provider facts stored on the endpoint, or for an ALYT device, from its unit’s model. The client should display that data as part of the thing details, not as a separate device lookup.
What this means for rendering
Section titled “What this means for rendering”A client does not need a bespoke screen for every device type. It needs a capability catalogue, a known set of primitives, and a renderer that respects read/write rules, bindings, and unknown values. If the server sends a new category that the catalogue knows, the client renders it without a release. If the server sends a primitive the app does not know, the client falls back to a readable non-interactive representation.