Connection
The broker refuses the CONNECT (
Not authorized) when the credential is rejected, when its binding doesn’t match the username, or when a scoped personal API key is used.
Topics
Every topic islua/devices/<agentId>/<deviceName>/<suffix>.
Messages
status (retained)
Published first, so a late subscriber sees the device’s presence. It must not contain the credential; the broker stores retained messages.status (command list)
Published right after the retained message, withretain false. The platform registers the device and stores the commands when it receives this message. The Node client adds "apiKey": "<key>" only for a legacy key; the Python and MicroPython clients always add it. When it’s absent the platform uses the identity proven at CONNECT; when present, it’s validated again. clientFamily and clientVersion are diagnostics.
{"status":"offline"} on status (retained) is how a client disconnects cleanly; the will message does the same when the connection drops.
command
commandId is a UUID. Keep the last 1,000 IDs for 5 minutes and re-publish the cached response for a repeated ID; QoS 1 can deliver a command twice. The platform doesn’t send a timeout field; the timeout is enforced server-side.
response
commandId (string) and success (boolean) are required. data is any JSON up to 1 MB; error is a string, present when success is false, and its 4 KB cap is enforced over Socket.IO only. A response for an unknown, expired, or already-settled command is ignored.
trigger
triggerName is matched exactly against the handlers on the agent. payload is any JSON up to 1 MB.
trigger_ack
connected
error and trigger_error
pong
Limits
Error codes
A message a credential isn’t allowed to publish is refused by the broker’s authorization check and never reaches the platform, so a device without the
triggers operation gets no trigger_error for a trigger; the publish is denied.
Authorization by topic
A device credential is checked on every publish and subscribe. The topic must be under the credential’s own<agentId>/<deviceName> prefix, and the suffix must be allowed for a granted operation. The credential is re-validated at the next publish or subscribe after 60 seconds; a revoked one is denied and its connection state dropped.
A legacy API key may publish and subscribe to anything under its own prefix.
Socket.IO equivalents
A Socket.IO client connects tohttps://api.heylua.ai/devices with transports: ['websocket'] and auth: { apiKey, agentId, deviceName, group?, commands?, deviceKind?, clientVersion? }; apiKey carries the device credential. Messages are events with the same names and payloads: the platform emits connected, command (with an acknowledgment callback the device must call), trigger_ack, trigger_error, error, and pong; the device emits response, trigger, heartbeat, and ping. Socket.IO adds the handshake codes MISSING_AUTH, AUTH_FAILED, AGENT_FORBIDDEN, DEVICE_DISABLED, CONNECT_ERROR, RATE_LIMITED (with retryAfterMs), plus OPERATION_FORBIDDEN and COMMAND_ROUTE_MISMATCH on error.
See also
- Node device client — the reference implementation of both transports
- Python device client
- Reliability and limits
- How devices work

