API Design & Developer Experience¶
The IoT platform API is the interface between the data layer and the applications built on top of it — dashboards, mobile apps, ERP integrations, third-party analytics. A well-designed IoT API must handle time-series query patterns, device command issuing, streaming updates, and fleet management operations. These are fundamentally different from typical CRUD REST APIs. The most common design mistakes: offset-based pagination (breaks at 100k+ device lists), polling for live data (creates unnecessary load), and treating telemetry as a simple key-value store (ignores aggregation, quality codes, and time ranges).
21.1 REST API Design for IoT¶
The full API resource model for a production IoT platform:
GET /v1/devices # List with pagination + filters
GET /v1/devices/{id} # Device metadata + current state
POST /v1/devices/{id}/commands # Issue command
GET /v1/devices/{id}/commands/{cmd_id} # Command status
GET /v1/telemetry/{device_id} # Time-series query
?tags=temp_inlet_c,pressure_bar # Tag selection
&from=2026-03-01T00:00:00Z # Time range start
&to=2026-03-19T23:59:59Z # Time range end
&interval=1m # Aggregation interval (raw if omitted)
&agg=avg,min,max # Aggregation functions
&quality=good_only # Filter by OPC-UA quality
GET /v1/events/{device_id} # Alarms and events
?severity=high,critical
&state=active
GET /v1/fleet/summary # Fleet-level KPIs
GET /v1/fleet/firmware # Firmware version distribution
Pagination: Device lists at scale require cursor-based pagination — offset pagination (?page=500&limit=100) requires the database to scan and discard 50,000 rows to return page 500, which becomes catastrophically slow at 100k+ devices. Cursor-based pagination uses an opaque continuation token that encodes the last-seen position:
{
"devices": [...],
"next_cursor": "eyJpZCI6IkdXLTAwNDUiLCJ0cyI6MTcxMDg0NDgwMH0=",
"has_more": true,
"total_count": 12847
}
The client passes ?cursor=eyJpZCI6... to fetch the next page. The cursor is a base64-encoded JSON object containing the sort key of the last returned item — the query becomes WHERE device_id > last_device_id LIMIT 100 which is always an index scan regardless of page depth.
21.2 Real-Time Push: WebSocket vs SSE¶
| Feature | WebSocket | Server-Sent Events (SSE) |
|---|---|---|
| Direction | Bidirectional | Server → client only |
| HTTP/2 support | Separate connection | Native HTTP/2 multiplexing |
| Auto-reconnect | Client must implement | Built into browser EventSource API |
| Proxy compatibility | Some proxies block upgrades | Standard HTTP — no issues |
| Use case fit | Command + telemetry in one connection | Read-only dashboard streaming |
Recommendation: Use SSE for read-only dashboard streaming (simpler, HTTP/2 compatible, browser auto-reconnect) and WebSocket only when the same connection needs to carry both live data and bidirectional commands.
SSE endpoint design:
GET /v1/stream/devices/{id}/telemetry
Accept: text/event-stream
HTTP/1.1 200 OK
Content-Type: text/event-stream
Cache-Control: no-cache
data: {"ts":1710844800123,"temp_inlet_c":72.4,"pressure_bar":4.2}
data: {"ts":1710844801124,"temp_inlet_c":72.5,"pressure_bar":4.2}
Include a retry: 3000 directive in the SSE stream header so the browser reconnects within 3 seconds if the connection drops. Send a :keepalive comment every 15 seconds to prevent proxies and load balancers from closing idle connections.
21.3 Rate Limiting & Quotas¶
| Operation Type | Free Tier | Standard | Enterprise |
|---|---|---|---|
| Telemetry write (device → platform) | Exempt | Exempt | Exempt |
| Device list / metadata reads | 100 req/min | 1,000 req/min | 10,000 req/min |
| Time-series queries | 30 req/min | 300 req/min | Custom |
| Command issue | 10 req/min | 100 req/min | Custom |
| SSE stream connections | 5 concurrent | 50 concurrent | Custom |
Critical principle: Telemetry write operations (data flowing from device to platform) must never be rate-limited. A device that cannot write its telemetry due to an API rate limit is silently losing data — the device has no buffer for this path in the managed API model. Rate limits apply to read operations only. Sensor data is always accepted.
When a read operation is rate-limited, return HTTP 429 with a Retry-After header:
HTTP/1.1 429 Too Many Requests
Retry-After: 12
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1710844812
{"error": "rate_limit_exceeded", "message": "Query limit reached. Retry in 12 seconds."}