Nouns, not verbs
/sensors/temp, not /getTemp. The method carries the action already.
An air-quality display expects a detailed temperature record, while a small bridge can decode only a plain number. Both reach the same resource, yet they cannot safely interpret the same content. Negotiation makes that difference visible before a plausible value reaches the screen.
Make One Resource Contract Fail Clearly
Picture an air sensor that can send a short number or a detailed record. A dashboard asks for the detailed form, but an old bridge understands only the short one. A safe interface must prevent a plausible but wrong reading.
An application programming interface means a named way for programs to exchange requests and results; it is called an API. A protocol means shared rules for an exchange. Constrained Application Protocol (CoAP) means a compact request method for small devices. Hypertext Transfer Protocol (HTTP) means a request-and-response format used by web systems. JavaScript Object Notation (JSON) means a text format built from named values. A payload means the useful data inside one message. A gateway means the bridge between local devices and a wider network.
Request each form, send an unknown form, omit the unit, repeat a change, and restart the gateway. Each side should agree on the chosen form or return a clear refusal. No guessed value should enter the history.
This runway does not define every field or version rule. The deeper sections show how resource names, formats, negotiation, errors, caches, and release evidence fit together.
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 a clear default is to name them as nouns arranged in a hierarchy: a collection /sensors, an item /sensors/temp, a sub-resource /sensors/temp/value. Paths like /getTemperature or /setValve often duplicate what the method already says — GET reads a representation, PUT writes one, POST creates or triggers, DELETE removes. Verb-shaped paths are not forbidden and do not by themselves disable caching, but noun-based resource names usually make the method and cache contract easier to reason about; actual cacheability still follows the method, response code, and cache-control options.
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.
A resource contract is coherent only when its URI, method, representation, and success response tell the same story. Inspect the three-exchange diagram in Figure 12.1 before adding content negotiation or versioning to that contract.
At the top of Figure 12.1, CON GET targets coap://sensor/temperature and returns ACK 2.05 Content with the temperature representation; the noun names what is read. The middle CON POST sends configuration data to coap://sensor/config, then ACK 2.01 Created identifies the new /config/7 location. The final CON PUT replaces the known coap://sensor/threshold state and receives ACK 2.04 Changed. Together, those concrete pairs show why method and response code—not verbs embedded in the path—carry the action and outcome.
/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 methods, status codes, and selected metadata map to each other. For example, 2.05 Content can map to HTTP 200 OK, 2.01 Created to 201, and 4.04 Not Found to 404. Representation transcoding, such as converting CBOR to JSON, is a separate implementation choice rather than a behavior guaranteed by that mapping. Design codes and content-formats so the proxy can preserve the contract, and document any transcoding explicitly.
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.
Consider a sensor whose supported representation is a JSON record with a value and unit. A client requests a different format through Accept. If the server cannot provide that form, the contract calls for Not Acceptable, rather than a successful response carrying whatever bytes were convenient. The client must branch on the code before attempting to decode content.
The reverse exchange tests another boundary. Suppose a client writes a configuration using an unsupported request Content-Format. That failure is Unsupported Content-Format, because the server cannot interpret the submitted representation. These refusals answer different questions: what the client can receive, and what the server can consume. Collapsing them into one generic error loses a useful diagnosis.
For a numerical cache example, assume the temperature response arrives at 200 s with Max-Age of 20 s. At 212 s, its remaining allowance is 20 s minus 12 s = 8 s. A proxy cannot make that cached content newly fresh simply by forwarding it again. Preserve the remaining allowance and the representation’s identity when checking the resource contract.
Read Figure 12.1 from the temperature GET through configuration creation to the threshold PUT. Each path names the object; each response code states the outcome. Negotiation adds the representation agreement to those exchanges. It must not change Created into Changed or make a failed read look successful merely to simplify the bridge.
Predict the outcome when a payload omits the unit but remains valid JSON. Syntax alone passes, yet the application contract may fail because the number lacks physical meaning. Next, ask whether a successful cached response proves that the sensor is currently reachable. It does not: freshness and connectivity are separate claims.
This gives the module’s resource-design work a practical test. Define supported content, rejected forms, units and cache behaviour together. A compact refusal is more useful than an attractive but guessed value. When a gateway transcodes representations, include that translation in the same tests so that meaning survives the boundary as well as syntax.
Before signing off a CoAP resource API, verify that:
Read these points as one connected sequence: start with Every resource row names URI, method, content-format, success code, error codes, and allowed message type; then URI paths use short noun segments, a clear version strategy, and no action verbs in the path; then Accept failures return 4.06 Not Acceptable, while unsupported request bodies return 4.15 Unsupported Content-Format; then Creation, update, read, delete, overload, and missing-resource outcomes use distinct response codes; then Max-Age matches the real change rate, ETags support revalidation where useful, and proxy cache behavior is tested; then Observe notifications are throttled by threshold or cadence, not by raw sensor sampling rate; and finish with HTTP-CoAP proxy mappings and any CBOR/JSON transcoding are documented with failure behavior.
4.06 Not Acceptable, while unsupported request bodies return 4.15 Unsupported Content-Format.Read these points as one connected sequence: start with CoAP Resource API Design: Return to the main API design chapter and implementation examples; then CoAP Message Format: Review the fields and options that carry Code, Token, Content-Format, ETag, Max-Age, and Uri-Path; then CoAP Method Codes and Multicast Contracts: Connect method semantics to safe retries, idempotency, Location options, and multicast responses; then CoAP Observe Registration and Freshness Contracts: Apply freshness, tokens, and notification control to long-lived subscriptions; and finish with CoAP Block Transfer and Discovery Contracts: Extend resource contracts to discovery metadata, block sizing, ETag checks, and cache freshness.
After this API contract pass, continue with CoAP Observe Registration and Freshness Contracts for long-running resource relationships that need freshness and liveness guarantees.