Start with the story: The design rules become real when you have to choose the exact URL, method, status code, and payload for a smart thermostat or offline device. Each worked example turns an IoT situation into an API decision you can test.
In 60 Seconds
This chapter provides hands-on practice designing RESTful IoT APIs through worked examples covering resource hierarchies, proper HTTP status codes for device states, offline device handling, protocol selection between CoAP/MQTT/HTTP, and calculating protocol overhead impact on battery life.
Phoebe’s Field Notes: What The Idealised 79-Year Battery Number Leaves Out
Phoebe’s Why
The 1000 mAh nameplate on the soil-moisture sensor’s battery is a charge rating, not an energy rating – it only becomes a usable energy budget once it is multiplied by the cell’s voltage, and that voltage is not the fixed number on the label. Every time the radio keys up, the cell’s own internal resistance drags the terminal voltage down below open-circuit, exactly during the burst that the mAh ledger is trying to account for. And the mAh figure itself is not fully spendable: some of it leaks away continuously as self-discharge whether or not the sensor ever transmits, and a derating margin has to stay in reserve so the electronics do not brown out before the label’s number is reached. The chapter’s own aside – “realistically a few years once self-discharge and temperature are included” – is the right instinct; the numbers below work out what “realistically” actually means.
The Derivation
Charge only becomes energy once voltage multiplies in:
A cell sags under load by its own internal resistance:
\[V_{term} = V_{oc} - I\,R_{int}\]
Usable charge is nameplate charge after a reserved cutoff/temperature margin \(\delta\):
\[Q_{usable} = Q_{nameplate}\times(1-\delta)\]
Self-discharge drains that same budget at a roughly constant rate even when the radio never keys up, so the realistic life divides usable charge by active consumption plus self-discharge together:
Worked Numbers: This Chapter’s Own Soil-Sensor Battery
Using the chapter’s own \(1000\) mAh cell and CoAP’s own \(0.86\) s/day of active time over 4 cycles: per-cycle active time \(= 0.86/4 = 0.215\) s, and the chapter’s own \(0.00267\) mAh per cycle implies an average active current \(I = (0.00267\times3600)/0.215 = 44.7\) mA.
Sag (catalog-typical Li-SOCl2 primary cell, \(V_{oc}=3.6\) V, \(R_{int}=15\ \Omega\), a chemistry chosen for its very low self-discharge on multi-year deployments): \(\Delta V = 0.0447\times15 = 0.671\) V, so \(V_{term} = 3.6-0.671 = 2.93\) V during every active burst – a real, repeatable dip the mAh ledger never shows.
Usable budget (30% reserved for cold-temperature capacity loss and cutoff-voltage headroom): \(Q_{usable} = 1000\times0.70 = 700\) mAh \(\to E_{usable} = 0.700\times3.6 = 2.52\) Wh
Self-discharge (catalog-typical \(1\%\)/year for Li-SOCl2): \(\approx 0.01\times1000 = 10.0\) mAh/yr, roughly constant against the near-full nameplate
All three protocols still clear the 2-year target by a wide margin, so the chapter’s protocol recommendation stands – but the idealised 27% CoAP-over-MQTT advantage (79 vs 62.5 yr) shrinks to about 15% (30.9 vs 26.9 yr) once self-discharge is added, because that 10.0 mAh/yr leak is the same fixed tax on every protocol. Self-discharge, not protocol overhead, is now the dominant term in the ledger.
Chapter Roadmap
This is a long worked-example chapter, so use it as a sequence of design decisions:
First convert thermostat requirements into resource paths, methods, responses, and request load.
Then handle an offline tracker without confusing stale data with missing resources.
Next use the quizzes to test status codes, protocol choice, and byte-level overhead.
After that use the calculators to scale requests, payloads, battery life, and fleet bandwidth.
Finally read one HTTP exchange like a device contract and design the parking API yourself.
Checkpoints recap what each section has established, and the longer calculators can be treated as deep-dive tools on a first pass.
7.1 Learning Objectives
By the end of this chapter, you will be able to:
Design RESTful IoT APIs: Construct resource hierarchies that apply REST constraints to multi-device deployments
Implement Proper Error Handling: Select and apply the correct HTTP status codes for all device states including offline and missing resources
Diagnose Offline Device Scenarios: Distinguish between “no data” (404), “stale data” (200 with metadata), and “service unavailable” (503) in IoT REST APIs
Compare Protocol Trade-offs: Evaluate CoAP, MQTT, and HTTP against deployment constraints such as battery life, latency, and network topology
Calculate Protocol Overhead: Assess the byte-level impact of protocol choice on battery life and justify the selection for constrained IoT devices
Key Concepts
Core Concept: Fundamental principle underlying REST API Practice — understanding this enables all downstream design decisions
Key Metric: Primary quantitative measure for evaluating REST API Practice performance in real deployments
Trade-off: Central tension in REST API Practice design — optimizing one parameter typically degrades another
Protocol/Algorithm: Standard approach or algorithm most commonly used in REST API Practice implementations
Deployment Consideration: Practical factor that must be addressed when deploying REST API Practice in production
Common Pattern: Recurring design pattern in REST API Practice that solves the most frequent implementation challenges
Performance Benchmark: Reference values for REST API Practice performance metrics that indicate healthy vs. problematic operation
7.2 For Beginners: REST API Examples
This chapter walks through complete REST API designs for real IoT scenarios. You will see how to create endpoints for reading sensor data, controlling actuators, and managing device fleets. Each example includes the URL structure, request format, and response format, so you can use them as templates for your own projects.
Sensor Squad: Building a Real API
“Let’s build an actual API for our smart garden!” said Max the Microcontroller, rolling up his sleeves. “Sammy, you’re the temperature sensor at /api/v1/sensors/garden-temp. When someone sends a GET request to your URL, you reply with your current reading in JSON.”
Sammy practiced: “GET /api/v1/sensors/garden-temp and I respond with {\"value\": 24.5, \"unit\": \"celsius\", \"timestamp\": \"2026-02-07T10:30:00Z\"}. Easy!”
“Now for the sprinkler control,” Lila the LED continued. “POST to /api/v1/actuators/sprinkler/commands with a body like {\"action\": \"water\", \"duration_minutes\": 15}. The sprinkler turns on and returns a 202 Accepted status – meaning ‘got it, working on it’ – because watering takes time.”
Bella the Battery added the fleet management angle: “And GET /api/v1/devices?status=low-battery returns all devices that need charging. See how the URL pattern is consistent? Resources are nouns, actions use HTTP verbs, and filters go in query parameters. Follow the pattern and every developer who sees your API will instantly understand it!”
7.3 Prerequisites
Before diving into this chapter, you should be familiar with:
This chapter provides hands-on practice with REST API design through worked examples and comprehensive quizzes.
7.5 Worked Examples: REST API Design for IoT
These worked examples demonstrate practical REST API design decisions for real-world IoT scenarios.
The first example is deliberately concrete: one thermostat fleet, one mobile app, and a few endpoints that must stay predictable as the deployment grows.
Worked Example: Designing a Smart Thermostat REST API
Scenario: You are building a REST API for a smart thermostat system that allows mobile apps to read current temperature, set target temperature, and retrieve historical data. The system has 500 deployed thermostats.
Given:
Thermostat reports temperature every 30 seconds
Users want real-time display and control from mobile app
Historical data retention: 30 days
Expected concurrent mobile app users: 2,000
Steps:
Define resource hierarchy - Organize resources around the device:
/api/v1/thermostats/{device_id} # Device info
/api/v1/thermostats/{device_id}/temperature # Current reading
/api/v1/thermostats/{device_id}/setpoint # Target temperature
/api/v1/thermostats/{device_id}/history # Historical readings
Choose HTTP methods for each operation:
GET /thermostats/thermo-42/temperature - Read current temp (idempotent)
PUT /thermostats/thermo-42/setpoint - Set target (full update, idempotent)
GET /thermostats/thermo-42/history?start=2025-01-01&end=2025-01-15 - Query with filters
Design response format with proper status codes:
//GET/thermostats/thermo-42/temperature//Response:200OK{"device_id":"thermo-42","current_temp_c":22.5,"humidity_pct":45,"timestamp":"2025-01-15T10:30:00Z","unit":"celsius"}//PUT/thermostats/thermo-42/setpointwithbody{"target_c":21.0}//Response:200OK(or204NoContent){"device_id":"thermo-42","target_c":21.0,"estimated_time_minutes":15}//GET/thermostats/nonexistent/temperature//Response:404NotFound{"error":"DEVICE_NOT_FOUND","message":"Device 'nonexistent' is not registered","timestamp":"2025-01-15T10:30:00Z"}
Result: A clean, RESTful API with predictable endpoints, proper HTTP semantics, and clear error handling. The hierarchy /thermostats/{id}/resource scales to thousands of devices while remaining intuitive for developers.
Putting Numbers to It
For a deployment of 500 thermostats reporting every 30 seconds, calculate the API request load:
For historical data queries returning 2,880 readings per thermostat per day (30-second intervals):
\[\text{Query payload} = 2880 \times 120 \text{ bytes} = 345 \text{ KB per device per day}\]
With 2,000 concurrent users, if 10% query historical data daily, that’s 200 users × 345 KB = 69 MB/day for historical queries. Total bandwidth: 533 + 69 = 602 MB/day, well within most cloud tier limits.
Key Insight: REST API design should follow the principle of resource-oriented design - model your API around nouns (thermostat, temperature, setpoint) not verbs (getTemperature, setTarget). The HTTP methods (GET, PUT, POST, DELETE) provide the verbs. This makes the API self-documenting and consistent across all resources.
Checkpoint: Resource Modeling
You now know:
A 500-thermostat fleet reporting every 30 seconds produces about 16.67 requests per second.
Keeping resources as nouns lets GET, PUT, and query filters carry the action without inventing verb-style URLs.
The same hierarchy can return current readings, setpoints, history, and clean 404 Not Found errors without changing the API shape.
Worked Example: Handling Device Offline State in REST APIs
Scenario: A fleet management system has 1,000 GPS trackers on delivery trucks. Some trucks lose cellular connectivity in remote areas. Your REST API must handle requests for offline devices gracefully without confusing mobile app users.
Given:
GPS trackers report location every 60 seconds when connected
Some trucks go offline for hours in low-coverage areas
Mobile dispatch app needs last known location even when device is offline
App users must clearly understand device connectivity status
Steps:
Define “offline” threshold and track last-seen timestamp:
Use appropriate HTTP status codes for different scenarios:
@app.route('/api/v1/vehicles/<vehicle_id>/location')def get_location(vehicle_id): vehicle = db.get_vehicle(vehicle_id)ifnot vehicle:# Device never registeredreturn {"error": "VEHICLE_NOT_FOUND"}, 404 location = cache.get_last_location(vehicle_id)ifnot location:# Device registered but never reported locationreturn {"error": "NO_LOCATION_DATA","message": "Device has not reported location yet"}, 404# Return cached location with connectivity status# Use 200 OK - we have valid data, just stalereturn {"vehicle_id": vehicle_id,"latitude": location.lat,"longitude": location.lng,"timestamp": location.timestamp.isoformat(),"connectivity": get_connectivity_status(vehicle_id) }, 200
Result: The API returns 200 OK with the last known location and explicit connectivity metadata, allowing the mobile app to display “Last seen 2 hours ago at [location]” rather than showing an error. The 404 status is reserved for truly missing resources (unknown vehicle ID).
Key Insight: For IoT REST APIs, distinguish between “no data” and “stale data”. A device being offline is not an error condition - it’s expected state information. Return cached/stale data with metadata about freshness rather than failing with 503 Service Unavailable. This keeps mobile apps functional even with intermittent device connectivity.
Checkpoint: Offline Devices
You now know:
A tracker can be considered offline after 180 seconds without heartbeat, but that does not make the resource disappear.
200 OK fits cached location plus connectivity metadata; 404 is for unknown vehicles or missing data.
503 Service Unavailable is reserved for cases where the service cannot fulfill the request and should usually include retry guidance.
Quick Check: Offline Device Handling
The next few quizzes switch from reading examples to making choices. Treat each answer as a client-behavior contract: the status code tells firmware whether to retry, fix input, refresh credentials, or stop.
Code Challenge
7.6 Key Takeaways
Label the Diagram
Order the Steps
Match the Concepts
7.7 Summary
REST API Design Principles:
Model APIs around resources (nouns), not actions (verbs) – use HTTP methods (GET, PUT, POST, DELETE) as the verbs
Organize resource hierarchies: /api/v1/{collection}/{id}/{sub-resource} scales to thousands of devices
Include API versioning in the URL path (e.g., /api/v1/) for backward compatibility
Offline Device Handling:
Return 200 OK with stale data plus connectivity metadata for offline devices with cached data
Reserve 404 for truly missing resources (unknown device ID) and 503 for temporarily unavailable services
Include last_seen, offline_duration, and data_freshness fields so clients can make informed decisions
Protocol Selection for IoT:
CoAP over UDP: Best for battery-powered sensors with sporadic transmission (4-byte header, no TCP handshake)
MQTT over TCP: Best for publish-subscribe patterns with multiple subscribers (dashboards, cloud analytics)
Hybrid architectures (CoAP local + MQTT cloud) combine the strengths of both patterns
Common Pitfalls:
Returning 503 for offline devices when cached data is available (confuses mobile apps)
Using HTTP for battery-constrained sensors (92% protocol overhead for small payloads)
Designing verb-based endpoints (/getTemperature) instead of resource-based (/temperature)
Checkpoint: Status and Protocol Choices
You now know:
201 Created belongs to successful device registration, while 200 OK belongs to successful reads or cached stale data.
For a 4-byte payload over CoAP/UDP/IPv4/Ethernet, the chapter’s 46 header bytes make protocol overhead 92%.
CoAP fits constrained request-response sensors; MQTT fits publish-subscribe monitoring; HTTP is useful but expensive for tiny battery messages.
7.8 Knowledge Check: Matching and Sequencing
7.9 Quiz: Comprehensive Protocol Review
Quiz 2: Comprehensive Review
Decision Framework: When to Return 404 vs 503 vs 200 with Stale Data
Scenario: Your IoT REST API receives a request for device data, but the device is offline. What HTTP status code should you return?
Include Retry-After: 60 header to suggest retry timing
Key principle: IoT APIs should distinguish between “resource doesn’t exist” (404), “resource exists but unavailable” (503), and “resource available but data is stale” (200 with metadata).
7.10 Concept Relationships
Understanding how REST API design concepts interconnect:
Resource hierarchies determine endpoint structure, which directly impacts HTTP status code selection (404 vs 503 for missing resources)
Protocol overhead calculations (CoAP vs MQTT battery impact) inform protocol selection decisions for battery-powered deployments
Hybrid architectures (CoAP local + MQTT cloud) leverage strengths of both request-response and publish-subscribe patterns
Offline device handling requires distinguishing between no data (404) and stale data (200 with metadata), a pattern that extends to all IoT REST APIs
7.11 Interactive Calculators
The calculators turn the earlier examples into sliders. Use them after the quizzes to check whether an API shape still works when device count, report interval, payload size, or battery capacity changes.
7.11.1 REST API Request Load Calculator
Estimate the API request rate and daily bandwidth for your IoT device fleet.
Request rate comes from device count divided by report interval, so growth can break an API before the endpoint design changes.
Payload and header bytes both matter; the thermostat example combined 250 bytes of HTTP overhead with 120 bytes of JSON payload.
Battery estimates depend on radio-on time as well as protocol headers, which is why a small byte saving may still matter in a sleep-heavy device.
7.12 Deep Dive: Reading an HTTP Message Like an Engineer
The worked examples above build whole endpoints. This layered walkthrough zooms into a single message on the wire: what the status-code classes promise, what each header in a request and response is doing, and how to choose the status code that tells a device exactly what to do next.
A REST response is a control signal for firmware, not just a message for a dashboard. The first digit of the status code sets the device action: 2xx means the request succeeded, 3xx means use a different or cached representation, 4xx means the client must change something, and 5xx means the server or upstream path failed. The exact code refines that action. For example, 202 Accepted fits a command queued for a sleeping device, while 204 No Content would falsely imply the action already completed.
Here is the device-registration exchange as a contract rather than a snippet:
HTTP/1.1 201 Created
Location: /v1/devices/42
Content-Type: application/json
Content-Length: 48
{"id":42,"name":"gh2-temp","type":"temperature"}
Authorization proves who is calling before anything is created. Content-Type tells the server and client how to parse the body. Content-Length must match the actual byte count so parsers can detect truncation or smuggling mistakes. Location is the authoritative URL for the new resource; the device should persist /v1/devices/42 instead of guessing its assigned identifier.
Symptom
Right status
Device action
Malformed JSON body
400 Bad Request
Fix the payload; do not retry unchanged
Missing or expired token
401 Unauthorized
Refresh credentials, then retry
Token lacks scope
403 Forbidden
Stop or request authorization; retrying as-is will not help
Unknown device id
404 Not Found
Correct the resource path or provisioning record
Over rate limit
429 Too Many Requests
Back off and honor Retry-After
Gateway/server overloaded
503 Service Unavailable
Retry later with backoff and honor Retry-After
flowchart TD
A["Request failed"] --> B{"Client or server fault?"}
B -->|Client| C{"Which client problem?"}
B -->|Server overloaded| S["503 + Retry-After<br/>back off, then retry"]
C -->|Malformed body| E1["400 Bad Request"]
C -->|Expired token| E2["401 Unauthorized"]
C -->|Missing scope| E3["403 Forbidden"]
C -->|Unknown resource| E4["404 Not Found"]
C -->|Over rate limit| E5["429 Too Many Requests"]
The two fleet-scale traps are status masking and retry amplification. Returning 200 OK with an error buried in the body tells caches, proxies, client libraries, and dashboards that the request succeeded. Returning 500 for a bad client payload makes devices retry a request that can never succeed. For sleeping or overloaded infrastructure, 503 Service Unavailable plus Retry-After: 300 turns a thundering-herd retry loop into an orderly wait.
For each worked example, write the status code beside the expected firmware action: retry now, retry after delay, refresh credential, change payload, re-provision resource, or stop. That line is the API contract devices actually execute.
Checkpoint: Message Contracts
You now know:
Authorization, Content-Type, Content-Length, and Location are part of the API contract, not decorative headers.
400, 401, 403, 404, 429, and 503 each imply a different device action.
Returning 200 OK with a hidden error or 500 for bad input can amplify retries across a fleet.
Hands-On Exercise: Design a Smart Parking REST API
Scenario: Create a REST API for a city parking system with 1,000 parking spots across 10 parking lots. Each spot has a magnetic sensor reporting occupied/vacant status.
Requirements:
Mobile app needs real-time spot availability
City operators need daily/monthly occupancy reports
Parking enforcement needs to verify payment for specific spots
System must handle sensor offline gracefully
Tasks:
Design the resource hierarchy (parking lots, spots, sensors, reports)
Define HTTP endpoints with proper methods (GET, POST, PUT)
Specify status codes for: spot found occupied, spot not found, sensor offline
Calculate protocol overhead: Compare HTTP vs CoAP for 1,000 sensors reporting every 30 seconds
Design offline device handling: What should GET /lots/3/spots/42/status return if sensor hasn’t reported in 2 hours?
Verification Questions:
How many endpoints did you define? (Hint: At least 6-8 for full CRUD operations)
Did you distinguish between “spot doesn’t exist” (404) and “sensor offline” (200 with stale data)?
What’s the daily bandwidth consumption difference between HTTP and CoAP for your design?
Sample Solution Sketch:
GET /lots # List all parking lots
GET /lots/{lotId}/spots # List spots in lot
GET /lots/{lotId}/spots/{spotId} # Get specific spot status
PUT /lots/{lotId}/spots/{spotId} # Update spot (internal only)
GET /reports/occupancy?date=... # Occupancy reports
For offline sensors:
GET /lots/3/spots/42 → 200 OK
{
"spot_id": 42,
"status": "occupied",
"last_updated": "2026-02-09T08:15:00Z",
"sensor": {
"status": "offline",
"last_seen": "2026-02-09T08:15:00Z",
"offline_duration_minutes": 120
}
}
7.15 What’s Next?
REST API Design Patterns Focus: Versioning, payload formats, pagination, rate limiting Why read it: Apply the design principles from this chapter to build production-ready API contracts
Application Protocols Overview Focus: Side-by-side comparison of CoAP, MQTT, and HTTP Why read it: Deepen the protocol selection skills practised in the quizzes above
Protocol Selection Worked Examples Focus: Agricultural sensor network end-to-end case study Why read it: See a full deployment decision from sensor hardware to cloud using the same frameworks
Real-Time Protocol Workflows Focus: VoIP, SIP, RTP for audio/video IoT Why read it: Extend your protocol knowledge to streaming and real-time communication patterns
MQTT Fundamentals Focus: Broker architecture, QoS levels, topic design Why read it: Implement the MQTT cloud-reporting leg of the hybrid architectures covered here
CoAP Fundamentals and Architecture Focus: CON/NON messages, observe, block transfer Why read it: Implement the CoAP local-control leg and understand the confirmable message reliability model