Building and Consuming REST APIs for IoT and Monitoring Systems
A practical guide to designing, building, and consuming REST APIs — for IoT data ingestion, NMS integration, device management, and real-time monitoring dashboards.
Why APIs Matter for IoT and Monitoring
APIs (Application Programming Interfaces) are the glue that connects IoT devices, monitoring systems, dashboards, and external services. A well-designed API enables:
- IoT devices to send sensor data to a central server
- Dashboards to query real-time and historical data
- External systems to integrate with your monitoring platform
- Mobile apps to control devices remotely
- Automation scripts to trigger actions based on conditions
REST API Fundamentals
REST (Representational State Transfer) is the most common API architecture for web services.
Core Principles
- Stateless — Each request contains all information needed (no server-side sessions)
- Resource-based — URLs represent resources (nouns, not verbs)
- HTTP methods — Use standard methods for operations
- JSON responses — Standard data format
HTTP Methods
| Method | Operation | Example | Idempotent | |--------|-----------|---------|-----------| | GET | Read | Get sensor data | Yes | | POST | Create | Submit new reading | No | | PUT | Update (full) | Update device config | Yes | | PATCH | Update (partial) | Update one field | Yes | | DELETE | Remove | Delete a device | Yes |
URL Design
# Good URL design (resource-based)
GET /api/v1/devices # List all devices
GET /api/v1/devices/123 # Get device 123
POST /api/v1/devices # Create new device
PUT /api/v1/devices/123 # Update device 123
DELETE /api/v1/devices/123 # Delete device 123
GET /api/v1/devices/123/readings # Get readings for device 123
POST /api/v1/devices/123/readings # Submit new reading
# Filtering, sorting, pagination
GET /api/v1/readings?device=123&from=2026-01-01&to=2026-02-01
GET /api/v1/devices?status=active&sort=name&page=2&limit=20
HTTP Status Codes
| Code | Meaning | Use | |------|---------|-----| | 200 | OK | Successful GET, PUT, PATCH | | 201 | Created | Successful POST | | 204 | No Content | Successful DELETE | | 400 | Bad Request | Validation error | | 401 | Unauthorized | Missing or invalid auth | | 403 | Forbidden | Authenticated but not authorized | | 404 | Not Found | Resource doesn't exist | | 409 | Conflict | Duplicate resource | | 429 | Too Many Requests | Rate limit exceeded | | 500 | Server Error | Unexpected error |
Building an API: Node.js + Express
Project Setup
mkdir iot-api && cd iot-api
npm init -y
npm install express cors helmetBasic API Server
// server.js
import express from 'express';
import cors from 'cors';
import helmet from 'helmet';
const app = express();
// Middleware
app.use(helmet()); // Security headers
app.use(cors()); // Cross-origin requests
app.use(express.json()); // Parse JSON bodies
// In-memory store (use a database in production)
const devices = new Map();
const readings = [];
// --- Device Endpoints ---
// List all devices
app.get('/api/v1/devices', (req, res) => {
const { status } = req.query;
let result = [...devices.values()];
if (status) result = result.filter(d => d.status === status);
res.json({ data: result, total: result.length });
});
// Get single device
app.get('/api/v1/devices/:id', (req, res) => {
const device = devices.get(req.params.id);
if (!device) return res.status(404).json({ error: 'Device not found' });
res.json({ data: device });
});
// Create device
app.post('/api/v1/devices', (req, res) => {
const { id, name, type, location } = req.body;
if (!id || !name) {
return res.status(400).json({ error: 'id and name are required' });
}
if (devices.has(id)) {
return res.status(409).json({ error: 'Device already exists' });
}
const device = { id, name, type, location, status: 'active', createdAt: new Date() };
devices.set(id, device);
res.status(201).json({ data: device });
});
// --- Reading Endpoints ---
// Submit sensor reading
app.post('/api/v1/devices/:id/readings', (req, res) => {
const device = devices.get(req.params.id);
if (!device) return res.status(404).json({ error: 'Device not found' });
const reading = {
deviceId: req.params.id,
...req.body,
timestamp: req.body.timestamp || new Date().toISOString()
};
readings.push(reading);
res.status(201).json({ data: reading });
});
// Get readings for a device
app.get('/api/v1/devices/:id/readings', (req, res) => {
const { from, to, limit = 100 } = req.query;
let result = readings.filter(r => r.deviceId === req.params.id);
if (from) result = result.filter(r => r.timestamp >= from);
if (to) result = result.filter(r => r.timestamp <= to);
result = result.slice(-Number(limit));
res.json({ data: result, total: result.length });
});
// Start server
app.listen(3000, () => console.log('API running on http://localhost:3000'));API Authentication
API Key Authentication
Simple and suitable for IoT devices:
// Middleware
function authenticateApiKey(req, res, next) {
const apiKey = req.headers['x-api-key'];
if (!apiKey || !isValidApiKey(apiKey)) {
return res.status(401).json({ error: 'Invalid API key' });
}
next();
}
app.use('/api/v1', authenticateApiKey);JWT (JSON Web Token) Authentication
For user-facing dashboards:
import jwt from 'jsonwebtoken';
// Login endpoint
app.post('/api/v1/auth/login', (req, res) => {
const { username, password } = req.body;
const user = authenticateUser(username, password);
if (!user) return res.status(401).json({ error: 'Invalid credentials' });
const token = jwt.sign(
{ userId: user.id, role: user.role },
process.env.JWT_SECRET,
{ expiresIn: '24h' }
);
res.json({ token });
});
// Middleware
function authenticateJWT(req, res, next) {
const auth = req.headers.authorization;
if (!auth?.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Token required' });
}
try {
req.user = jwt.verify(auth.slice(7), process.env.JWT_SECRET);
next();
} catch {
res.status(401).json({ error: 'Invalid token' });
}
}Consuming APIs
From IoT Devices (Python)
import requests
import time
API_URL = "https://api.example.com/api/v1"
API_KEY = "your-api-key"
headers = {
"X-API-Key": API_KEY,
"Content-Type": "application/json"
}
# Submit sensor reading
def send_reading(device_id, temperature, humidity):
response = requests.post(
f"{API_URL}/devices/{device_id}/readings",
headers=headers,
json={
"temperature": temperature,
"humidity": humidity
}
)
return response.status_code == 201
# Main loop
while True:
temp = read_temperature_sensor()
hum = read_humidity_sensor()
send_reading("sensor-001", temp, hum)
time.sleep(60) # Send every 60 secondsFrom JavaScript (Dashboard)
// Fetch with async/await
async function getDevices() {
const response = await fetch('/api/v1/devices', {
headers: {
'Authorization': `Bearer ${token}`,
}
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const { data } = await response.json();
return data;
}
// Submit data
async function submitReading(deviceId, reading) {
const response = await fetch(`/api/v1/devices/${deviceId}/readings`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
},
body: JSON.stringify(reading),
});
return response.ok;
}Using curl (Testing)
# List devices
curl -H "X-API-Key: your-key" https://api.example.com/api/v1/devices
# Create device
curl -X POST -H "Content-Type: application/json" \
-H "X-API-Key: your-key" \
-d '{"id":"sensor-001","name":"Temperature Sensor","type":"DHT22"}' \
https://api.example.com/api/v1/devices
# Submit reading
curl -X POST -H "Content-Type: application/json" \
-H "X-API-Key: your-key" \
-d '{"temperature":23.5,"humidity":65}' \
https://api.example.com/api/v1/devices/sensor-001/readingsBest Practices
Rate Limiting
import rateLimit from 'express-rate-limit';
// IoT devices: 60 requests per minute
const deviceLimiter = rateLimit({
windowMs: 60 * 1000,
max: 60,
message: { error: 'Rate limit exceeded' }
});
// Dashboard users: 100 requests per minute
const userLimiter = rateLimit({
windowMs: 60 * 1000,
max: 100,
});
app.use('/api/v1/devices/:id/readings', deviceLimiter);
app.use('/api/v1', userLimiter);Input Validation
// Validate reading data
app.post('/api/v1/devices/:id/readings', (req, res) => {
const { temperature, humidity } = req.body;
if (typeof temperature !== 'number' || temperature < -50 || temperature > 100) {
return res.status(400).json({ error: 'Invalid temperature value' });
}
if (typeof humidity !== 'number' || humidity < 0 || humidity > 100) {
return res.status(400).json({ error: 'Invalid humidity value' });
}
// Process valid data...
});API Versioning
Always version your API to allow backward-compatible changes:
/api/v1/devices # Current version
/api/v2/devices # Future version with breaking changes
Error Response Format
Consistent error responses make debugging easier:
{
"error": "Validation failed",
"details": [
{ "field": "temperature", "message": "Must be a number between -50 and 100" }
],
"status": 400
}Conclusion
REST APIs are the standard way to connect IoT devices, monitoring systems, and dashboards. Whether you're sending sensor readings from an Arduino, querying data for a dashboard, or integrating with third-party services, a well-designed API makes it all possible. Focus on clear URL design, proper authentication, input validation, and rate limiting to build APIs that are both powerful and secure.
Related: WebSocket for Real-Time Dashboards, IoT Real-Time Data Transfer, and SNMP Monitoring.