Skip to main content

Cloud API

The Cloud API is a Fastify (Node.js) service that does two things simultaneously:

  1. Maintains a persistent MQTT subscription to Mosquitto, routes incoming agent telemetry into Redis Streams and TimescaleDB, and dispatches desired-state and job commands to agents.
  2. Serves the REST API for the dashboard, agent provisioning, fleet management, and all data access.

All REST routes are prefixed with /api/v1.


Authentication

Local JWT (default)

POST /api/v1/auth/login
Body: { "username": "...", "password": "..." }
Response: { "accessToken": "...", "refreshToken": "..." }

Include the access token in subsequent requests:

Authorization: Bearer <accessToken>

Access tokens expire after JWT_ACCESS_TOKEN_EXPIRY (default: 15 minutes). Refresh tokens expire after JWT_REFRESH_TOKEN_EXPIRY (default: 7 days).

POST /api/v1/auth/refresh
Body: { "refreshToken": "..." }
Response: { "accessToken": "..." }

Auth0 (optional)

When AUTH0_ENABLED=true, the API accepts Auth0 JWTs in addition to local tokens. Exchange an Auth0 access token for a local session:

POST /api/v1/auth/auth0-exchange
Authorization: Bearer <auth0-access-token>
Response: { "accessToken": "...", "refreshToken": "..." }

Auth0 configuration: AUTH0_DOMAIN, AUTH0_AUDIENCE, AUTH0_ISSUER.

API Keys

Long-lived API keys for programmatic access (CI/CD, integrations):

GET /api/v1/api-keys — list keys (hashed, plaintext not returned)
POST /api/v1/api-keys — create key (plaintext returned once)
DELETE /api/v1/api-keys/:id — revoke key

Pass an API key as a Bearer token in the Authorization header.


Endpoints

Auth & Users

MethodPathDescription
POST/auth/registerCreate a new dashboard user
POST/auth/loginObtain access + refresh tokens
POST/auth/auth0-exchangeExchange Auth0 token for local session
POST/auth/bootstrap-adminCreate the first admin (one-time, locked after use)
POST/auth/refreshRefresh an expired access token
POST/auth/logoutRevoke refresh token
POST/auth/change-passwordChange own password
POST/auth/reset-passwordAdmin-triggered password reset
GET/auth/meCurrent user profile
GET/usersList all users (admin)
GET/users/:idGet user by ID
POST/usersCreate user (admin)
PUT/users/:idUpdate user (admin)
DELETE/users/:idDelete user (admin)
GET/invitesList pending invites
POST/invitesSend invite email
POST/invites/acceptAccept invite and set password
DELETE/invites/:idCancel invite

MQTT Users & ACLs

See MQTT Broker — Managing Users via API for the full MQTT user management endpoint list.

Agent Management

MethodPathDescription
GET/agentsList all agents with status
GET/agents/:uuidGet agent by UUID
POST/agentsRegister a new agent record
PATCH/agents/:uuidUpdate agent metadata
PATCH/agents/:uuid/activeEnable / disable agent
DELETE/agents/:uuidRemove agent
GET/agents/:uuid/current-stateLive reported state from agent
GET/agents/:uuid/target-stateCloud desired state
POST/agents/:uuid/target-statePush new desired state
GET/agents/:uuid/metricsLatest system metrics (CPU, memory, storage)
GET/agents/:uuid/logsAgent and service logs
GET/agents/:uuid/devicesRegistered protocol devices on this agent
POST/agents/:uuid/devicesAdd a protocol device
PUT/agents/:uuid/devices/:nameUpdate device config
DELETE/agents/:uuid/devices/:nameRemove device
GET/agents/:uuid/tagsAgent tags
POST/agents/:uuid/tagsSet tags
GET/agents/locationsAgent map locations
POST/agents/queryQuery agents by tag filter

Jobs

MethodPathDescription
GET/jobs/templatesList job templates
POST/jobs/templatesCreate template
PUT/jobs/templates/:idUpdate template
DELETE/jobs/templates/:idDelete template
GET/jobs/executionsList job executions
GET/jobs/executions/:jobIdGet execution detail
POST/jobs/executeDispatch a job immediately
POST/jobs/executions/:jobId/cancelCancel a running job
POST/agents/:uuid/jobsQueue a job for an agent
GET/agents/:uuid/jobs/nextAgent polls for its next pending job
PATCH/agents/:uuid/jobs/:jobId/statusAgent reports job status

Provisioning

MethodPathDescription
GET/provisioning/keysList provisioning keys
POST/provisioning/keysCreate a provisioning key
POST/provisioning/keys/generateGenerate a one-time key
DELETE/provisioning/keys/:idRevoke a key
POST/provisioning/registerAgent self-registration (uses provisioning key)

Telemetry & Readings

MethodPathDescription
GET/readings/latestLatest value per metric per agent
GET/readings/catalogMetric catalog (distinct metric names + metadata)
GET/readings/hourlyHourly aggregates
GET/readings/dailyDaily aggregates
POST/readings/queryFlexible time-range query with filters

Anomaly Detection

MethodPathDescription
GET/anomaly/summaryFleet-wide anomaly summary
GET/anomaly/hourlyHourly anomaly counts
GET/anomaly/dailyDaily anomaly counts
GET/anomaly/top-metricsMost anomalous metrics
GET/anomaly-alertsActive anomaly alerts
GET/anomaly-incidentsAnomaly incidents
PATCH/anomaly-incidents/:incidentId/resolveResolve an incident

Fleets

MethodPathDescription
GET/fleetsList fleets
POST/fleetsCreate fleet
PATCH/fleets/:idUpdate fleet
DELETE/fleets/:idDelete fleet
POST/fleets/:id/startStart fleet
POST/fleets/:id/stopStop fleet
GET/fleets/:id/usage-eventsFleet usage timeline

MQTT Broker Monitoring

MethodPathDescription
GET/broker/statusBroker connection status
GET/broker/metricsLive message rates, throughput
GET/broker/statsAccumulated broker statistics
GET/broker/topicsActive topic tree
GET/broker/topics/:topic/schemaInferred JSON schema for a topic
GET/broker/system-stats$SYS broker statistics
GET/broker/dashboardAggregated broker dashboard data

Events & Audit

MethodPathDescription
GET/events/recentRecent audit events
GET/events/searchSearch events by filters
GET/events/statsEvent volume statistics
GET/events/device/:deviceUuidEvents for a specific device
GET/events/device/:deviceUuid/timelineChronological event timeline
POST/events/device/:deviceUuid/compareCompare two event snapshots
POST/events/device/:deviceUuid/replayReplay events into a target state

Dashboard

MethodPathDescription
GET/dashboard-layouts/:deviceUuidDefault layout for a device
GET/dashboard-layouts/:deviceUuid/allAll layouts for a device
POST/dashboard-layouts/:deviceUuidCreate a layout
PUT/dashboard-layouts/:layoutIdUpdate a layout
DELETE/dashboard-layouts/:layoutIdDelete a layout
POST/ai/chatAI chat for device/fleet analysis

License

MethodPathDescription
GET/licenseCurrent license info and feature flags
POST/billing/refresh-licenseRe-validate license key

Health

MethodPathAuthDescription
GET/healthNone{ status, uptime, db, redis, mqtt }
GET/metricsNonePrometheus scrape endpoint
GET/metrics/ingestion-healthJWTIngestion stream lag and spool status

MQTT Resilience

The Cloud API maintains a single persistent MQTT connection to Mosquitto. All agent telemetry flows through this subscription into the processing pipeline.

Reconnect with Exponential Backoff

When the broker connection drops, the API schedules reconnects with exponential backoff:

AttemptDelay
11 s
22 s
34 s
4+8 s (cap)

After MQTT_MAX_RECONNECT_ATTEMPTS consecutive failures (default: 20), the client enters a fatal state. It automatically resets after MQTT_FATAL_RECOVERY_COOLDOWN_MS (default: 60 000 ms) and resumes reconnecting. Set MQTT_MAX_RECONNECT_ATTEMPTS=0 for unlimited retries.

Pending Publish Queue

Outbound MQTT messages (desired-state updates, job dispatches) that fail to send while the broker is unreachable are queued in memory:

  • Queue capacity: 1 000 messages
  • On reconnect, queued messages are flushed in order before any new publishes
  • If the queue fills beyond capacity, the oldest messages are dropped and a warning is logged
  • QoS 1 (at-least-once delivery) is used by default — MQTT_QOS overrides this

Disk Spool (Telemetry Offline Buffer)

When the ingestion pipeline cannot write to Redis (Redis unreachable or circuit breaker open), the Cloud API's telemetry publisher falls back to a disk spool. The spool buffers NDJSON files locally and replays them once connectivity is restored.

Telemetry pipeline

├─[Redis available]──▶ Redis Stream → Ingestion service

└─[Redis unavailable / circuit open]──▶ Disk spool

spool-N.ndjson

(circuit closes / Redis recovers)

Replay to Redis

Configuration

VariableDefaultDescription
DISK_SPOOL_ENABLEDfalseEnable disk spool fallback
DISK_SPOOL_PATH/tmp/iotistic-spoolDirectory for spool files
DISK_SPOOL_MAX_SIZE_MB500Max total spool size before oldest files are pruned

Behavior

  • Each spool file is capped at 10 MB, then a new numbered file is created (spool-1.ndjson, spool-2.ndjson, …).
  • Files are replayed in numeric order (oldest first). After a restart, the file index is seeded from the highest existing file number so replay order is preserved.
  • When the total spool size exceeds DISK_SPOOL_MAX_SIZE_MB, the oldest file is deleted to make room for new data. A warning is logged.
  • Replay is non-blocking: spool files drain into Redis in the background while the API continues processing new telemetry.
  • Each spool entry is a JSON line: { "source": "mqtt", "data": [...] }.

Circuit Breaker

The circuit breaker monitors failures in the ingestion write path. If the error rate exceeds its threshold within a rolling window it opens — all new telemetry is routed to the disk spool. The breaker polls Redis in the background and closes automatically when connectivity recovers, at which point the spool replayer drains buffered data.

CLOSED ──errors exceed threshold──▶ OPEN
▲ │
└──────── Redis recovers ────────────┘
(spool drains)

Monitor spool state:

GET /api/v1/metrics/ingestion-health
{
"redisConnected": true,
"circuitBreaker": "closed",
"spoolFileCount": 0,
"spoolSizeMb": 0
}