Chapters

11 CoAP API Design: Delivery and Security Decisions

coap
api

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:

  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

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:

  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, 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-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

This section: 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 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 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 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:

  • 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:

  • 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

  • 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

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:

TX message (50 ms @ 10 mA)=50×10−3×10×10−3=0.5 mAsRX ACK (100 ms @ 5 mA)=100×10−3×5×10−3=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

This section: 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:

  • 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)
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:

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.

Label the Diagram
Code Challenge
Order the Steps

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.

Relates to:

Compare MQTT publish and subscribe APIs with HTTP resource APIs before choosing how clients interact with the service.

Enables:

Build the API in the labs. Then use the decision guide to check whether CoAP fits.

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.

External Resources:

Read the CoAP rules first. Then use the CoRE link format to describe resources.

Match the Concepts

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.

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 paths

Reviewed

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 stuck

Reviewed

How do interactive examples work?

Use the interactive guide to check how examples and simulations work before changing your project.

Interactive examples guide

Reviewed