Chapters

3 CoAP Messages: Codes, Tokens, and Options

coap
message
format

3.1 Start With the Decision

A CoAP request can share a message ID yet belong to a different exchange. Codes, tokens, and options must each keep their own job.

3.2 Route Overview

This is part 2 of 3. Review CoAP Messages: Format Foundations for the preceding evidence.

3.3 Learning Objectives

  • Map CoAP method and response codes to their wire values.
  • Decode tokens and delta-encoded options from a message.

3.4 Chapter Roadmap

  • The CoAP Message Format
  • Minimum Viable Understanding: CoAP Message Structure
  • The 4-Byte Fixed Header
  • Message in a Bottle
  • Checkpoint: Header Decisions
  • CoAP Method Codes
  • Idempotent Methods
  • Response Codes
  • Checkpoint: Methods and Codes
  • Token: Request-Response Matching
  • Token Security Consideration
  • CoAP Options
  • CBOR vs JSON for IoT
  • Checkpoint: Tokens and Options
  • Worked Example: Parsing a CoAP Message
  • Worked Example: Decoding a GET Request
  • Worked Example: GET /sensors/temp with JSON Response
  • Putting Numbers to It
  • Knowledge Check: Matching and Sequencing

3.5 The CoAP Message Format

Minimum Viable Understanding: CoAP Message Structure

Core Concept: A CoAP message consists of a 4-byte fixed header, optional token (0-8 bytes), zero or more options, and an optional payload. The fixed header contains version, message type, token length, method/response code, and message ID.

Why It Matters: Understanding the binary format is essential for debugging network captures, implementing CoAP libraries, and optimizing message sizes for constrained networks where every byte counts.

Key Takeaway: The 4-byte header efficiency is CoAP’s main advantage over HTTP - it encodes everything needed for request-response matching, reliability selection, and method identification in just 32 bits.

CoAP messages follow a compact binary format designed for efficiency:

CoAP message layout: compact fixed header followed by optional fields

4-byte fixed header

Ver, Type, TKL, Code, Message ID

Token

0-8 bytes for request-response matching

Options

Uri-Path, Content-Format, Max-Age, Observe, and other metadata encoded with deltas

0xFF

Payload marker

Payload

Application data such as JSON, CBOR, sensor values, or firmware bytes

Header: always present and only 32 bits long, which is where most of CoAP’s efficiency starts.

Token: optional, but critical when multiple exchanges are in flight at once.

Options: sorted numerically so each option can encode only its delta from the previous one.

Payload: present only when the message carries application content after the 0xFF marker.

CoAP message structure showing the 4-byte fixed header followed by optional token, delta-encoded options, the payload marker (0xFF), and application payload.

3.6 The 4-Byte Fixed Header

Now move from design pressure to the packet: byte 0 through byte 3.

The fixed header contains all essential message metadata in just 32 bits:

  • Byte 0: Version (2 bits), Type (2 bits), and Token Length (4 bits)
  • Byte 1: Code
  • Bytes 2-3: Message ID
FieldBitsDescription
Ver2Version (always 01 for CoAP 1.0)
T2Type: CON (00), NON (01), ACK (10), RST (11)
TKL4Token Length (0-8 bytes)
Code8Method (0.xx) or Response (2.xx-5.xx)
Message ID16For deduplication and ACK matching

3.6.1 Header Fields Explained

Version (Ver): Always 01 binary (value 1) for CoAP RFC 7252.

Type (T): Determines reliability and message flow:

TypeBinaryNamePurpose
CON00ConfirmableReliable, requires ACK
NON01Non-confirmableFire-and-forget
ACK10AcknowledgmentConfirms CON receipt
RST11ResetRejects message

Token Length (TKL): Number of token bytes (0-8). Tokens correlate responses to requests.

Code: Split into class (3 bits) and detail (5 bits), formatted as class.detail:

ClassRangeMeaning
00.01-0.04Methods (GET, POST, PUT, DELETE)
22.01-2.05Success responses
44.00-4.15Client error
55.00-5.05Server error

Message ID: 16-bit identifier for:

  • Matching ACK/RST to CON messages
  • Detecting duplicate messages (server caches for ~247 seconds)

3.6.2 When to Use Each Message Type: A Decision Guide

Choosing between CON and NON is one of the most important decisions in a CoAP implementation. The wrong choice either wastes battery on unnecessary retransmissions or silently loses critical commands.

IoT ScenarioRecommended TypeRationale
Temperature reading every 5 minNONMissing one reading out of 288/day is acceptable. Retransmission doubles airtime and battery cost for negligible benefit.
Firmware update chunkCONEvery 256-byte block must arrive. A single missing block corrupts the entire firmware image. The cost of retransmission is far less than the cost of a bricked device.
Smoke alarm triggeredCONLife-safety event. The server must acknowledge receipt. Use exponential backoff (2s, 4s, 8s, 16s) per RFC 7252 Section 4.2.
Soil moisture (LoRaWAN, 1% duty cycle)NONDuty cycle constraints mean retransmission may violate regulatory limits. Design the application to tolerate 5-10% packet loss.
Door lock commandCONUser expects confirmation that the lock state changed. Without ACK, the app cannot show “Locked” reliably.
Parking sensor heartbeatNONHeartbeats are periodic. If one is lost, the next one arrives in minutes. Only escalate to CON if the server hasn’t heard from the device in 3x the heartbeat interval.
HVAC setpoint changeCONChanging from 22C to 18C is a deliberate user action. The system must confirm the setpoint was applied, not silently ignored.

Rule of thumb: If losing the message would require human intervention to notice or correct, use CON. If the next periodic message will convey equivalent information, use NON.

Temperature Terry says: “Imagine sending a message in a bottle! The CoAP header is like the label you put on the bottle.”

The label tells you:

  • Type: “Please write back!” (CON) or “No reply needed” (NON)
  • Code: What you want - “Can I have the temperature?” (GET)
  • Message ID: A number so you know which bottle they’re answering

Just like how a bottle label is tiny compared to the letter inside, CoAP’s header is tiny (4 bytes!) compared to HTTP’s headers (hundreds of bytes!).

Broker BexCheckpoint: Header Decisions

You now know:

  • Byte 0 splits into Version, Type, and TKL; byte 1 is Code; bytes 2-3 are Message ID.
  • CON needs an ACK; NON fits readings where the next sample can recover from loss.
  • Message ID supports ACK matching and duplicate detection for about 247 seconds.

3.7 CoAP Method Codes

With transport behavior decoded, ask what the endpoint was told to do.

CoAP methods mirror HTTP methods but use numeric codes:

CodeMethodDescriptionIdempotent
0.01GETRetrieve resourceYes
0.02POSTCreate resource or submit dataNo
0.03PUTUpdate/replace resourceYes
0.04DELETERemove resourceYes
Idempotent Methods

An idempotent method produces the same result regardless of how many times it’s called:

  • GET /temperature returns current temperature (safe to retry)
  • PUT /led {"on": true} sets LED on (calling twice = same result)
  • DELETE /alarm/5 removes alarm 5 (calling twice = same result, even if already deleted)

POST is NOT idempotent - POST /log {"event": "click"} creates new entry each time.

Why this matters: When a CON message times out, CoAP can safely retry idempotent methods. For POST, the application must handle potential duplicates.

3.8 Response Codes

CoAP uses a structured response code system similar to HTTP:

3.8.1 Success (2.xx)

CodeNameHTTP EquivalentUse Case
2.01Created201 CreatedPOST created new resource
2.02Deleted200 OK (for DELETE)Resource removed
2.03Valid304 Not ModifiedCached response still valid
2.04Changed200 OK (for PUT)Resource updated
2.05Content200 OKGET successful with payload

3.8.2 Client Error (4.xx)

CodeNameHTTP EquivalentCause
4.00Bad Request400Malformed request
4.01Unauthorized401Missing authentication
4.03Forbidden403Access denied
4.04Not Found404Resource doesn’t exist
4.05Method Not Allowed405Wrong method for resource
4.12Precondition Failed412ETag mismatch
4.15Unsupported Content-Format415Unknown payload format

3.8.3 Server Error (5.xx)

Server errors mean the request was understood but the server or an upstream hop could not complete it. Read the class from general to specific: 5.00 Internal Server Error covers an unexpected local failure, 5.01 Not Implemented says the requested method capability is absent, and 5.03 Service Unavailable signals a temporary overload. The gateway-specific 5.02 Bad Gateway and 5.04 Gateway Timeout preserve whether the upstream reply was invalid or never arrived, which determines whether a client should retry, fall back, or escalate.

CodeNameHTTP EquivalentCause
5.00Internal Server Error500Server crashed
5.01Not Implemented501Method not supported
5.02Bad Gateway502Proxy error
5.03Service Unavailable503Server overloaded
5.04Gateway Timeout504Proxy timeout

Broker BexCheckpoint: Methods and Codes

You now know:

  • Method codes 0.01 through 0.04 map to GET, POST, PUT, and DELETE.
  • Successful PUT returns 2.04 Changed; successful GET with payload returns 2.05 Content.
  • Client and server failures remain visible as 4.xx and 5.xx responses.

3.9 Token: Request-Response Matching

Code tells you the operation; Token ties the response back to a request.

The Token correlates responses to requests, especially important when multiple requests are in flight:

Client sends: GET /temperature, Token=0xAB12, MsgID=0x5678
Client sends: GET /humidity, Token=0xCD34, MsgID=0x5679

Server sends: 2.05 Content, Token=0xCD34, "65%"  (humidity response)
Server sends: 2.05 Content, Token=0xAB12, "22.5" (temperature response)

Token vs. Message ID:

AspectTokenMessage ID
PurposeMatch response to requestDeduplication, ACK matching
ScopeApplication-levelTransport-level
Length0-8 bytes (TKL field)Fixed 16 bits
PersistenceAcross retransmissionsChanges on each transmission
Generated byClientClient
Token Security Consideration

In secure deployments (DTLS), tokens should be unpredictable (randomly generated) to prevent response spoofing attacks. A malicious actor who can predict tokens could inject fake responses.

# BAD: Sequential tokens
token = counter
counter += 1

# GOOD: Random tokens
import os
token = os.urandom(4)

3.10 CoAP Options

After header and token, parse sorted options first and payload only after the marker.

Options carry metadata similar to HTTP headers but use efficient binary encoding:

3.10.1 Common Options

Option NumberNameLengthPurpose
3Uri-HostStringTarget host
7Uri-Port0-2 bytesTarget port
11Uri-PathStringPath segments (/sensors/temp)
12Content-Format0-2 bytesPayload MIME type
14Max-Age0-4 bytesCacheability (seconds)
17Accept0-2 bytesAcceptable response format
35Proxy-UriStringFor proxied requests

3.10.2 Option Delta Encoding

Options are sorted by number and use delta encoding to minimize bytes:

Read these points as one connected sequence: start with Option delta (OD): 4 bits storing the difference from the previous option number; then Option length (OL): 4 bits storing the number of value bytes; then Extended delta: 0, 1, or 2 extra bytes when the delta does not fit in 4 bits; and finish with Value: the option payload bytes.

  • Option delta (OD): 4 bits storing the difference from the previous option number
  • Option length (OL): 4 bits storing the number of value bytes
  • Extended delta: 0, 1, or 2 extra bytes when the delta does not fit in 4 bits
  • Value: the option payload bytes

Example: Encoding Uri-Path /sensors/temp

Read these points as one connected sequence: start with Option 1: Uri-Path = "sensors" uses delta 11, length 7, and encodes as 0xB7 | "sensors"; then Option 2: Uri-Path = "temp" uses delta 0, length 4, and encodes as 0x04 | "temp"; and finish with Total size: 13 bytes vs HTTP GET /sensors/temp HTTP/1.1\r\n at 27 bytes.

  • Option 1: Uri-Path = "sensors" uses delta 11, length 7, and encodes as 0xB7 | "sensors"
  • Option 2: Uri-Path = "temp" uses delta 0, length 4, and encodes as 0x04 | "temp"
  • Total size: 13 bytes vs HTTP GET /sensors/temp HTTP/1.1\r\n at 27 bytes

3.10.3 Content-Format Codes

The Content-Format option tells the receiver how to decode the payload, so select it as part of the resource contract rather than after serialization. 0 text/plain fits a human-readable scalar, while 50 application/json supports familiar structured objects. On constrained links, 60 application/cbor carries structured data in a compact binary representation; 40 application/link-format has the separate job of encoding discovery links. A receiver must interpret the numeric code before parsing payload bytes.

CodeMIME TypeDescription
0text/plainSimple text
40application/link-formatResource discovery
41application/xmlXML data
42application/octet-streamBinary data
47application/exiEfficient XML
50application/jsonJSON data
60application/cborCompact Binary (CBOR)
CBOR vs JSON for IoT

CBOR (Concise Binary Object Representation) is to JSON what CoAP is to HTTP - a compact binary alternative:

# JSON (42 bytes)
{"device":"temp42","value":23.5,"unit":"C"}

# CBOR (~20 bytes) - same data, half the size
A3 66 64 65 76 69 63 65 66 74 65 6D 70 34 32...

When to use:

  • CBOR: Production deployments, battery-powered devices, constrained networks
  • JSON: Development, debugging, cloud integration with JSON-native APIs
Broker BexCheckpoint: Tokens and Options

You now know:

  • Tokens are 0-8 bytes and correlate responses to requests.
  • Uri-Path 11 and Content-Format 12 are sorted delta options, not text headers.
  • Content-Format 50 for JSON and 60 for CBOR tells the receiver how to parse payload.

3.11 Worked Example: Parsing a CoAP Message

For the examples, keep the order fixed: header, token, options, marker, payload.

Worked Example: Decoding a GET Request

Raw bytes (hexadecimal):

44 01 5A F2 AB CD 00 00 B5 68 65 6C 6C 6F

Step 1: Parse fixed header (4 bytes)

Byte 1: 0x44 = 0100 0100 binary

  • Ver = 01 = Version 1 (valid CoAP)
  • T = 00 = CON (Confirmable)
  • TKL = 0100 = 4 (4-byte token)

Byte 2: 0x01 = Code

  • Class = 0, Detail = 01 = 0.01 = GET

Bytes 3-4: 0x5AF2 = Message ID = 23282

Step 2: Parse token (TKL = 4 bytes)

Bytes 5-8: AB CD 00 00 = Token = 0xABCD0000

Step 3: Parse options

Byte 9: 0xB5 = 1011 0101

  • Option Delta = 11 (decimal) = Uri-Path
  • Option Length = 5

Bytes 10-14: 68 65 6C 6C 6F = ASCII “hello”

Decoded message:

FieldValue
Version1
TypeCON (Confirmable)
MethodGET
Message ID23282
Token0xABCD0000
Uri-Path/hello

Full URI: coap://server/hello

3.11.1 Worked Example: Complete Sensor Reading – CoAP vs HTTP

Worked Example: GET /sensors/temp with JSON Response

This example traces a complete request-response pair for reading a temperature sensor, showing every byte in the CoAP message and comparing to the equivalent HTTP exchange.

Scenario: An ESP32 gateway requests the current temperature from a sensor node at coap://sensor.local/sensors/temp, expecting a JSON response.


CoAP Request (21 bytes total):

Offset  Hex          Binary             Field
------  ---          ------             -----
 0      44           0100 0100          Ver=1, T=CON, TKL=4
 1      01           0000 0001          Code=0.01 (GET)
 2-3    7D 34        0111 1101 0011 0100  MsgID=32052
 4-7    A1 B2 C3 D4  (random bytes)     Token=0xA1B2C3D4
 8      B7           1011 0111          OptDelta=11(Uri-Path), OptLen=7
 9-15   73 65 6E 73 6F 72 73            "sensors"
16      04           0000 0100          OptDelta=0(Uri-Path), OptLen=4
17-20   74 65 6D 70                     "temp"

Total: 21 bytes on the wire (plus 8 bytes UDP header = 29 bytes from IP layer).


Equivalent HTTP Request (minimum):

GET /sensors/temp HTTP/1.1\r\n         (27 bytes)
Host: sensor.local\r\n                 (20 bytes)
Accept: application/json\r\n           (26 bytes)
Connection: keep-alive\r\n             (24 bytes)
\r\n                                   (2 bytes)

Total: 99 bytes minimum (plus 20 bytes TCP header + 20 bytes TCP options typical = 139 bytes from IP layer). Real-world HTTP requests with cookies, user-agent, and other headers typically reach 300-800 bytes.


CoAP Response (35 bytes total):

Offset  Hex          Field
------  ---          -----
 0      64           Ver=1, T=ACK, TKL=4
 1      45           Code=2.05 (Content)
 2-3    7D 34        MsgID=32052 (matches request)
 4-7    A1 B2 C3 D4  Token=0xA1B2C3D4 (matches request)
 8      C1           OptDelta=12(Content-Format), OptLen=1
 9      32           Value=50 (application/json)
10      FF           Payload marker
11-34   {"temp":22.5,"unit":"C"}       (24 bytes JSON payload)

Total: 35 bytes. The ACK + response is piggybacked — one message serves as both “I received your request” and “here is the data.”


Size Comparison Summary:

ComponentCoAPHTTP/1.1Savings
Request21 bytes99 bytes (min)4.7x smaller
Response35 bytes~180 bytes (headers + body)5.1x smaller
Transport overhead8 bytes (UDP)40+ bytes (TCP)5x smaller
Total exchange64 bytes~320 bytes5x smaller
Connection setup0 messages3 messages (TCP handshake)No handshake

On a LoRa link at SF10 (EU868), each byte costs approximately 1.2 ms of airtime. The CoAP exchange takes ~77 ms airtime vs ~384 ms for HTTP — a difference that matters when duty cycle limits you to 1% transmission time.

We can calculate the exact battery impact over the device lifetime. The total energy consumption for protocol overhead is:

Etotal=bytes×energy per byte×messages per day×daysE_{\text{total}} = \text{bytes} \times \text{energy per byte} \times \text{messages per day} \times \text{days}

For CoAP over 5 years (1,825 days) at 96 readings/day:

ECoAP=64 bytes×0.5 mJ/byte×96×1,825=5,606,400 mJ5,606 JE_{\text{CoAP}} = 64 \text{ bytes} \times 0.5 \text{ mJ/byte} \times 96 \times 1{,}825 = 5{,}606{,}400 \text{ mJ} \approx 5{,}606 \text{ J}

For HTTP over the same period:

EHTTP=320 bytes×0.5 mJ/byte×96×1,825=28,032,000 mJ28,032 JE_{\text{HTTP}} = 320 \text{ bytes} \times 0.5 \text{ mJ/byte} \times 96 \times 1{,}825 = 28{,}032{,}000 \text{ mJ} \approx 28{,}032 \text{ J}

This means HTTP consumes 28,032/5,6065×28{,}032 / 5{,}606 \approx 5\times more energy just for protocol messaging. With 2 AA batteries providing roughly 10,000 J total capacity, the savings directly translates to multi-year battery life differences.

Battery impact: At 0.5 mJ per byte (typical LoRa SF12), the CoAP exchange costs 32 mJ vs 160 mJ for HTTP per reading. Over 96 readings per day for 5 years (175,200 total readings), CoAP consumes ~5,606 J vs ~28,032 J just for protocol messaging — a 5x difference. With 2 AA batteries providing roughly 10,000 J total capacity, HTTP protocol overhead alone would exhaust nearly three battery sets over that period, while CoAP protocol overhead consumes just over half of one set.

Knowledge Check: Matching and Sequencing

Test your understanding of CoAP concepts and message processing order.

3.12 Continue to the Next Part

Carry this evidence into CoAP Messages: Parsing and Validation, which begins with Label the Diagram.