Developer documentation
Everything you need to connect devices to AIOTA IoT Cloud and build on top of it. Devices talk MQTT; your own software uses the REST API and a WebSocket for live updates.
Quick start
Get a reading onto the dashboard in about five minutes.
- Create an account at /register and sign in.
- Create a project from the Projects page, for example Home lab.
- Add a device inside the project. Copy the device ID and password from the window that opens. The password is only shown once.
- Connect. Use one of the code examples, or test from your computer with the bundled simulator:
pip install paho-mqtt
python tools/simulator.py --host mqtt.aiota.tech --device dev_xxxxxxxxxxxx --password your-device-passwordOpen the device page. It turns Online and the chart starts to fill. Try sending relay_on from the Send command panel; the simulator prints it.
Projects and devices
- Project
- A group of devices that belong together: a site, a customer, a product line. Every device lives in exactly one project. Deleting a project deletes its devices and their data.
- Device
- One MQTT client. It has a public ID like
dev_9a8b7c6d5e4f, which is also its MQTT client ID and username, and a secret password. - Telemetry
- The JSON readings a device publishes. Each message is stored with the time the platform received it.
- Command
- A JSON message the platform publishes to a device, for example to switch a relay.
Connection settings
| Broker host | mqtt.aiota.tech |
| Port | 8883 MQTT over TLS (use this in production). 1883 without TLS is for local testing only. |
| Protocol | MQTT 3.1.1 or MQTT 5 |
| Client ID | The device ID. It must be the same as the username, or the connection is refused. |
| Username | The device ID, for example dev_9a8b7c6d5e4f |
| Password | The device password from the dashboard |
| Keep-alive | 30 to 60 seconds |
| Clean session | Either works. Use a persistent session if the device must not miss QoS 1 commands while briefly offline. |
For TLS, devices need a trusted root certificate. Python, Node.js and Mosquitto use the system store. On ESP32 call net.setCACert(rootCA) with your certificate authority's root (for Let's Encrypt, ISRG Root X1).
Topics
Each device may only use its own three topics. Anything else is refused by the broker.
| Topic | Direction | Payload | QoS |
|---|---|---|---|
devices/<id>/telemetry | Device publishes | JSON object of readings | 0 or 1 |
devices/<id>/status | Device publishes | online or offline, retained | 1 |
devices/<id>/cmd | Device subscribes | JSON command | 1 |
Online and offline status
Set a Last Will of offline on the status topic when connecting (retained, QoS 1), then publish online right after connecting. If the device loses power or network, the broker publishes the will for it and the dashboard shows it offline within one keep-alive period. Sending any telemetry also marks a device online.
Payload format
Telemetry is a single JSON object. Keys are your choice.
{
"temperature": 24.6,
"humidity": 55.2,
"relay": false,
"firmware": "1.4.2"
}- Numbers are charted. Each numeric key gets its own tab on the device page.
- Booleans and strings are stored and shown in Latest values, but not charted.
- Keep objects flat. Nested objects are stored but not charted.
- A bare value like
24.6is stored as{"value": 24.6}. Messages that aren't valid JSON are ignored. - The platform timestamps each message when it arrives, so devices don't need a clock.
- Keep messages under about 4 KB.
Commands
Commands arrive on devices/<id>/cmd as JSON:
{
"command": "set_interval",
"params": { "seconds": 30 },
"ts": "2026-10-29T09:15:00.120Z"
}command is any name up to 64 characters, and params is any JSON object. The dashboard offers relay_on, relay_off, set_interval and reboot as shortcuts, but your firmware decides what each command does. Commands are not queued: a device that is offline when a command is sent won't receive it, unless it uses a persistent session.
Code examples
Complete programs that connect, report online status, publish readings every 5 seconds and handle commands. Replace the device ID and password with your own.
// Arduino IDE / PlatformIO. Libraries: PubSubClient, ArduinoJson
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
const char* DEVICE_ID = "dev_xxxxxxxxxxxx"; // from the dashboard
const char* PASSWORD = "your-device-password";
const char* BROKER = "mqtt.aiota.tech";
WiFiClientSecure net;
PubSubClient mqtt(net);
String base = String("devices/") + DEVICE_ID;
void onCommand(char* topic, byte* payload, unsigned int len) {
JsonDocument doc;
if (deserializeJson(doc, payload, len)) return;
const char* cmd = doc["command"] | "";
if (strcmp(cmd, "relay_on") == 0) digitalWrite(2, HIGH);
if (strcmp(cmd, "relay_off") == 0) digitalWrite(2, LOW);
}
void connectMqtt() {
while (!mqtt.connected()) {
String status = base + "/status";
// Last Will: the broker publishes "offline" if the device drops
if (mqtt.connect(DEVICE_ID, DEVICE_ID, PASSWORD, status.c_str(), 1, true, "offline")) {
mqtt.publish(status.c_str(), "online", true);
mqtt.subscribe((base + "/cmd").c_str(), 1);
} else delay(5000);
}
}
void setup() {
pinMode(2, OUTPUT);
WiFi.begin("wifi-name", "wifi-password");
while (WiFi.status() != WL_CONNECTED) delay(500);
net.setInsecure(); // for production use net.setCACert(rootCA)
mqtt.setServer(BROKER, 8883);
mqtt.setCallback(onCommand);
}
void loop() {
if (!mqtt.connected()) connectMqtt();
mqtt.loop();
static unsigned long last = 0;
if (millis() - last > 5000) {
last = millis();
JsonDocument doc;
doc["temperature"] = temperatureRead();
doc["rssi"] = WiFi.RSSI();
char buf[200];
size_t n = serializeJson(doc, buf);
mqtt.publish((base + "/telemetry").c_str(), (uint8_t*)buf, n, false);
}
}The project also includes firmware/esp32_example and tools/simulator.py, ready to run.
REST API: authentication
The API lives under https://mqtt.aiota.tech/api and speaks JSON. Sign in to get a token, then send it as Authorization: Bearer <token>. Tokens last 24 hours. Interactive reference with every field: https://mqtt.aiota.tech/api/docs (Swagger UI).
# Sign in and keep the token
curl -s -X POST https://mqtt.aiota.tech/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email": "you@example.com", "password": "your-password"}'
# → {"access_token": "eyJhbGciOi...", "token_type": "bearer"}
export TOKEN="eyJhbGciOi..."
# Send it on every request
curl -s https://mqtt.aiota.tech/api/auth/me -H "Authorization: Bearer $TOKEN"| Method | Path | What it does |
|---|---|---|
| POST | /api/auth/register | Create an account. The first account on a server becomes admin. |
| POST | /api/auth/login | Get a token |
| GET | /api/auth/me | The signed-in user |
REST API: projects and devices
| Method | Path | What it does |
|---|---|---|
| GET | /api/projects | Your projects, with device and online counts |
| POST | /api/projects | Create a project: name, description |
| GET | /api/projects/{uid} | One project |
| PATCH | /api/projects/{uid} | Rename or change the description |
| DELETE | /api/projects/{uid} | Delete a project with all its devices and data |
| GET | /api/projects/{uid}/devices | Devices in a project |
| GET | /api/devices | All your devices, across projects |
| POST | /api/devices | Add a device: project_uid, name, description. Returns MQTT credentials once. |
| GET | /api/devices/{uid} | One device with its latest values |
| PATCH | /api/devices/{uid} | Rename a device |
| DELETE | /api/devices/{uid} | Delete a device and its data |
| POST | /api/devices/{uid}/rotate-credentials | Issue a new MQTT password. The old one stops working. |
# Create a project
curl -s -X POST https://mqtt.aiota.tech/api/projects \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"name": "Cold storage, Pune", "description": "Freezers 1-6"}'
# → {"uid": "prj_4f1c2a9b7d3e", "name": "Cold storage, Pune", "device_count": 0, ...}
# Add a device to it. The response holds the MQTT password, returned only once.
curl -s -X POST https://mqtt.aiota.tech/api/devices \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"project_uid": "prj_4f1c2a9b7d3e", "name": "Freezer 2"}'Response:
{
"device": {
"uid": "dev_9a8b7c6d5e4f",
"name": "Freezer 2",
"project_uid": "prj_4f1c2a9b7d3e",
"project_name": "Cold storage, Pune",
"is_online": false,
"last_seen": null,
"last_data": null,
"created_at": "2026-10-29T09:12:44Z"
},
"mqtt": {
"host": "mqtt.aiota.tech",
"port": 8883,
"client_id": "dev_9a8b7c6d5e4f",
"username": "dev_9a8b7c6d5e4f",
"password": "psw1-k2...",
"telemetry_topic": "devices/dev_9a8b7c6d5e4f/telemetry",
"status_topic": "devices/dev_9a8b7c6d5e4f/status",
"command_topic": "devices/dev_9a8b7c6d5e4f/cmd"
}
}REST API: data and commands
| Method | Path | What it does |
|---|---|---|
| GET | /api/devices/{uid}/telemetry | Readings, oldest first. Query: minutes (1 to 43200, default 60), limit (up to 10000) |
| POST | /api/devices/{uid}/commands | Publish a command: command, params |
# Readings from the last 60 minutes (oldest first)
curl -s "https://mqtt.aiota.tech/api/devices/dev_9a8b7c6d5e4f/telemetry?minutes=60&limit=1000" \
-H "Authorization: Bearer $TOKEN"
# → [{"time": "2026-10-29T09:14:05Z", "data": {"temperature": -18.4}}, ...]
# Send a command
curl -s -X POST https://mqtt.aiota.tech/api/devices/dev_9a8b7c6d5e4f/commands \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"command": "set_interval", "params": {"seconds": 30}}'
# → {"status": "sent", "topic": "devices/dev_9a8b7c6d5e4f/cmd"}REST API: admin endpoints
Only admin accounts can call these. Others get 403.
| Method | Path | What it does |
|---|---|---|
| GET | /api/admin/stats | Users, projects, devices, online devices and messages in the last 24 hours |
| GET | /api/admin/users | Every user with project, device and online counts |
| GET | /api/admin/users/{id} | One user with their projects and devices |
| PATCH | /api/admin/users/{id} | Set is_active (false denies access) or is_admin |
| DELETE | /api/admin/users/{id} | Delete a user and everything they own |
Denying access ({"is_active": false}) signs the user out on their next request, blocks sign-in, makes the broker refuse their devices and drops any messages still arriving. Data is kept. Admins can't deny or demote themselves.
Live WebSocket
Connect to wss://mqtt.aiota.tech/api/ws?token=<token> to receive events for your devices as they happen. Admins receive events for all devices. The server ignores anything you send.
const token = "eyJhbGciOi..."; // from /api/auth/login
const ws = new WebSocket(`wss://mqtt.aiota.tech/api/ws?token=${token}`);
ws.onmessage = (msg) => {
const event = JSON.parse(msg.data);
if (event.type === "telemetry") {
console.log(event.device, event.data); // {"temperature": -18.4}
} else if (event.type === "status") {
console.log(event.device, event.online ? "online" : "offline");
}
};Event shapes:
{"type": "telemetry", "device": "dev_9a8b7c6d5e4f", "time": "2026-10-29T09:14:05Z", "data": {"temperature": -18.4}}
{"type": "status", "device": "dev_9a8b7c6d5e4f", "time": "2026-10-29T09:20:31Z", "online": false}Errors and troubleshooting
MQTT
| Symptom | Likely cause and fix |
|---|---|
Not authorized (code 5 or 135) | Wrong password, client ID not equal to username, the device was deleted, or its owner's access was denied. Copy the ID again or generate a new password. |
| Connects, but the dashboard stays offline | You may be connected to a different broker, for example a local Mosquitto on the same port. Check the host and port, and that nothing else is listening on 1883. |
| Disconnects right after publishing | Publishing to a topic the device doesn't own. Check the device ID in the topic. |
| Data arrives but nothing is charted | Values are strings like "24.5". Send numbers: 24.5. |
| TLS handshake fails on ESP32 | Missing or wrong root certificate, or the clock isn't set. Set the CA certificate and sync time with NTP. |
HTTP status codes
400 | The request can't be done, for example an admin denying themselves |
401 | Missing, expired or invalid token, or the account is denied. Sign in again. |
403 | Signed in, but not allowed: admin endpoints, closed sign-up, denied account at sign-in |
404 | Not found, or it belongs to someone else |
409 | Email already registered |
422 | Invalid fields. The detail array says which. |
503 | The platform can't reach the broker right now, so commands can't be sent |
Best practices
- Report every 5 to 60 seconds. For fast signals, average on the device and send the summary.
- Reconnect with backoff. Wait 2, 4, 8 seconds and so on, up to a minute, so devices don't all reconnect at once after an outage.
- Always set the Last Will so offline devices show up quickly.
- One password per device. Never share credentials between devices. If one leaks, generate a new password for that device only.
- Use TLS on port 8883 in production, and keep port 1883 closed on your firewall.
- Use QoS 0 for frequent readings and QoS 1 for status and commands.
- Use clear key names with units, like
temp_corpower_kw, so charts read well.