Nouns, not verbs
/sensors/temp, not /getTemp. The method carries the action already.
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.
After this page, you should be able to:
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.
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.
/sensors/temp, not /getTemp. The method carries the action already.
/sensors lists; /sensors/temp is one item; /sensors/temp/value is a field.
Every segment is an option and costs bytes; brevity is a performance choice on constrained links.
{URI, method, content-format, response code} is what clients build against — define it per resource.
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.
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.
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.
Match advertised freshness to the true change rate so proxies never serve stale readings.
Lets a cache confirm freshness with 2.03 Valid instead of refetching the whole body.
Codes that map to HTTP (2.05→200, 4.04→404) let one resource serve CoAP and web clients.
Notify on a change threshold, not every sample, to avoid flooding constrained links.
Before signing off a CoAP resource API, verify that:
4.06 Not Acceptable, while unsupported request bodies return 4.15 Unsupported Content-Format.After this API contract pass, continue with CoAP Observe Registration and Freshness Contracts for long-running resource relationships that need freshness and liveness guarantees.