1217 CoAP API Design Best Practices
1217.1 Learning Objectives
By the end of this chapter, you will be able to:
- Design RESTful CoAP APIs: Structure resources as nouns with proper URI conventions
- Select Content Formats: Choose between JSON, CBOR, and text/plain for payloads
- Implement Error Handling: Use proper CoAP response codes with helpful error messages
- Apply Security Practices: Configure DTLS, rate limiting, and access control
1217.2 Prerequisites
Before diving into this chapter, you should be familiar with:
- CoAP Introduction - CoAP basics and REST concepts
- CoAP Message Format - Response codes and options
1217.3 RESTful Resource Design
Core Concept: CoAP follows REST principles - design resources as nouns, use HTTP-like methods as verbs. The URI identifies WHAT youโre accessing, the method specifies HOW.
Why It Matters: Consistent RESTful design makes APIs intuitive for developers, enables caching, supports proxying, and allows standard tooling to work seamlessly.
Key Takeaway: Good resource design: coap://sensor.local/v1/temperature (noun). Bad design: coap://sensor.local/getTemperature (verb in URL).
1217.3.1 Good vs. Bad Resource Design
Good resource design (nouns):
coap://sensor.local/v1/temperature # Resource (noun)
coap://sensor.local/v1/humidity # Resource (noun)
coap://actuator.local/v1/led/state # Resource (noun)
coap://gateway.local/v1/config/network # Resource (noun)
Poor resource design (verbs - avoid):
coap://sensor.local/getTemperature # Verb in URL - wrong!
coap://actuator.local/turnOnLED # Verb in URL - wrong!
REST operations on resources:
GET coap://sensor.local/v1/temperature # Read current value
PUT coap://actuator.local/v1/led/state # Update LED state
POST coap://gateway.local/v1/logs # Create new log entry
DELETE coap://gateway.local/v1/logs/2025-01 # Delete old logs
1217.3.2 URI Naming Conventions
CoAP URIs should be short (constrained bandwidth) but descriptive:
Recommended structure:
coap://{host}/{version}/{resource_type}/{device_id}/{subresource}
Examples:
coap://sensors.local/v1/devices/temp42/reading
coap://sensors.local/v1/devices/temp42/metadata
coap://sensors.local/v1/devices/temp42/config
Best practices:
| Practice | Example | Rationale |
|---|---|---|
| Version your API | /v1/ |
Allows future changes without breaking clients |
| Use plural nouns | /devices/temp42 |
Consistent collection semantics |
| Keep it short | Every character counts | Constrained networks |
| Lowercase with hyphens | /motion-sensors |
URL standard |
| Avoid deep nesting | Max 3-4 levels | /devices/sensors/temp/reading is too deep |
1217.3.3 Payload Format Selection
Choose content format based on your constraints:
| Content-Format | Code | Size | Use Case |
|---|---|---|---|
| text/plain | 0 | Minimal | Simple sensor values: โ23.5โ |
| application/json | 50 | Large | Development, debugging |
| application/cbor | 60 | Small | Production constrained devices |
| application/octet-stream | 42 | Varies | Binary sensor data |
Example - Temperature reading in different formats:
# Text/plain (4 bytes) - best for simple values
"23.5"
# JSON (42 bytes) - good for debugging
{"device":"temp42","value":23.5,"unit":"C"}
# CBOR (20 bytes) - production choice
A3 66 64 65 76 69 63 65 66 74 65 6D 70 34 32...Recommendation:
- Battery sensors: Use text/plain or CBOR (minimize bytes)
- Development: Use JSON (easy debugging)
- Production: Use CBOR (efficient, supports rich structures)
1217.3.4 Versioning Strategy
IoT devices often run for years - plan for API evolution:
URI versioning (recommended for CoAP):
coap://sensor.local/v1/temperature # Original API
coap://sensor.local/v2/temperature # New version with metadata
Why URI versioning for IoT: - Simple for embedded clients - No custom header parsing needed - Clear in logs and debugging - Works with all CoAP libraries
Version migration example:
# v1: Simple temperature reading
GET coap://sensor.local/v1/temperature
Response: "23.5"
# v2: Rich metadata added
GET coap://sensor.local/v2/temperature
Response (CBOR): {"value":23.5,"unit":"C","timestamp":1642259400}
# Legacy devices continue using v1
# New devices adopt v2 when convenient
1217.4 Error Handling
Use proper CoAP response codes and provide helpful error payloads:
1217.4.1 Standard Response Codes
2.01 Created # POST created new resource
2.04 Changed # PUT updated resource
2.05 Content # GET successful with payload
4.00 Bad Request # Invalid syntax
4.01 Unauthorized # Authentication required
4.04 Not Found # Resource doesn't exist
4.05 Method Not Allowed # GET on write-only resource
5.00 Internal Server Error
1217.4.2 Error Payload Format
JSON for human readability:
{
"error": {
"code": "SENSOR_OFFLINE",
"message": "Device has not reported in 5 minutes",
"timestamp": "2025-01-15T10:30:00Z",
"device_id": "temp42",
"retry_after": 300
}
}CBOR for production (more efficient):
{1: "SENSOR_OFFLINE", 2: "Device offline", 3: 1642259400, 4: "temp42", 5: 300}
1217.5 Message Type Selection
Choose between CON and NON based on criticality:
Use CON (Confirmable) for: - Actuator commands (LED on/off, valve open/close) - Configuration changes - Alerts and alarms - Any operation where failure must be detected
Use NON (Non-confirmable) for: - Frequent sensor readings (temperature every minute) - Telemetry streams - Status updates - Any data where next update supersedes previous
Decision tree:
Is the data critical? -> YES -> CON
-> NO -> Will data be sent again soon?
-> YES -> NON
-> NO -> CON (don't risk losing it)
1217.6 Security Best Practices
1217.6.1 Always Use DTLS in Production
coaps://sensor.local/v1/temperature # Secure CoAP
Authentication options: 1. Pre-Shared Key (PSK) - Simplest for constrained devices 2. Raw Public Key (RPK) - No certificate infrastructure needed 3. X.509 Certificates - Enterprise deployments
1217.6.2 Security Checklist
1217.6.3 Rate Limiting
Protect your system from misbehaving devices:
Per-device limits:
CoAP response code: 4.29 Too Many Requests
Payload (JSON):
{
"error": "RATE_LIMIT_EXCEEDED",
"limit": "10 requests/minute",
"retry_after": 45
}
Implementation strategies: - Token bucket algorithm (allow bursts, limit sustained rate) - Return Max-Age option to indicate when retry is allowed - Log violations for debugging
1217.7 Worked Example: Smart Agriculture API
Scenario: 200 soil moisture sensors across a farm, battery-powered, reporting to a central gateway.
API Design:
# Resource structure
coap://gateway.local/v1/sensors/{sensor_id}/moisture
coap://gateway.local/v1/sensors/{sensor_id}/battery
coap://gateway.local/v1/sensors/{sensor_id}/config
# Normal operation (NON messages for efficiency)
Sensor -> Gateway: NON POST /v1/sensors/field3-42/moisture
Payload (CBOR): {value: 35, unit: "%", timestamp: 1642259400}
# Critical alerts (CON for reliability)
Sensor -> Gateway: CON POST /v1/sensors/field3-42/alert
Payload (CBOR): {type: "LOW_MOISTURE", threshold: 20, current: 15}
# Gateway observes battery levels
Gateway -> Sensor: GET /v1/sensors/field3-42/battery (Observe: 0)
Sensor -> Gateway: Notifications when battery drops below thresholds
# Versioning for future expansion
v1: Basic moisture reporting
v2: Adds soil temperature and pH
(v1 sensors continue working while v2 rolls out)
Why this works: - NON messages save battery (no ACK overhead) - CON ensures critical alerts arenโt lost - CBOR minimizes bandwidth - Observe pattern prevents polling battery status - Versioning allows gradual upgrades
1217.8 Common Pitfalls
The mistake: Configuring a server to send Observe notifications on every minor resource change, overwhelming clients.
Symptoms: - Client device becomes unresponsive or crashes - Battery drains rapidly (constant wake-ups) - Network congestion with notification traffic - Client sends RST messages repeatedly
Why it happens: Developers bind notifications directly to sensor sampling rates (e.g., 10 Hz accelerometer) without throttling.
The fix: Implement server-side notification throttling:
# BAD: Notify on every sensor reading
@coap_resource('/temperature')
def on_read():
current_temp = read_sensor()
notify_observers(current_temp) # Called 10x/second!
# GOOD: Throttle with change threshold
MIN_NOTIFY_INTERVAL = 5.0 # seconds
CHANGE_THRESHOLD = 0.5 # degrees
@coap_resource('/temperature')
def on_read():
current_temp = read_sensor()
should_notify = (
abs(current_temp - last_notified_temp) >= CHANGE_THRESHOLD or
(time.time() - last_notify_time) >= MIN_NOTIFY_INTERVAL
)
if should_notify:
notify_observers(current_temp)
last_notified_temp = current_temp
last_notify_time = time.time()Prevention: - Set minimum notification intervals (5-60 seconds) - Implement change thresholds (only notify on significant changes) - Use Max-Age option to tell clients how long values are valid - Monitor client RST responses (indicates overwhelmed client)
The Mistake: HTTP-to-CoAP proxy aggressively caches based on Max-Age without considering that freshness requirements vary by use case.
The Fix: Implement per-client cache control at the proxy:
@app.route('/coap/<path:resource>')
async def proxy_coap(resource):
# Client-specified freshness requirement
client_max_age = int(request.headers.get('Cache-Control', 'max-age=60').split('=')[1])
# Check cache with client's freshness requirement
if coap_uri in cache:
response, cached_time, server_max_age = cache[coap_uri]
age = time.time() - cached_time
effective_max_age = min(client_max_age, server_max_age)
if age < effective_max_age:
return response.payload # Cache hit
# ... fetch from device if staleKey principle: Safety-critical clients should always request fresh data (max-age=0).
1217.9 Summary
CoAP API design follows REST principles with IoT optimizations:
- Resource design: Nouns for URIs, methods for verbs
- Content format: CBOR for production, JSON for debugging
- Versioning: URI-based (
/v1/) for simplicity - Error handling: Standard response codes + helpful payloads
- Security: Always use DTLS in production
- Rate limiting: Protect against misbehaving devices
Key decisions: - Keep URIs short but descriptive - Use CON for commands, NON for telemetry - Implement notification throttling for Observe - Plan for multi-year device lifetimes with versioning
1217.10 Whatโs Next
Now that you understand API design:
- Protocol Selection: CoAP Decision Framework - When to use CoAP vs MQTT vs HTTP
- Hands-On Practice: CoAP Features and Labs - Build working CoAP implementations
- See All CoAP Topics: CoAP Fundamentals and Architecture - Complete chapter index