2 REST API Design
2.1 Learning Objectives
By the end of this module, you will be able to:
- Design RESTful IoT APIs: Construct resource hierarchies applying REST constraints to devices, sensors, and readings
- Implement Proper Error Handling: Configure HTTP status codes correctly to represent device states and failure modes
- Select Payload Formats: Evaluate trade-offs between JSON, CBOR, and Protocol Buffers and justify the choice for a given deployment
- Apply API Versioning: Implement URI, header, or query-parameter versioning strategies and distinguish their suitability for constrained devices
- Calculate Rate Limiting Parameters: Analyze request load across device fleets and configure token-bucket algorithms to protect infrastructure
- Assess IoT API Security: Diagnose authentication, authorization, and TLS configuration gaps in REST API designs
For Beginners: REST API Design
A REST API is a standardized way for devices and applications to communicate over the internet using simple requests like GET (read data) and POST (send data). Think of it as a restaurant menu – you look at what is available, place an order, and get a response. Most IoT cloud platforms use REST APIs for device management.
MVU: Minimum Viable Understanding
If you only have 5 minutes, here’s what you need to know about REST API design for IoT:
- Resources, not actions - Design around nouns (devices, sensors, readings) not verbs (getData, updateStatus)
- Use HTTP methods correctly - GET for reading, POST for creating, PUT for updating, DELETE for removing
- Choose compact formats - JSON for development, CBOR/Protobuf for production (60-80% smaller payloads)
- Version from day one - Use URI versioning (
/api/v1/) - it’s simplest for embedded clients - Rate limit everything - Device misbehavior can overwhelm servers; use token bucket algorithms
Bottom line: REST patterns translate well to IoT, but you must optimize for bandwidth, power, and unreliable connectivity. Design for offline-first, retry-with-backoff, and compact payloads.
Sensor Squad: Building APIs for Smart Devices!
Hey there, future IoT architect! Let’s learn about REST APIs with the Sensor Squad!
Sammy the Sensor wants to share his temperature readings with the world. But how does he talk to the cloud? He needs a common language that everyone understands - that’s where REST APIs come in!
Think of REST like a restaurant menu:
- The menu (API) lists what you can order (resources)
- GET is like asking “What’s today’s special?” (read data)
- POST is like saying “I’ll have the soup!” (create new data)
- PUT is like saying “Actually, change my order to salad” (update)
- DELETE is like saying “Cancel my order” (remove)
The Sensor Squad Adventure:
- Sammy sends his temperature to the cloud using
POST /sensors/sammy/readings - Lila the Light Sensor checks her status with
GET /sensors/lila/status - Max the Motion Detector updates his sensitivity with
PUT /sensors/max/config - Bella the Button removes an old alert with
DELETE /alerts/12345
Fun Analogy:
- REST API is like a universal translator for devices
- JSON is like writing a letter with lots of words
- CBOR is like using emojis - smaller but means the same thing!
Why This Matters:
When your smart home talks to the cloud, REST APIs make sure everyone understands each other - whether it’s a tiny temperature sensor or a powerful video doorbell!
Try This: Next time you use a weather app, it’s probably using a REST API to GET the current temperature from sensors just like Sammy!
2.2 Prerequisites
Before diving into this module, you should be familiar with:
- Introduction and Why Lightweight Protocols Matter: Understanding HTTP pitfalls in IoT
- Protocol Overview and Comparison: Technical comparison of HTTP, MQTT, and CoAP
- HTTP Basics: Request methods (GET, POST, PUT, DELETE), status codes, headers
2.3 How This Module Fits
Chapter Series Navigation:
- Introduction and Why Lightweight Protocols Matter
- Protocol Overview and Comparison
- REST API Design for IoT (this module)
- Real-time Protocols
- Worked Examples
This module focuses on practical REST API design patterns specifically for IoT systems, building on the protocol comparison from the previous chapter.
2.4 Module Overview
This module on REST API design for IoT has been organized into focused chapters for easier learning:
2.4.1 Chapter Guide
| Chapter | Topics Covered | Estimated Time |
|---|---|---|
| Design Patterns | RESTful patterns, naming conventions, payload formats, versioning, rate limiting, security | 15-20 min |
| Worked Examples and Quizzes | Thermostat API design, offline device handling, protocol overhead calculations, comprehensive quizzes | 20-25 min |
Total Module Time: ~35-45 minutes
2.5 Quick Reference
2.6 Key Concepts Summary
2.6.1 RESTful Design Principles
Resource-Oriented Design:
- Model APIs around nouns (devices, sensors, readings), not verbs
- HTTP methods provide the actions (GET, PUT, POST, DELETE)
- Use consistent URI patterns:
/api/v1/{resource}/{id}/{subresource}
Statelessness:
- Each request contains all needed information
- No server-side session state
- Enables horizontal scaling and device reconnection
2.6.2 HTTP Methods and CRUD Operations
2.6.3 Interactive: API Request Rate Calculator
Calculate the request load on your API server based on device count and reporting frequency.
2.6.4 Payload Format Selection
| Format | Size | Best For |
|---|---|---|
| JSON | Large | Development, debugging, web apps |
| CBOR | Small | Constrained devices, CoAP payloads |
| Protobuf | Small | High-volume, multi-language systems |
2.6.5 Interactive: Payload Format Comparison
Compare payload sizes for different serialization formats to optimize bandwidth usage.
2.6.6 API Versioning (URI Recommended)
coap://sensor.local/v1/temperature
coap://sensor.local/v2/temperature
- Simplest for embedded clients
- Clear in logs and debugging
- Works across all protocols
2.6.7 RESTful IoT Architecture
Common REST API Pitfalls in IoT
Avoid these common mistakes when designing REST APIs for IoT systems:
| Pitfall | Problem | Solution |
|---|---|---|
| Verb-based URLs | /api/getTemperature mixes actions into URLs |
Use nouns: /api/v1/sensors/{id}/readings |
| Missing pagination | Returning all 10,000 sensor readings crashes mobile apps | Add ?limit=100&offset=0 parameters |
| No rate limiting | Single misbehaving device overwhelms server | Token bucket: 100 req/min per device |
| Ignoring HTTP caching | Same config fetched repeatedly wastes bandwidth | Add ETag and Cache-Control headers |
| Verbose error messages | “SQLException: table sensors column…” exposes internals | Generic messages + internal logging |
| No versioning | Breaking changes brick deployed devices | Version from day one: /api/v1/ |
2.6.8 Interactive: Rate Limiting Token Bucket Simulator
Simulate token bucket algorithm to understand rate limiting behavior.
2.7 Start Learning
Recommended path:
Start with Design Patterns - Learn RESTful patterns, naming conventions, payload formats, versioning, and security best practices
Continue to Worked Examples - Practice with thermostat API design, offline device handling, and test your knowledge with comprehensive quizzes
Common Mistake: Not Implementing Exponential Backoff for Retries
The Mistake: An IoT cloud platform has 10,000 sensors reporting temperature data via REST API POST every 60 seconds. During a 5-minute server outage for emergency patching, all devices fail their POST requests. When the server comes back online, all 10,000 devices retry simultaneously at exactly 00:00, 01:00, 02:00, etc., because developers hardcoded a “retry every 60 seconds” strategy.
Why It Happens: Developers implement simple retry logic with fixed intervals because it’s easier to code than exponential backoff with jitter. The pattern while True: try_request(); sleep(60) seems reasonable until you multiply it by 10,000 concurrent devices.
What Actually Occurs (Thundering Herd):
14:35:00 - Server goes down for patching
14:35:01 - All 10,000 devices fail their POST, schedule retry at 14:36:01
14:36:01 - 10,000 simultaneous requests hit the server (just rebooted)
14:36:01 - Server overloaded: 8,500 requests time out (500 req/sec capacity)
14:36:01 - Failed devices schedule retry at 14:37:01
14:37:01 - 8,500 retries + 10,000 new scheduled posts = 18,500 simultaneous requests
14:37:01 - Server crashes again under load (cascading failure)
The Numbers:
- Normal load: 10,000 devices ÷ 60 seconds = 167 requests/second (easily handled)
- Thundering herd: 10,000 requests in 1 second = 60x spike (server overload)
- Cascading failure: Each retry adds more synchronized devices, worsening the problem
The Fix: Implement exponential backoff with random jitter:
import random
import time
# ❌ BAD: Fixed retry interval
def post_with_fixed_retry(url, data):
while True:
response = requests.post(url, json=data)
if response.status_code == 200:
return response
time.sleep(60) # All devices retry at same time!
# ✅ GOOD: Exponential backoff with jitter
def post_with_backoff(url, data, max_retries=5):
base_delay = 1 # Start with 1 second
max_delay = 300 # Cap at 5 minutes
for attempt in range(max_retries):
try:
response = requests.post(url, json=data, timeout=10)
if response.status_code == 200:
return response
elif response.status_code == 429: # Rate limited
# Use Retry-After header if provided
retry_after = int(response.headers.get('Retry-After', 0))
if retry_after:
time.sleep(retry_after)
continue
except requests.exceptions.RequestException:
pass # Network error, retry with backoff
# Exponential backoff: 1s, 2s, 4s, 8s, 16s...
delay = min(base_delay * (2 ** attempt), max_delay)
# Add random jitter: +/- 50%
jitter = delay * (0.5 + random.random())
print(f"Retry {attempt+1}/{max_retries} after {jitter:.1f}s")
time.sleep(jitter)
raise Exception("Max retries exceeded")
# Example retry timing for 3 devices (same base, different jitter):
# Device A: 0.7s, 1.8s, 5.2s, 13.1s, 19.3s
# Device B: 1.2s, 2.4s, 3.8s, 10.9s, 24.7s
# Device C: 0.9s, 3.1s, 4.6s, 14.2s, 21.8s
# Result: Retries spread across 25-second window instead of synchronizedRetry Strategy Comparison:
| Strategy | First Retry | Second Retry | Spread | Thundering Herd Risk |
|---|---|---|---|---|
| Fixed 60s | 60s | 60s | 0% | Extreme (all synchronized) |
| Exponential (no jitter) | 1s | 2s | 0% | High (powers of 2 collide) |
| Exponential + 50% jitter | 0.5-1.5s | 1-3s | High | Low (randomized) |
| Full jitter | 0-2s | 0-4s | Maximum | Minimal (uniform distribution) |
Best Practice Configuration:
RETRY_CONFIG = {
'base_delay': 1, # 1 second minimum
'max_delay': 300, # 5 minutes maximum (prevent infinite growth)
'max_retries': 5, # Give up after 5 attempts
'jitter_factor': 0.5, # +/- 50% randomization
}
# Total time before giving up: 1 + 2 + 4 + 8 + 16 = 31 seconds (with jitter: 15-46s)Server-Side Support: Include Retry-After header in 503/429 responses:
# Flask example
@app.route('/api/v1/sensors/<sensor_id>/readings', methods=['POST'])
def post_reading(sensor_id):
if server_overloaded():
return jsonify({'error': 'Server overloaded'}), 503, {
'Retry-After': '120' # Tell clients to wait 2 minutes
}When to Use Different Strategies:
- Fixed retry: Never in production IoT (only for testing)
- Exponential backoff (no jitter): Acceptable for <100 devices
- Exponential + jitter: Standard for 100-10,000 devices (use this by default)
- Full jitter + server-directed backoff: Essential for 10,000+ devices
Putting Numbers to It
Thundering herd synchronization probability:
With 10,000 devices using fixed 60-second retry after a 5-minute outage, what’s the probability they all hit the same second?
Without jitter (all retry at exact 60-second intervals):
\[P(\text{synchronized}) = 1.0\]
All 10,000 devices retry at exactly 14:36:00, 14:37:00, etc.
With 50% jitter (retry between 30-90 seconds):
- Retry window: 60 seconds
- Devices spread uniformly across window
- Expected devices per second: \(\frac{10000}{60} \approx 167\) devices/second
Load comparison:
- No jitter: \(10000\text{ req/sec}\) (60× normal load of 167 req/sec)
- With jitter: \(167\text{ req/sec}\) (same as steady-state!)
Retry timing variance with exponential backoff + jitter:
For attempt \(n\), delay \(d = \min(2^n \times \text{base}, \text{max}) \times (0.5 + U(0,1))\)
Where \(U(0,1)\) is uniform random in [0,1].
Spread across 10,000 devices (first retry, base=1s):
- Mean delay: \(1 \times (0.5 + 0.5) = 1.0\) second
- Standard deviation: \(1 \times \sigma_{U(0,1)} = 1 \times 0.289 = 0.289\) seconds
- 10,000 devices spread over \(\approx 3\sigma = 0.87\) seconds (most devices)
This breaks synchronization completely—devices retry randomly within 0.5-1.5 second window.
Key Takeaway: Without exponential backoff and jitter, even a brief outage can trigger a cascading failure that keeps your system down far longer than the original issue. The “retry in 60 seconds” pattern seems fine for a single device but becomes a distributed denial-of-service attack against your own infrastructure when multiplied by thousands of devices. Always add randomization to break synchronization, and cap the maximum delay to prevent devices from disappearing for hours after a transient error.
2.7.1 Interactive: Exponential Backoff Visualizer
Visualize retry timing strategies to understand thundering herd prevention.
Common Pitfalls
1. Prioritizing Theory Over Measurement in REST API Design
Relying on theoretical models without profiling actual behavior leads to designs that miss performance targets by 2-10×. Always measure the dominant bottleneck in your specific deployment environment — hardware variability, interference, and load patterns routinely differ from textbook assumptions.
2. Ignoring System-Level Trade-offs
Optimizing one parameter in isolation (latency, throughput, energy) without considering impact on others creates systems that excel on benchmarks but fail in production. Document the top three trade-offs before finalizing any design decision and verify with realistic workloads.
3. Skipping Failure Mode Analysis
Most field failures come from edge cases that work in the lab: intermittent connectivity, partial node failure, clock drift, and buffer overflow under peak load. Explicitly design and test failure handling before deployment — retrofitting error recovery after deployment costs 5-10× more than building it in.
2.8 Concept Relationships
REST API design connects to:
Foundation Concepts:
- HTTP and Modern Protocols - HTTP/2 multiplexing and HTTP/3 QUIC for efficient REST APIs
- Application Protocols Overview - When to use REST vs message-based protocols
Design Patterns:
- IoT API Design Best Practices - Naming conventions and payload format selection
- RESTful Architecture - Stateless design and resource modeling
Security:
- API Security - Authentication, authorization, and rate limiting for IoT APIs
- TLS for IoT - Securing REST endpoints with certificates
2.9 See Also
Implementation Guides:
- REST API Design Patterns - Detailed naming conventions and versioning strategies
- OpenAPI for IoT - Documenting REST APIs with OpenAPI 3.0
Comparison Resources:
- CoAP vs HTTP - When RESTful CoAP is better than HTTP for constrained devices
- GraphQL for IoT - Alternative to REST for flexible data queries
Tools:
- Postman - Testing REST APIs with collections
- HTTPie - Command-line HTTP client for API testing
- Swagger UI - Interactive API documentation
2.10 What’s Next?
| Chapter | Focus | Why Read It |
|---|---|---|
| REST API Design Patterns | Naming conventions, payload formats, versioning, security best practices | Deepen practical skills for the patterns introduced in this overview module |
| REST API Worked Examples | Thermostat API design, offline device handling, overhead calculations | Apply concepts hands-on and test understanding with comprehensive quizzes |
| Real-time Protocols | VoIP, SIP, and RTP for audio/video IoT | Explore protocols that go beyond request-response for latency-sensitive streams |
| Protocol Selection Worked Examples | Agricultural sensor network case study | See REST vs MQTT vs CoAP trade-offs evaluated in a real end-to-end design |
| MQTT Fundamentals | Publish-subscribe messaging, broker architecture, QoS levels | Compare the message-based alternative to REST for event-driven IoT pipelines |
| CoAP Fundamentals and Architecture | RESTful protocol for constrained nodes and lossy networks | Implement REST-like semantics over UDP for the most resource-limited devices |
2.11 Self-Assessment Checklist
Before moving on, ensure you can: