Chapters

11 CoAP API Design: Delivery and Security Decisions

coap
api

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.

  1. Is the data critical?
  2. If yes, use CON.
  3. If no, ask whether the data will be sent again soon.
  4. If yes, use NON.
  5. If no, use CON so 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.

  1. Pre-Shared Key (PSK) - Simplest for constrained devices
  2. Raw Public Key (RPK) - No certificate infrastructure needed
  3. 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-Age option to indicate when retry is allowed
  • Log violations for debugging

11.6.4 Interactive Rate Limit Analysis

11.7 Worked Example: Smart Agriculture API

Case Study: Smart Agriculture Sensor Network

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/moisture payload uses compact CBOR with value, unit, and timestamp
  • Critical alerts sensor sends CON POST to .../v1/sensors/field3-42/alert payload includes type, threshold, and current reading
  • Battery monitoring gateway uses GET on .../battery with Observe enabled sensor notifies only when thresholds are crossed
  • Version rollout v1 handles moisture today v2 adds 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 TemperatureResource that stores the latest value and timestamp.
  • Implement render_get() to return JSON with CoAP code 2.05 Content.
  • Implement render_put() to update the reading and call updated_state() for Observe subscribers.
  • Add a ConfigResource for reporting interval settings.
  • Register both resources under the v1/temperature and v1/config paths.
  • Start the server with create_server_context(...), binding to UDP port 5683.

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.value and self.last_updated.
  • Encode a JSON object with value, unit, and timestamp as bytes.
  • Return an aiocoap.Message with content_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 Changed when 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.

  • GET returns 2.05 Content with a JSON payload.
  • PUT returns 2.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-bin on Linux or brew install libcoap on macOS.

  • Read a value: coap-client -m get .../v1/temperature Keep one practical point in view: Update a value:

  • Update a value: coap-client -m put .../v1/temperature body: {"value":23.1} Keep one practical point in view: Observe changes:

  • Observe changes: coap-client -m get -s 60 .../v1/temperature Keep 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:

TX message (50 ms @ 10 mA)=50×103×10×103=0.5 mAsRX ACK (100 ms @ 5 mA)=100×103×5×103=0.5 mAsTotal per message=1.0 mAs=0.278 μAh\begin{align} \text{TX message (50 ms @ 10 mA)} &= 50 \times 10^{-3} \times 10 \times 10^{-3} = 0.5 \text{ mAs} \\ \text{RX ACK (100 ms @ 5 mA)} &= 100 \times 10^{-3} \times 5 \times 10^{-3} = 0.5 \text{ mAs} \\ \text{Total per message} &= 1.0 \text{ mAs} = 0.278 \text{ } \mu\text{Ah} \end{align}

NON (Non-confirmable) message energy:

TX message (50 ms @ 10 mA)=0.5 mAsNo ACK wait=0 mAsTotal per message=0.5 mAs=0.139 μAh\begin{align} \text{TX message (50 ms @ 10 mA)} &= 0.5 \text{ mAs} \\ \text{No ACK wait} &= 0 \text{ mAs} \\ \text{Total per message} &= 0.5 \text{ mAs} = 0.139 \text{ } \mu\text{Ah} \end{align}

Annual comparison (525,600 messages):

CON energy=525,600×0.278=146 mAhSleep energy=0.005×24×365=44 mAhTotal CON=146+44=190 mAh (battery life=220/190=1.16 years)NON energy=525,600×0.139=73 mAhTotal NON=73+44=117 mAhBattery life=220117=1.88 years\begin{align} \text{CON energy} &= 525{,}600 \times 0.278 = 146 \text{ mAh} \\ \text{Sleep energy} &= 0.005 \times 24 \times 365 = 44 \text{ mAh} \\ \text{Total CON} &= 146 + 44 = 190 \text{ mAh (battery life} = 220/190 = 1.16 \text{ years)} \\ \\ \text{NON energy} &= 525{,}600 \times 0.139 = 73 \text{ mAh} \\ \text{Total NON} &= 73 + 44 = 117 \text{ mAh} \\ \text{Battery life} &= \frac{220}{117} = 1.88 \text{ years} \end{align}

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

Common Pitfall: CoAP Observe Notification Flood

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)
Pitfall: HTTP-CoAP Proxy Caching Stale Data

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).

Label the Diagram
Code Challenge
Order the Steps

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.

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.

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.

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.

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.

Match the Concepts

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.

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.