10  CoAP Resource Negotiation Patterns

coap
api-design
content-negotiation
caching

10.1 Start With the Wrong Format

A client asks for CBOR, the server only has JSON, and a proxy still has an older cached reading. The human description of the API may sound fine, but the machine contract decides whether the client receives a valid representation, a clear error, or stale data.

Use this page to make each resource tuple explicit: URI, method, request format, response format, response code, freshness rule, and Observe behavior. That tuple is the review artifact.

10.2 Learning Objectives

After this page, you should be able to:

  • Specify each CoAP resource as a URI, method, content-format, and response-code contract.
  • Choose response codes that let constrained clients branch on success, client error, or server error without parsing prose.
  • Distinguish Accept negotiation failures from unsupported request Content-Format failures.
  • Review Max-Age, ETag, Observe throttling, and HTTP-CoAP proxy mappings for stale-data and flooding risks.

10.3 Why This Contract Comes After API Design

CoAP Resource API Design introduces the API surface for constrained devices: noun-based resource paths, compact payload formats, error handling, security, rate limits, and implementation checks. This page narrows in on the contract details that make that API predictable for machines rather than merely readable for humans.

The CoAP resource design point stays explicit: resource design is the review object, and the resource design record covers the path, media type, response code, cache rule, and error behavior as one contract.

Use it when a resource table needs sign-off, a gateway has to translate between HTTP and CoAP, a client receives the wrong representation format, or a cache/Observe policy is serving stale values or flooding a sleepy device.

Overview: Model Resources as Nouns; Let Methods Be the Verbs

A CoAP API is a set of addressable resources, and good design starts by naming them as nouns arranged in a hierarchy: a collection /sensors, an item /sensors/temp, a sub-resource /sensors/temp/value. The action never belongs in the URI. Paths like /getTemperature or /setValve duplicate what the method already says — GET reads a representation, PUT writes one, POST creates or triggers, DELETE removes. Putting the verb in the path breaks caching and confuses the contract.

Keep paths short, because every Uri-Path segment is bytes on a constrained link, and each segment is its own option. The real contract a client codes against is the tuple {URI, method, content-format, response code}: which resource, which verb, what representation it exchanges, and what codes it can return. Nail that tuple down per resource and the API becomes reviewable rather than guessed at.

For example, a freezer sensor can expose /v1/freezers/f17/temp for readings, /v1/freezers/f17/cfg for the sampling interval, and /v1/freezers/f17/alarm for the current alarm state. A dashboard reads temperature with GET and receives 2.05 Content, a commissioning tool updates the interval with PUT and expects 2.04 Changed, and the gateway creates a maintenance note with POST under /v1/freezers/f17/events. Those names stay short, but the method/code pair still tells a reviewer exactly what changed.

CoAP RESTful operations showing GET, POST, and PUT requests against resource URIs with matching CoAP success response codes.
Resource URIs remain noun-based while GET, POST, and PUT carry the action and response codes carry the programmable outcome.

Nouns, not verbs

/sensors/temp, not /getTemp. The method carries the action already.

Collections and items

/sensors lists; /sensors/temp is one item; /sensors/temp/value is a field.

Short paths

Every segment is an option and costs bytes; brevity is a performance choice on constrained links.

The contract

{URI, method, content-format, response code} is what clients build against — define it per resource.

Practitioner: Response Codes Are the Contract, and Negotiation Is Explicit

Return the correct code; never wrap a failure inside a 2.05 Content with an error message in the body. A machine client branches on the code class first, so an honest code is what makes the API programmable. Creation returns 2.01 Created, an update returns 2.04 Changed, a read returns 2.05 Content, a delete returns 2.02 Deleted; client mistakes use class 4 and server faults use class 5.

Situation
Code
Class
Design note
Created a resource
2.01 Created
Success
Include Location options so the client learns the new URI.
Client asked for a format you can't produce
4.06 Not Acceptable
Client error
Driven by the request's Accept option (17).
Request body in a format you don't support
4.15 Unsupported Content-Format
Client error
Driven by the request's Content-Format option (12).
Transient overload
5.03 Service Unavailable
Server error
May carry Max-Age as a retry-after hint.

Content negotiation is explicit and worth designing deliberately. The client's Accept option (17) states the format it wants back; if the server cannot produce it, the honest answer is 4.06 Not Acceptable, not a wrong-format body. A request body whose Content-Format (12) the server does not understand earns 4.15. For interoperable sensor data, prefer CBOR (60) or, better, SenML (RFC 8428, formats 110/112), whose {n, u, v, t} records let unrelated clients parse the same readings without a bespoke schema.

A useful test case is an actuator configuration endpoint that accepts SenML CBOR for production and JSON only on a lab build. If the lab client asks for JSON with Accept 50, the server can return a readable 2.05 body. If a deployed meter asks for JSON where only CBOR is enabled, the correct answer is 4.06. If it uploads JSON to a CBOR-only PUT endpoint, the answer is 4.15. Those distinctions prevent clients from guessing whether the resource, method, or representation failed.

Under the Hood: Cacheable by Design, and Clean Across an HTTP-CoAP Proxy

A resource is only as cache-friendly as its Max-Age (option 14, default 60 s) is honest. Advertise a freshness that matches how fast the value really changes: a reading that updates every few seconds must not claim a five-minute Max-Age, or a caching proxy will serve stale data to every downstream client. Set it too low and you defeat caching entirely and wake the device needlessly. Pair readable resources with an ETag so a proxy can revalidate with 2.03 Valid, and mark observable resources so clients subscribe instead of poll — but rate-limit those notifications, because an unthrottled Observe on a noisy sensor floods the link.

Many CoAP APIs are reached from the web through a cross-protocol proxy, and RFC 8075 defines how HTTP and CoAP map to each other. The proxy translates methods and codes both ways — 2.05 Content maps to HTTP 200 OK, 2.01 Created to 201, 4.04 Not Found to 404 — and transcodes representations such as CBOR to JSON. Designing your codes and content-formats so they map cleanly means one resource can serve both a constrained CoAP node and an ordinary web client without special cases.

Two recurring pitfalls come straight from these mechanics: a proxy serving stale data because Max-Age was set larger than the real change rate, and an Observe notification flood because a fast-changing resource pushed on every reading instead of on a meaningful change threshold. Both are API-design faults, not protocol bugs.

Honest Max-Age

Match advertised freshness to the true change rate so proxies never serve stale readings.

ETag revalidation

Lets a cache confirm freshness with 2.03 Valid instead of refetching the whole body.

Clean code mapping

Codes that map to HTTP (2.05→200, 4.04→404) let one resource serve CoAP and web clients.

Throttled Observe

Notify on a change threshold, not every sample, to avoid flooding constrained links.

10.4 Release Checklist

Before signing off a CoAP resource API, verify that:

  • Every resource row names URI, method, content-format, success code, error codes, and allowed message type.
  • URI paths use short noun segments, a clear version strategy, and no action verbs in the path.
  • Accept failures return 4.06 Not Acceptable, while unsupported request bodies return 4.15 Unsupported Content-Format.
  • Creation, update, read, delete, overload, and missing-resource outcomes use distinct response codes.
  • Max-Age matches the real change rate, ETags support revalidation where useful, and proxy cache behavior is tested.
  • Observe notifications are throttled by threshold or cadence, not by raw sensor sampling rate.
  • HTTP-CoAP proxy mappings and any CBOR/JSON transcoding are documented with failure behavior.

10.5 See Also

10.6 Next

After this API contract pass, continue with CoAP Observe Registration and Freshness Contracts for long-running resource relationships that need freshness and liveness guarantees.