11 CoAP API Design: Delivery and Security Decisions
11.1 Start With the Decision
A sleepy node may lose a confirmable message or repeat a command. The API must pair message type with a security rule that keeps the action safe.
11.2 Route Overview
This is part 2 of 2. Review CoAP API Design: Resource and Response Contracts for the preceding evidence.
11.3 Learning Objectives
- Choose confirmable or non-confirmable delivery for each operation.
- Apply CoAP security controls to a constrained deployment.
11.4 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
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:
Read these points as one connected sequence: start with Actuator commands (LED on/off, valve open/close); then Configuration changes; then Alerts and alarms; and finish with Any operation where failure must be detected.
- 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:
Read these points as one connected sequence: start with Frequent sensor readings (temperature every minute); then Telemetry streams; then Status updates; and finish with Any data where next update supersedes previous.
- Frequent sensor readings (temperature every minute)
- Telemetry streams
- Status updates
- Any data where next update supersedes previous
Decision tree:
Read these points as one connected sequence: start with Is the data critical?; then If yes, use CON; then If no, ask whether the data will be sent again soon; then If yes, use NON; and finish with If no, use CON so you can detect loss.
- 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
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 Always Use DTLS in Production
coaps://sensor.local/v1/temperature # Secure CoAP
Authentication options:
Read these points as one connected sequence: start with Pre-Shared Key (PSK) - Simplest for constrained devices; then Raw Public Key (RPK) - No certificate infrastructure needed; and finish with X.509 Certificates - Enterprise deployments.
- 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, Use DTLS (coaps://) not plain CoAP (coap://); 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.
- Use DTLS (coaps://) not plain CoAP (coap://)
- 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:
Read these points as one connected sequence: start with Response code: 4.29 Too Many Requests; then Error key: RATE_LIMIT_EXCEEDED; then Example limit: 10 requests/minute; and finish with Retry hint: retry_after = 45.
- Response code:
4.29 Too Many Requests - Error key:
RATE_LIMIT_EXCEEDED - Example limit:
10 requests/minute - Retry hint:
retry_after = 45
Implementation strategies:
Read these points as one connected sequence: start with Token bucket algorithm (allow bursts, limit sustained rate); then Return Max-Age option to indicate when retry is allowed; and finish with Log violations for debugging.
- 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
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 ensures critical alerts aren’t lost
- CBOR minimizes bandwidth
- Observe pattern prevents polling battery status
- Versioning allows gradual upgrades
11.8 Working Code: Python CoAP Client and Server
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:
Read these points as one connected sequence: start with Create a TemperatureResource that stores the latest value and timestamp; then Implement render_get() to return JSON with CoAP code 2.05 Content; then Implement render_put() to update the reading and call updated_state() for Observe subscribers; then Add a ConfigResource for reporting interval settings; then Register both resources under the v1/temperature and v1/config paths; and finish with Start the server with create_server_context(...), binding to UDP port 5683.
- 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 these points as one connected sequence: start with Read self.value and self.last_updated; then Encode a JSON object with value, unit, and timestamp as bytes; and finish with Return an aiocoap.Message with content_format=50.
- 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:
Read these points as one connected sequence: start with Create a client context with create_client_context(); then Build a GET message for .../v1/temperature; then Decode the JSON payload returned with 2.05 Content; then Build a PUT message carrying a JSON body with the new value; and finish with Expect 2.04 Changed when the update succeeds.
- 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:
Read these points as one connected sequence: start with GET returns 2.05 Content with a JSON payload; then PUT returns 2.04 Changed; then Wrong URI returns 4.04 Not Found; and finish with Unsupported method returns 4.05 Method Not Allowed.
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
Read these points as one connected sequence: start with Install the CLI with apt install libcoap2-bin on Linux or brew install libcoap on macOS; and finish with Read a value:
-
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
Scenario: Battery sensor reports every 60 seconds for 1 year using CR2032 (220 mAh @ 3V).
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
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:
Read these points as one connected sequence: start with Set minimum notification intervals (5-60 seconds); then Implement change thresholds (only notify on significant changes); then Use Max-Age option to tell clients how long values are valid; and finish with Monitor client RST responses (indicates overwhelmed client).
- 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)
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:
@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 stale
Key principle: Safety-critical clients should always request fresh data (max-age=0).
11.10 Concept Relationships
This chapter on CoAP API design connects to several key concepts:
Builds on:
Read these points as one connected sequence: start with CoAP Message Format - Response codes (2.xx/4.xx/5.xx) and content formats used in API design; then CoAP Introduction - REST principles and resource-based addressing; and finish with CoAP Message Types - CON vs NON selection for different API endpoints.
- 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:
Read these points as one connected sequence: start with MQTT API Patterns - Alternative publish-subscribe API design approach; then HTTP API Design - Traditional web API patterns adapted for IoT; and finish with Security Best Practices - DTLS and access control implementation.
- 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:
Read these points as one connected sequence: start with CoAP Implementation Labs - Building production-ready CoAP servers; and finish with CoAP Decision Framework - Choosing CoAP vs other protocols for your use case.
- CoAP Implementation Labs - Building production-ready CoAP servers
- CoAP Decision Framework - Choosing CoAP vs other protocols for your use case
11.11 See Also
Related Chapters:
Read these points as one connected sequence: start with CoAP Advanced Features - Block-wise transfer, Observe extension, resource discovery; then CoAP Resource Contract and Negotiation Design - Review URI/method/content-format/code contracts, negotiation failures, cache freshness, and proxy mapping; then RESTful Architecture Principles - Foundation concepts for API design; and finish with IoT Protocol Selection - When to use CoAP vs MQTT vs HTTP.
- 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 these points as one connected sequence: start with RFC 7252 - CoAP Specification - Official protocol definition; then CoRE Link Format (RFC 6690) - Resource discovery format; and finish with CBOR (RFC 8949) - Efficient binary encoding for payloads.
- 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
Read these points as one connected sequence: start with CoAP Decision Framework - protocol selection for CoAP vs MQTT vs HTTP in a real deployment; then CoAP Advanced Features - block-wise transfer and Observe for larger or more dynamic APIs; then CoAP Implementation Labs - hands-on server and client work with aiocoap and libcoap; then CoAP Fundamentals and Architecture - revisit the chapter index and fill any remaining foundation gaps; then MQTT API Patterns - compare request-response APIs against publish-subscribe messaging; and finish with IoT Protocol Selection - evaluate CoAP, MQTT, HTTP, and AMQP side by side.
- 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
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 final part closes the route from Message Type Selection through Summary. Return to CoAP API Design: Resource and Response Contracts or continue from the coap module index.
