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.

Your deviceESP32, Pi, gateway
AIOTA brokerchecks login and topics
Dashboard and APIcharts, commands, REST

Quick start

Get a reading onto the dashboard in about five minutes.

  1. Create an account at /register and sign in.
  2. Create a project from the Projects page, for example Home lab.
  3. Add a device inside the project. Copy the device ID and password from the window that opens. The password is only shown once.
  4. 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 iot.aiota.tech --device dev_xxxxxxxxxxxx --password your-device-password

Open 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 hostiot.aiota.tech
Port8883 MQTT over TLS (use this in production). 1883 without TLS is for local testing only.
ProtocolMQTT 3.1.1 or MQTT 5
Client IDThe device ID. It must be the same as the username, or the connection is refused.
UsernameThe device ID, for example dev_9a8b7c6d5e4f
PasswordThe device password from the dashboard
Keep-alive30 to 60 seconds
Clean sessionEither 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.

TopicDirectionPayloadQoS
devices/<id>/telemetryDevice publishesJSON object of readings0 or 1
devices/<id>/statusDevice publishesonline or offline, retained1
devices/<id>/cmdDevice subscribesJSON command1

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.6 is 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    = "iot.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://iot.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://iot.aiota.tech/api/docs (Swagger UI).

# Sign in and keep the token
curl -s -X POST https://iot.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://iot.aiota.tech/api/auth/me -H "Authorization: Bearer $TOKEN"
MethodPathWhat it does
POST/api/auth/registerCreate an account. The first account on a server becomes admin.
POST/api/auth/loginGet a token
GET/api/auth/meThe signed-in user

REST API: projects and devices

MethodPathWhat it does
GET/api/projectsYour projects, with device and online counts
POST/api/projectsCreate 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}/devicesDevices in a project
GET/api/devicesAll your devices, across projects
POST/api/devicesAdd 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-credentialsIssue a new MQTT password. The old one stops working.
# Create a project
curl -s -X POST https://iot.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://iot.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": "iot.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

MethodPathWhat it does
GET/api/devices/{uid}/telemetryReadings, oldest first. Query: minutes (1 to 43200, default 60), limit (up to 10000)
POST/api/devices/{uid}/commandsPublish a command: command, params
# Readings from the last 60 minutes (oldest first)
curl -s "https://iot.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://iot.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.

MethodPathWhat it does
GET/api/admin/statsUsers, projects, devices, online devices and messages in the last 24 hours
GET/api/admin/usersEvery 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://iot.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://iot.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

SymptomLikely 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 offlineYou 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 publishingPublishing to a topic the device doesn't own. Check the device ID in the topic.
Data arrives but nothing is chartedValues are strings like "24.5". Send numbers: 24.5.
TLS handshake fails on ESP32Missing or wrong root certificate, or the clock isn't set. Set the CA certificate and sync time with NTP.

HTTP status codes

400The request can't be done, for example an admin denying themselves
401Missing, expired or invalid token, or the account is denied. Sign in again.
403Signed in, but not allowed: admin endpoints, closed sign-up, denied account at sign-in
404Not found, or it belongs to someone else
409Email already registered
422Invalid fields. The detail array says which.
503The 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_c or power_kw, so charts read well.