Skip to main content
The MQTT protocol is what the Node, Python, and MicroPython clients speak to the platform; use this page to write a device client in another language or to debug one. Socket.IO carries the same messages as events; the mapping is at the end. Verified against @lua-ai-global/device-client 1.1.0 and lua-device-client 1.3.0.

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 is lua/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, with retain 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.
Publishing {"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

Sent once the event is queued. A handler may still be missing on the agent; see Handle device triggers.

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 to https://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