11 CoAP API Design: Delivery and Security Decisions
More
Depth: Practitioner
Progress saves as you read. Mark sections complete as you go; your progress appears in the sidebar.
11.1 Start With the Decision
This section: Start With the Decision — A sleepy node may lose a confirmable message, one that asks for an acknowledgement, or repeat a command.
A sleepy node may lose a confirmable message, one that asks for an acknowledgement, or repeat a command. Its API, the set of requests another program may make, must pair the message type, the delivery choice, with a security rule that keeps the action safe.
11.2 Route Overview
This section: Route Overview — This is part 2 of 2.
This is part 2 of 2. Review CoAP API Design: Resource and Response Contracts for the preceding evidence.
11.3 Learning Objectives
This section: Learning Objectives
- Choose confirmable or non-confirmable delivery for each operation.
- Apply CoAP security controls to a constrained deployment.
11.4 Chapter Roadmap
This section: Chapter Roadmap
- Message Type Selection
- Security Best Practices
- Worked Example: Smart Agriculture API
- Case Study: Smart Agriculture Sensor Network
- Working Code: Python CoAP Client and Server
- Putting Numbers to It: CoAP Message Types and Battery Life
- Common Pitfalls
- Common Pitfall: CoAP Observe Notification Flood
- Pitfall: HTTP-CoAP Proxy Caching Stale Data
- Label the Diagram
- Code Challenge
- Order the Steps
- Concept Relationships
- See Also
- Match the Concepts
- What’s Next
- Summary
11.5 Message Type Selection
This section: Message Type Selection — A CoAP message type expresses the delivery policy needed by a particular resource operation or update.
A CoAP message type expresses the delivery policy needed by a particular resource operation or update. Confirmable messages support failure detection for actuator commands, configuration changes, and important alerts. Non-confirmable messages suit frequent telemetry when a later reading replaces a missed update. The selection depends on criticality and update timing rather than using the same exchange policy for both valve commands and routine temperature reports.
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?
- If yes, use
CON. - If no, ask whether the data will be sent again soon.
- If yes, use
NON. - If no, use
CONso you can detect loss.
11.6 Security Best Practices
This section: Security Best Practices — A protected CoAP service combines transport protection with controls on resource access and request behavior.
A protected CoAP service combines transport protection with controls on resource access and request behavior. DTLS protects the transport path, while access control decides which clients may use particular resources. Input validation and rate limits address malformed or excessive requests that encryption alone does not resolve. Credential rotation and session limits remain separate operating responsibilities after the protected connection has been established.
11.6.1 Protect Production CoAP Exchanges
coaps://sensor.local/v1/temperature # Secure CoAP
Authentication options:
- Pre-Shared Key (PSK) - Simplest for constrained devices
- Raw Public Key (RPK) - No certificate infrastructure needed
- X.509 Certificates - Enterprise deployments
11.6.2 Security Checklist
Work through this verification in order: first, Protect the production exchange with DTLS (coaps://) or an appropriate OSCORE deployment; then Implement access control (not all clients can access all resources); then Rate-limit requests (prevent DoS); then Validate input (prevent injection attacks); then Use short session timeouts (minimize exposure); and finally Rotate credentials regularly.
- Protect the production exchange with DTLS (coaps://) or an appropriate OSCORE deployment
- Implement access control (not all clients can access all resources)
- Rate-limit requests (prevent DoS)
- Validate input (prevent injection attacks)
- Use short session timeouts (minimize exposure)
- Rotate credentials regularly
11.6.3 Rate Limiting
Protect your system from misbehaving devices:
Per-device limits:
- Response code:
4.29 Too Many Requests - Error key:
RATE_LIMIT_EXCEEDED - Example limit:
10 requests/minute - Retry hint:
retry_after = 45
Implementation strategies:
- Token bucket algorithm (allow bursts, limit sustained rate)
- Return
Max-Ageoption to indicate when retry is allowed - Log violations for debugging
11.6.4 Interactive Rate Limit Analysis
11.7 Worked Example: Smart Agriculture API
This section: Worked Example: Smart Agriculture API
Scenario: 200 soil moisture sensors across a farm, battery-powered, reporting to a central gateway.
API Design:
- Resource structure
.../v1/sensors/{sensor_id}/moisture.../v1/sensors/{sensor_id}/battery.../v1/sensors/{sensor_id}/config - Normal operation
sensor sends NON POST to
.../v1/sensors/field3-42/moisturepayload uses compact CBOR with value, unit, and timestamp - Critical alerts
sensor sends CON POST to
.../v1/sensors/field3-42/alertpayload includes type, threshold, and current reading - Battery monitoring
gateway uses GET on
.../batterywith Observe enabled sensor notifies only when thresholds are crossed - Version rollout
v1handles moisture todayv2adds soil temperature and pH later
Why this works:
- NON messages save battery (no ACK overhead)
- CON provides ACK and retransmission feedback for critical alerts; the application still verifies the physical outcome
- CBOR minimizes bandwidth
- Observe pattern prevents polling battery status
- Versioning allows gradual upgrades
11.8 Working Code: Python CoAP Client and Server
This section: Working Code: Python CoAP Client and Server — Real request/response examples using aiocoap (Python) and the coap-client CLI.
Real request/response examples using aiocoap (Python) and the coap-client CLI.
11.8.1 Python CoAP Server (Gateway)
The gateway server only needs a few moving parts:
- Create a
TemperatureResourcethat stores the latest value and timestamp. - Implement
render_get()to return JSON with CoAP code2.05 Content. - Implement
render_put()to update the reading and callupdated_state()for Observe subscribers. - Add a
ConfigResourcefor reporting interval settings. - Register both resources under the
v1/temperatureandv1/configpaths. - Start the server with
create_server_context(...), binding to UDP port5683.
Minimal GET response flow:
- Read
self.valueandself.last_updated. - Encode a JSON object with value, unit, and timestamp as bytes.
- Return an
aiocoap.Messagewithcontent_format=50.
11.8.2 Python CoAP Client (Sensor)
The sensor client loop is similarly compact:
- Create a client context with
create_client_context(). - Build a GET message for
.../v1/temperature. - Decode the JSON payload returned with
2.05 Content. - Build a PUT message carrying a JSON body with the new value.
- Expect
2.04 Changedwhen the update succeeds.
What to verify during testing:
GETreturns2.05 Contentwith a JSON payload.PUTreturns2.04 Changed.- Wrong URI returns
4.04 Not Found. - Unsupported method returns
4.05 Method Not Allowed.
11.8.3 CLI Testing with coap-client
-
Install the CLI with
apt install libcoap2-binon Linux orbrew install libcoapon macOS. -
Read a value:
coap-client -m get.../v1/temperatureKeep one practical point in view: Update a value: -
Update a value:
coap-client -m put.../v1/temperaturebody:{"value":23.1}Keep one practical point in view: Observe changes: -
Observe changes:
coap-client -m get -s 60.../v1/temperatureKeep one practical point in view: Discover resources: -
Discover resources:
coap-client -m get.../.well-known/core
Illustrative charge model: A battery sensor reports every 60 seconds for 1 year using a nominal 220 mAh CR2032 at 3 V. These assumed current and time values are not a measured device profile.
CON (Confirmable) message energy:
NON (Non-confirmable) message energy:
Annual comparison (525,600 messages):
Result: CON drains battery in 14 months; NON achieves 23-month target. 62% longer battery life with NON.
11.8.4 Interactive Battery Life Optimizer
11.9 Common Pitfalls
This section: Common Pitfalls
An Observe server can overwhelm clients when notifications follow minor sensor changes without a separate delivery policy. Repeated wakeups consume battery energy, while excessive traffic can make a client unresponsive and produce repeated reset responses. A minimum notification interval and a meaningful change threshold separate sensor sampling from useful notification delivery. Max-Age communicates value validity, and client reset responses provide evidence that the notification policy needs review.
The mistake: Configuring a server to send Observe notifications on every minor resource change, overwhelming clients.
Symptoms:
Read these points as one connected sequence: start with Client device becomes unresponsive or crashes; then Battery drains rapidly (constant wake-ups); then Network congestion with notification traffic; and finish with Client sends RST messages repeatedly.
- 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 (may indicate an unrecognized notification or lost observation state)
An HTTP-to-CoAP proxy can return stale readings when one cache policy is applied to clients with different freshness needs. Max-Age describes a validity interval, but that interval must still fit the receiving use case. Per-client cache control allows the proxy to distinguish routine reuse from a request that needs fresh evidence. The safety-critical path in this example requests fresh data rather than accepting a cached value merely because the proxy retains it.
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:
ALLOWED_COAP_TARGETS = {'temperature': 'coap://sensor.local/temperature'}
@app.route('/coap/<path:resource>')
async def proxy_coap(resource):
# Resolve only configured targets; never construct a target from raw input.
coap_uri = ALLOWED_COAP_TARGETS.get(resource)
if coap_uri is None:
abort(404)
# HTTP Cache-Control controls this client's use of the proxy cache.
directives = {}
for part in request.headers.get('Cache-Control', 'max-age=60').split(','):
name, separator, value = part.strip().partition('=')
name = name.strip().lower()
if name == 'max-age' and (name in directives or not separator or
not value.strip().isdecimal()):
abort(400)
directives[name] = value.strip()
client_max_age = int(directives.get('max-age', 60))
if 'no-cache' in directives or 'no-store' in directives:
client_max_age = 0
# 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 stale
Key principle: For safety-critical reads through this HTTP proxy, request Cache-Control: max-age=0 and verify the reading timestamp. CoAP Max-Age itself is a response freshness option, not a client request option.
11.10 Concept Relationships
This section: Concept Relationships — Review response codes and options in the message format, then revisit the REST principles that shape this API.
This chapter on CoAP API design connects to several key concepts:
Builds on:
Review response codes and options in the message format, then revisit the REST principles that shape this API.
- CoAP Message Format - Response codes (2.xx/4.xx/5.xx) and content formats used in API design
- CoAP Introduction - REST principles and resource-based addressing
- CoAP Message Types - CON vs NON selection for different API endpoints
Relates to:
Compare MQTT publish and subscribe APIs with HTTP resource APIs before choosing how clients interact with the service.
- MQTT API Patterns - Alternative publish-subscribe API design approach
- HTTP API Design - Traditional web API patterns adapted for IoT
- Security Best Practices - DTLS and access control implementation
Enables:
Build the API in the labs. Then use the decision guide to check whether CoAP fits.
- CoAP Implementation Labs - Building production-ready CoAP servers
- CoAP Decision Framework - Choosing CoAP vs other protocols for your use case
11.11 See Also
This section: See Also — Review advanced CoAP features before refining the resource contract, since block transfer and Observe affect API behavior.
Related Chapters:
Review advanced CoAP features before refining the resource contract, since block transfer and Observe affect API behavior.
- CoAP Advanced Features - Block-wise transfer, Observe extension, resource discovery
- CoAP Resource Contract and Negotiation Design - Review URI/method/content-format/code contracts, negotiation failures, cache freshness, and proxy mapping
- RESTful Architecture Principles - Foundation concepts for API design
- IoT Protocol Selection - When to use CoAP vs MQTT vs HTTP
External Resources:
Read the CoAP rules first. Then use the CoRE link format to describe resources.
- RFC 7252 - CoAP Specification - Official protocol definition
- CoRE Link Format (RFC 6690) - Resource discovery format
- CBOR (RFC 8949) - Efficient binary encoding for payloads
11.12 What’s Next
This section: What’s Next — Use the decision framework to choose CoAP for the service, then study advanced features before building the API.
Use the decision framework to choose CoAP for the service, then study advanced features before building the API.
- CoAP Decision Framework - protocol selection for CoAP vs MQTT vs HTTP in a real deployment.
- CoAP Advanced Features - block-wise transfer and Observe for larger or more dynamic APIs.
- CoAP Implementation Labs - hands-on server and client work with
aiocoapandlibcoap. - CoAP Fundamentals and Architecture - revisit the chapter index and fill any remaining foundation gaps.
- MQTT API Patterns - compare request-response APIs against publish-subscribe messaging.
- IoT Protocol Selection - evaluate CoAP, MQTT, HTTP, and AMQP side by side.
11.13 Summary
This section: Summary — CoAP API design applies REST principles to constrained devices.
CoAP API design applies REST principles to constrained devices. Resources should be nouns, methods should carry the action, payloads should stay compact, and discovery metadata should help clients understand available endpoints without hard-coded assumptions.
11.14 Continue Your Route
This section: Continue Your Route — This is the last part of this chapter.
This is the last part of this chapter. Return to CoAP API Design: Resource and Response Contracts or continue from the coap module index.
Find help
3 reviewed answers.
Where should I start in a module?
Open the module page from the sidebar to see its chapters in order. Use the module map to return to a topic you need.
Modules and pathsReviewed
What can I try when a project does not work?
Check the symptom, then work through the diagnostic steps. Keep the error message and the smallest example that reproduces it.
When you are stuckReviewed
How do interactive examples work?
Use the interactive guide to check how examples and simulations work before changing your project.
Interactive examples guideReviewed
