Chapters

10 REST APIs: Versioning and Fleet Operations

app-protocols
rest
api
patterns

Start with the story: A REST API is the map a device, app, or service uses to talk about real things: thermostats, readings, commands, firmware, and alerts. Good design makes those things obvious in the URL, uses HTTP verbs consistently, and returns errors that tell the next engineer what actually happened.

10.1 Start With the Decision

A thermostat API cannot break when old devices miss an update. Versioning, rate limits, and retry rules must protect the whole fleet.

10.2 Route Overview

This is part 2 of 2. Review REST APIs: Payload and Resource Design for the preceding evidence.

10.3 Learning Objectives

  • Choose an API versioning strategy for deployed devices.
  • Design rate-limit, error, security, and cache behavior.

10.4 Chapter Roadmap

  • API Versioning Strategies
  • Understanding API Versioning
  • Checkpoint: Naming the Contract
  • Error Response Format
  • Rate Limiting and Throttling
  • Understanding Rate Limiting
  • Security Best Practices
  • Checkpoint: Protecting the Fleet
  • Case Study: Smart HVAC System API Design
  • Case Study: Smart Building HVAC System
  • Interactive: REST API Explorer Animation
  • Worked Example: API Versioning Strategy for Deployed Thermostats
  • Common Mistake: Ignoring API Rate Limiting on IoT Devices
  • Common Pitfalls
  • 1. Prioritizing Theory Over Measurement in REST API Design Patterns
  • 2. Ignoring System-Level Trade-offs
  • 3. Skipping Failure Mode Analysis
  • Label the Diagram
  • Order the Steps
  • Match the Concepts
  • Deep Dive: Methods, Idempotency, and Conditional Caching
  • Checkpoint: Retry and Cache Semantics
  • Summary
  • Knowledge Check
  • Quiz: REST API Design Patterns
  • What’s Next?

10.5 API Versioning Strategies

IoT systems run for years - versioning prevents breaking deployed devices:

Understanding API Versioning

Core Concept: API versioning provides a contract between API providers and consumers that allows the API to evolve without breaking existing clients.

Why It Matters: IoT devices deployed in the field may run for 5-10 years without firmware updates. Without versioning, any API change (adding required fields, changing response formats, deprecating endpoints) will break thousands of devices simultaneously, causing service outages and costly emergency patches.

Key Takeaway: Always version from day one using URI path versioning (/v1/) for IoT APIs - it is the simplest approach that works across all protocols and is immediately visible in logs and debugging tools.

10.5.2 Header Versioning

GET /temperature
Accept: application/vnd.iot.v1+json

Pros: Clean URLs Cons: Embedded devices may not support custom headers

10.5.3 Query Parameter

coap://sensor.local/temperature?version=1

Pros: Flexible, backward compatible Cons: Easy to forget, adds overhead

IoT-specific recommendation: Use URI versioning (/v1/, /v2/) because:

Put the version in the resource path when constrained clients need the contract to be obvious without custom-header parsing. A path such as /v1/temperature remains visible in device logs, proxy traces, and test fixtures, so a reviewer can tell which schema was used. Apply the same explicit namespace discipline to MQTT topics and CoAP resources where appropriate. The trade-off is duplicated routes during migration, which should be bounded by an owner, compatibility tests, and a retirement date rather than hidden behind an easy-to-forget query parameter.

Broker BexCheckpoint: Naming the Contract

Before using the versioning calculator, verify that the API contract is readable without tribal knowledge:

  • You now know why REST should expose resources such as /devices/, /sensors/, and /readings/ rather than action URLs such as /getTemperature.
  • You now know why MQTT topics need organization, location, device type, device id, and data type fields when a deployment grows to thousands of devices.
  • You now know why URI versioning with /v1/ and /v2/ is the default IoT choice: it is visible in logs, works across protocols, and avoids custom header parsing on constrained clients.

10.5.4 Interactive: API Versioning Migration Cost Calculator

Compare the cost of breaking existing devices (modifying v1) versus maintaining parallel API versions.


10.6 Error Response Format

Consistent error handling reduces debugging time:

Standard error structure (JSON):

{
  "error": {
    "code": "SENSOR_OFFLINE",
    "message": "Device has not reported in 5 minutes",
    "timestamp": "2025-01-15T10:30:00Z",
    "device_id": "sensor-42",
    "retry_after": 300
  }
}

CoAP response codes:

2.01 Created   - Resource created successfully
2.04 Changed   - Resource updated
2.05 Content   - Successful GET with payload
4.00 Bad Request - Invalid syntax
4.04 Not Found - Resource doesn't exist
5.00 Internal Server Error

MQTT error patterns:

# Publish errors to special topics
acme/errors/sensor-42  → {"code": "SENSOR_OFFLINE", ...}

# Or use QoS 0 for best-effort error reporting

10.7 Rate Limiting and Throttling

Protect infrastructure from device misbehavior:

Understanding Rate Limiting

Core Concept: Rate limiting restricts the number of API requests a client can make within a specified time window, protecting servers from overload and ensuring fair resource allocation across clients.

Why It Matters: In IoT systems, a single malfunctioning device or firmware bug can generate thousands of requests per second, overwhelming your cloud infrastructure and causing cascading failures that affect all devices. Rate limiting acts as a circuit breaker that isolates misbehaving devices while keeping the system operational for well-behaved clients.

Key Takeaway: Implement rate limits at multiple levels (per-device, per-tenant, per-endpoint) and always return meaningful error responses (HTTP 429 with Retry-After header) so clients can implement proper backoff strategies rather than hammering your servers.

Patterns:

# Per-device limits
Device temp42: 1 request/second max
Response: 429 Too Many Requests (HTTP)
          4.29 Too Many Requests (CoAP)

# Per-tenant limits
Organization ACME: 10,000 messages/minute
MQTT: Disconnect with reason code (0x97 Quota Exceeded)

Implementation:

  • Use token bucket algorithm (burst allowed, sustained rate limited)
  • Return Retry-After header with backoff time
  • Log violations for debugging misbehaving devices

10.7.1 Interactive: Rate Limiting Cost Impact Calculator

Model the financial impact of missing rate limits when a firmware bug causes devices to over-poll.


10.8 Security Best Practices

Always authenticate and authorize:

# CoAP with DTLS
coaps://sensor.local/v1/temperature  # Note: 's' for secure

# MQTT with TLS + auth
Username: device-42
Password: [device-specific token]
Client Certificate: [for mutual TLS]

# HTTP Bearer tokens
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Design principles:

Protect the write path first: authenticate the caller, authorize the requested resource and method, and reject unauthenticated changes. Use TLS or DTLS across local as well as remote segments because link-layer protection ends at gateways and does not secure every later hop. Give devices distinct credentials, rotate and revoke them through a tested lifecycle, and rate-limit failed authentication without blocking legitimate recovery traffic. Preserve the principal, decision, and failure reason in the audit trail so the controls can be verified rather than merely configured.


Broker BexCheckpoint: Protecting the Fleet

At this point the contract has names, formats, versions, errors, limits, and security boundaries:

  • You now know why a fleet with 50,000 deployed thermostats should add a /api/v2/ response rather than surprising strict v1 parsers with a new field.
  • You now know why 429 Too Many Requests, Retry-After, and a token bucket belong in the design, not only in incident response after a polling bug.
  • You now know why TLS, DTLS, per-request authentication, and credential rotation are part of the REST pattern: stateless APIs cannot rely on server-side sessions to remember trust.

10.9 Case Study: Smart HVAC System API Design

Case Study: Smart Building HVAC System

Requirements:

Read these points as one connected sequence: start with 500 temperature sensors per building; then Real-time alerts for anomalies; then Historical data queries; and finish with Mobile app control.

  • 500 temperature sensors per building
  • Real-time alerts for anomalies
  • Historical data queries
  • Mobile app control

Solution - Hybrid approach:

# 1. MQTT for telemetry (sensors → cloud)
Topic: buildings/bldg1/floor3/zone-a/temp42/reading
Payload (CBOR): {t:23.5, h:45, ts:1642259400}
QoS: 0 (frequent updates, loss acceptable)

# 2. CoAP for control (app → actuators)
PUT coap://hvac.local/v1/zones/zone-a/setpoint
Payload: {"target":22.0}
Type: CON (confirmable - critical command)

# 3. HTTP REST for historical queries (app → cloud)
GET https://api.example.com/v1/sensors/temp42/history?start=2025-01-01
Response (JSON): [{"timestamp":"2025-01-01T00:00:00Z","value":23.5}...]

Why this works:

Read these points as one connected sequence: start with MQTT handles high-volume telemetry efficiently; then CoAP provides low-latency local control; then HTTP enables rich queries from mobile apps; and finish with Each protocol optimized for its use case.

  • MQTT handles high-volume telemetry efficiently
  • CoAP provides low-latency local control
  • HTTP enables rich queries from mobile apps
  • Each protocol optimized for its use case

10.9.1 Choosing the Right Data Serialization Format: A Decision Framework

Payload format choice has outsized impact on constrained IoT devices. Here is a quantitative comparison for a typical sensor reading {"temperature": 23.5, "humidity": 45, "timestamp": 1706140800}:

FormatEncoded SizeHuman ReadableSchema RequiredLibrary Size (C)Parse Speed
JSON62 bytesYesNo5-20 kBModerate
CBOR35 bytesNo (binary)No (self-describing)2-5 kBFast
Protocol Buffers18 bytesNo (binary)Yes (.proto file)30-100 kBVery fast
MessagePack38 bytesNo (binary)No (self-describing)3-8 kBFast
Custom binary12 bytesNoYes (manual)0 kB (hand-coded)Fastest
If your project needs…Choose…Because…
Debugging ease, web dashboard integrationJSONUniversal tooling, browser-native, readable in logs
Compact payloads on constrained networks (LoRaWAN, 6LoWPAN)CBOR40-50% smaller than JSON, self-describing, IETF standard (RFC 8949)
Maximum efficiency with versioned schemasProtocol Buffers70%+ smaller than JSON, strong typing, backward-compatible evolution
Drop-in JSON replacement with size savingsMessagePackJSON-compatible data model, ~40% smaller, minimal code changes
Extreme constraints (<1 kB payload budget)Custom binaryHand-pack fields at bit level, zero overhead, but no interoperability

Quick Decision Flowchart:

Read these points as one connected sequence: start with Is the payload going over LoRaWAN (51-242 byte limit)? Yes —> CBOR or custom binary; then Do both ends share a compiled schema (.proto file)? Yes —> Protocol Buffers; then Is the API consumed by web browsers or curl? Yes —> JSON (use CBOR for device-to-gateway, JSON for gateway-to-cloud); then Do you need a drop-in replacement for JSON with smaller size? Yes —> MessagePack; and finish with Default: CBOR for device-to-gateway communication; JSON for cloud APIs and dashboards.

  1. Is the payload going over LoRaWAN (51-242 byte limit)? Yes —> CBOR or custom binary
  2. Do both ends share a compiled schema (.proto file)? Yes —> Protocol Buffers
  3. Is the API consumed by web browsers or curl? Yes —> JSON (use CBOR for device-to-gateway, JSON for gateway-to-cloud)
  4. Do you need a drop-in replacement for JSON with smaller size? Yes —> MessagePack
  5. Default: CBOR for device-to-gateway communication; JSON for cloud APIs and dashboards

10.9.2 Interactive: Fleet Bandwidth Savings Calculator

Estimate annual bandwidth and cost savings when migrating from JSON to a binary format across your IoT fleet.

Scenario: A smart thermostat manufacturer has 50,000 devices deployed running firmware v1.2 connecting to /api/v1/thermostats. They need to add a new field humidity to the response without breaking existing devices.

Option A: Add field to v1 (breaking change risk):

// Old v1 response (existing devices expect this)
{"temperature": 22.5, "mode": "heat"}

// New v1 response (adding humidity)
{"temperature": 22.5, "mode": "heat", "humidity": 45}

Risk: Devices with strict JSON parsing may reject extra fields
  - Estimated 5% of devices have strict parsers
  - 50,000 × 5% = 2,500 devices broken

Option B: Create /api/v2/ (recommended):

// v1 continues unchanged
GET /api/v1/thermostats/device-042
{"temperature": 22.5, "mode": "heat"}

// v2 includes new field
GET /api/v2/thermostats/device-042
{"temperature": 22.5, "mode": "heat", "humidity": 45}

Migration plan:
  - Year 1: Both v1 and v2 active (0 downtime)
  - Year 2: New firmware uses v2, old devices still on v1
  - Year 3: Deprecation warning on v1
  - Year 4: v1 sunset (98% devices upgraded by this point)

Cost comparison:

  • Option A (modify v1): Emergency firmware push to 2,500 devices, support tickets, reputational damage = $125,000
  • Option B (v2 endpoint): Dev cost for v2 endpoint + maintain both APIs for 2 years = $45,000

Decision: Create v2 endpoint. Savings: $80,000 + zero downtime.

Common Mistake: Ignoring API Rate Limiting on IoT Devices

The Error: Not implementing rate limits on device APIs, assuming “our devices are well-behaved.” A firmware bug causes 1,000 devices to poll every 100 ms instead of every 5 minutes.

Real Impact:

Normal traffic: 1,000 devices × 12 requests/hour = 12K req/hour
Bug traffic: 1,000 devices × 36,000 requests/hour = 36M req/hour (3,000× increase)

AWS API Gateway cost:
  12K req/hour: $0.04/hour = $29/month (normal)
  36M req/hour: $120/hour = $86,400/month (bug)

Damage: $86K bill + service outage for ALL devices (API throttled)

The Fix: Implement per-device rate limiting:

# Return 429 Too Many Requests after 20 requests/minute
@app.route('/api/v1/temperature')
@rate_limit(max_requests=20, window=60)  # 20 per minute per device
def get_temperature():
    return jsonify({"temp": 22.5})

Result: Bug triggers rate limit, affects only buggy devices. Bill capped at $150/month. Service continues for well-behaved devices.

Common Pitfalls

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.

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.

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.

Label the Diagram
Order the Steps
Match the Concepts

10.10 Deep Dive: Methods, Idempotency, and Conditional Caching

The patterns above cover URIs, versioning, and payloads. This layered walkthrough fills in the semantics that make a REST API survive a lossy IoT link: what each HTTP method promises, why idempotency decides whether a device can safely retry, and how ETag validators let a constrained device poll without re-downloading unchanged data.

REST models a system as resources named by URIs and acts on them with a small, agreed set of HTTP methods. A threshold should live under a noun such as /devices/42/config; the action belongs in PATCH or PUT, not in a custom endpoint like POST /setThreshold. Stable nouns make permissions, logs, test fixtures, and retry behavior easier to audit because each resource can list its allowed methods and expected outcomes once.

ShapeExampleDesign use
Collection/devicesList devices with GET; create with POST when the server assigns ids
Item/devices/42Read, replace, or delete one known device
Sub-resource/devices/42/readingsScope related data to its owner without inventing verbs
Anti-pattern/setDeviceConfigRPC-style URL hides method semantics from caches, proxies, and reviewers

Two method properties matter most on lossy IoT links. A method is safe when it makes no intended state change, so clients may cache, prefetch, or repeat it. A method is idempotent when doing it many times leaves the same state as doing it once. When a device times out after sending a request, it often cannot tell whether the server received it; idempotency decides whether the retry is harmless.

MethodSafe / idempotentTypical IoT use
GETSafe, idempotentRead a sensor value or config; success is usually 200 OK
PUTNot safe, idempotentReplace a complete config resource; success is usually 200 OK or 204 No Content
PATCHNot safe, not guaranteed idempotentPartially update config; make patches idempotent when devices may retry
POSTNot safe, not idempotentCreate a resource or enqueue a command; use 201 Created or 202 Accepted
DELETENot safe, idempotentRemove a rule or device; repeated deletes should not recreate state

A device that times out after PUT /devices/42/config can safely resend the same full representation, because two identical PUTs leave the same configuration. A blind retry of POST /commands can enqueue two commands. For retry-safe creation, either PUT to a client-chosen URI or send an idempotency key that the server deduplicates. Document that retry rule beside every method in the API contract.

Conditional requests solve the other expensive retry pattern: polling unchanged configuration. The server tags each representation with an ETag, such as ETag: "cfg-v7". The device stores that value and asks the server to send the body only if the representation changed:

GET /devices/42/config HTTP/1.1
Host: api.example
If-None-Match: "cfg-v7"

HTTP/1.1 304 Not Modified
ETag: "cfg-v7"
Cache-Control: max-age=30
ServerDeviceServerDeviceHas stored ETag cfg-v7alt[config unchanged][config changed]GET /config with If-None-Match cfg-v7304 Not Modified (no body)200 OK, new body, new ETag

If nothing changed, 304 Not Modified confirms the device is current without transferring the body. If the config changed, the server returns 200 OK, the new body, and a new ETag. The same validator protects writes: If-Match: "cfg-v7" on a PUT tells the server to apply the update only if the resource is still at version 7; otherwise it returns 412 Precondition Failed and avoids overwriting another client’s change.

Cache-Control decides how intermediaries may reuse the response. max-age=60 lets a gateway reuse a device list for 60 seconds, no-cache allows storage but requires revalidation before use, and no-store is the safer choice for tokens and secrets. A release test should cover all three validator cases: unchanged reads return 304 without a body, changed reads return 200 with a new ETag, and stale writes fail with 412.

For each REST endpoint, document three retry facts beside the request example: whether the method is idempotent, whether clients need an idempotency key, and which validator or cache header proves the response can be reused.

Broker BexCheckpoint: Retry and Cache Semantics

The deep dive turns REST naming into operational behavior:

  • You now know why repeated PUT can be safe for a full config replacement, while blind POST /commands retries can enqueue duplicate commands.
  • You now know how ETag, If-None-Match, 304 Not Modified, If-Match, and 412 Precondition Failed prevent wasted downloads and stale writes.
  • You now know what to document for every endpoint before release: idempotency, idempotency keys, validators, cache headers, and the status code a device should expect after a retry.

10.11 Summary

This chapter covered practical REST API design patterns for IoT systems:

Key topics:

Read these points as one connected sequence: start with RESTful vs message-based patterns: Request-response for control, pub-sub for events; then Topic and URI naming: Consistent hierarchies for MQTT topics and CoAP URIs; then Payload format selection: JSON for debugging, CBOR/Protobuf for efficiency; then API versioning: URI versioning recommended for simplicity and compatibility; then Rate limiting and throttling: Token bucket algorithm, Retry-After headers; and finish with Security: TLS/DTLS, authentication on every request, credential rotation.

  • RESTful vs message-based patterns: Request-response for control, pub-sub for events
  • Topic and URI naming: Consistent hierarchies for MQTT topics and CoAP URIs
  • Payload format selection: JSON for debugging, CBOR/Protobuf for efficiency
  • API versioning: URI versioning recommended for simplicity and compatibility
  • Rate limiting and throttling: Token bucket algorithm, Retry-After headers
  • Security: TLS/DTLS, authentication on every request, credential rotation

10.12 Knowledge Check

Quiz: REST API Design Patterns

10.13 What’s Next?

ChapterFocusWhy Read It
REST API PracticeHands-on API design scenariosPractice applying the patterns from this chapter to thermostat and fleet management case studies with step-by-step walkthroughs
MQTT FundamentalsPublish-subscribe protocol deep-diveUnderstand QoS levels, retained messages, Last Will and Testament, and broker architecture that make MQTT the dominant IoT telemetry protocol
CoAP Fundamentals and ArchitectureConstrained Application ProtocolLearn how CoAP implements REST semantics over UDP with confirmable messages, observe mode, and resource discovery for low-power devices
Real-Time Protocol WorkflowsStreaming and real-time IoT dataExplore WebSockets, Server-Sent Events, and RTSP for applications where sub-second latency matters — video surveillance, voice, and live monitoring
Protocol Selection Worked ExamplesMulti-protocol comparisonWalk through decision frameworks for choosing between HTTP, MQTT, CoAP, and AMQP across six real IoT deployment scenarios
Application Protocols OverviewProtocol landscape and comparisonReview the full technical comparison of IoT application-layer protocols as a reference when making architecture decisions for new projects

10.14 Continue Your Route

This final part closes the route from API Versioning Strategies through What’s Next?. Return to REST APIs: Payload and Resource Design or continue from the app-protocols module index.