> For the complete documentation index, see [llms.txt](https://docs.akenza.io/akenza.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.akenza.io/akenza.io/get-started/your-data-flow/device-connector/coap.md).

# CoAP

The **Constrained Application Protocol (CoAP)** ([RFC 7252](https://datatracker.ietf.org/doc/html/rfc7252)) is a specialized web transfer protocol designed for resource-constrained devices and networks. Operating over UDP, CoAP provides a low-overhead alternative to TCP/HTTP, making it ideal for IP-based IoT devices that require minimal power and bandwidth consumption.

To connect a CoAP-enabled device to **akenza**, you must first create a CoAP Device Connector and assign it to a Data Flow (Create Data Flow > Start with empty Data Flow > + Device Connector > CoAP > New CoAP Device Connector).

* For **Pre-shared Key Authentication**: a unique secret is generated upon creating the connector. Your device must pass this secret and the Device ID as query parameters in every CoAP request.
* For **Secure X.509 Authentication**: devices authenticate cryptographically via DTLS. You must submit your Custom Certificate Authority (CA) to akenza and provision devices matching the certificates, as detailed in the setup guide below.

{% hint style="warning" %}
By default CoAP communication without DTLS is insecure. Even if data transmission over a NB-IoT network is inherently encrypted within the cellular network, once the data leaves the carrier network for the public internet, additional measures are required to maintain security.

Therefore, we recommend using DTLS to secure the connection (see below). If your device does not support DTLS, the following alternative architecture can be implemented.

**Fallback Architecture: Secure Tunnel**

1. **Private APN:** Use a private APN configured by your network operator.
2. **VPN Tunnel:** Establish a secure VPN tunnel between the operator's APN and akenza.
3. **Result:** Data remains in its lightweight UDP format for the device, while the "hop" over the public internet is protected from eavesdropping by the tunnel.

[Contact](mailto:support@akenza.io) akenza for more information regarding transport security.
{% endhint %}

## Secure Transport (DTLS)

To secure communication over unencrypted UDP networks, akenza supports Datagram Transport Layer Security (DTLS). This ensures confidentiality, data integrity, and secure device authentication.

#### X.509 Certificate Authentication

Devices can authenticate securely with akenza using *X.509* Certificates. During the DTLS handshake, the device presents its cryptographic certificate to verify its identity, establishing an encrypted channel before any CoAP requests are transmitted. akenza will automatically extract the deviceId from the common name (CN) in the certificate.

To utilize X.509 certificate authentication, you must generate a **Device Certificate Authority (CA)** and individual **Device Client Certificates** for your devices. For more information, refer to [DTLS Device Client Certificates](/akenza.io/get-started/your-data-flow/device-connector/coap/dtls-device-client-certificates.md).

#### Verifying the server identity

akenza uses server certificates issued by ZeroSSL. Make sure your devices trust the [Sectigo Root Certificates](https://www.sectigo.com/knowledge-base/detail/Access-New-Sectigo-Certificate-Chain) specifically [ZeroSSLECCDVSSLCA2.crt](http://crt.sectigo.com/ZeroSSLECCDVSSLCA2.crt).

#### DTLS with Connection ID (CID)

To avoid costly DTLS handshakes after periods of inactivity, the protocol uses DTLS Connection IDs (as defined in [RFC 9146](https://www.rfc-editor.org/rfc/rfc9146)). The CID allows the DTLS session to be resumed without a full handshake even when the device's IP address or UDP port changes (e.g., after a network sleep cycle).

The device aims to perform as few handshakes as possible. The DTLS session is kept alive across transmission intervals by using the CID, so a new handshake is only required when the session cannot be resumed.

## Uplinks

#### Content Format

The akenza CoAP connector supports multiple payload formats. Ensure your device includes the correct `Content-Format` option header in its requests to match the transmitted data type:

<table data-header-hidden><thead><tr><th width="206.4375">CoAP Content-Format ID</th><th>Format</th><th>Behaviour</th><th data-hidden></th></tr></thead><tbody><tr><td><code>50</code></td><td><code>application/json</code></td><td><p>Standard JSON payload, parsed directly into a structured JSON object.<br><br>If JSON parsing fails:</p><ul><li><em>Valid UTF-8 payload:</em> Converted to plain text (<code>value</code>) plus Hex (<code>payloadHex</code>) and Base64 representations (<code>payloadBase64</code>).</li><li><em>Invalid UTF-8 payload:</em> Converted to Hex (<code>payloadHex</code>) and Base64 representations (<code>payloadBase64</code>).</li></ul></td><td><code>application/json</code></td></tr><tr><td><code>60</code></td><td><code>application/cbor</code></td><td>Concise Binary Object Representation (CBOR). Parsed and converted into standard JSON.</td><td><code>application/cbor</code></td></tr><tr><td><code>41</code></td><td><code>application/xml</code></td><td>XML payload, automatically converted into a JSON structure.</td><td><code>application/xml</code></td></tr><tr><td><code>42</code></td><td><code>application/octet-stream</code></td><td>Raw binary data. Automatically converted into both Hex (<code>payloadHex</code>) and Base64 (<code>payloadBase64</code>) string representations for the decoder.</td><td><code>application/octet-stream</code></td></tr><tr><td><code>0</code></td><td><code>text/plain</code></td><td>Plain text. The raw string is placed into a JSON object under the key <code>value</code>. Note that the string is expected to have utf-8 format.</td><td><code>text/plain</code></td></tr></tbody></table>

### Sending a CoAP Uplink

<mark style="color:green;">`POST`</mark> `coap://coap.akenza.io:5683/v3/capture?secret={uplinkSecret}&deviceId={deviceId}`

The body can be any **JSON** object.

#### Query Parameters

| Name         | Type   | Description                                                                                     |
| ------------ | ------ | ----------------------------------------------------------------------------------------------- |
| timestamp    | string | The timestamp of the event (ISO-8601 formatted - the current time will be used if not provided) |
| topic        | string | The data topic ("default" will be used if not provided)                                         |
| uplinkSecret | string | The uplink secret used to authenticate the request                                              |
| deviceId     | string | The device ID                                                                                   |

#### Headers

| Name            | Type   | Description      |
| --------------- | ------ | ---------------- |
| content\_format | number | application/json |

{% tabs %}
{% tab title="201 Note that the actual response code is 2.01 for CoAP" %}

```
{
    "id": "UUID",
    "timestamp": "ISO-8601 date string",
    "message": "uplink received"
}
```

{% endtab %}
{% endtabs %}

### Sample NodeJS Script

The below sample nodeJS script allows sending a CoAP uplink. It requires the node module [coap](https://www.npmjs.com/package/coap) to be installed.

```javascript
const coap = require('coap');

// define connection options
const options = {
    hostname: "coap.akenza.io",
    port: 5683,
    method: "POST",
    pathname: "/v3/capture",
    query: "secret={secret}&deviceId={deviceId}",
    options: {
        "Content-Format": "application/json",
    },
};
// create the request object
const req = coap.request(options);
// set the payload
const payload = {
    temperature: Math.random() * 100,
};
req.write(JSON.stringify(payload));
// handle success response
req.on('response', function (res) {
    res.pipe(process.stdout);
});
// handle error response
req.on('error', function (err) {
    console.log("error while sending coap request", err);
});
// send the request
req.end();
```

## Downlinks

CoAP downlinks enable cloud-to-device command delivery CoAP devices. All downlink interactions enforce a secure connection, support persistent queuing, and adhere to [RFC 7252](https://datatracker.ietf.org/doc/html/rfc7252#section-5.2) delivery mechanics.

### Security & Encryption

* **Enforced Encryption:** Plain CoAP downlinks over UDP is prohibited. All interactions must be wrapped in DTLS (CoAPs) to prevent command spoofing and replay attacks.
* **Replay Protection:** Devices must track transaction sequence IDs to identify and suppress duplicate frames.

### Queue Handling Strategies

Commands are stored in a persistent queue prior to transmission. When new commands are dispatched from the backend, the queue applies one of two policies:

| **Queue Policy** | **Description**                                                               |
| ---------------- | ----------------------------------------------------------------------------- |
| `ADD`            | Appends the incoming command to the target device's existing command queue.   |
| `REPLACE`        | Purges all pending, unacknowledged commands before inserting the new command. |

### Delivery Patterns

Because CoAP runs over UDP and devices are typically sleeping, akenza never pushes commands; instead devices retrieve pending commands when they wake up, via either a dedicated **command pull** or **piggy-backed** on a regular uplink. Commands are delivered strictly FIFO, one command per request.

**Command Pull:** a dedicated `GET /v3/commands` request:

<table><thead><tr><th width="367">Queue state</th><th>Response</th></tr></thead><tbody><tr><td>Commands pending</td><td><code>2.05 Content</code> with the command payload, in the content format chosen at enqueue time (JSON by default)</td></tr><tr><td>Queue empty</td><td><p><code>2.05 Content</code> with an empty response based on Accept header: </p><ul><li>JSON (default): <code>{}</code> </li><li>CBOR: <code>0xA0</code> </li><li>XML: <code>""</code></li></ul></td></tr></tbody></table>

**Piggy-Backed Uplink:** a pending command replaces the response of a standard `POST /v3/capture` uplink (requires the `downlinkOnUplink` feature on the device connector):

| Queue state      | Response                                                                                                                                          |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| Commands pending | `2.05 Content` with the command payload in the content format chosen at enqueue time (JSON by default) instead of the usual uplink acknowledgment |
| Queue empty      | `2.01 Created` with the normal uplink acknowledgment                                                                                              |

Devices using piggy-back must therefore branch on the response code: `2.05` carries a command, `2.01` is a plain uplink acknowledgment.

Each command contains a transaction id (`tx_id`) that is used to confirm receipt on the application-layer (see [#piggy-backed-execution-flow](#piggy-backed-execution-flow "mention")):

```json
{
  "tx_id": "tx_987654",
  ...
}
```

#### Command Pull Execution Flow (confirmed command)

Confirmed commands (`confirmed=true`, the default) are delivered as a **separate response** ([RFC 7252 §5.2.2](https://datatracker.ietf.org/doc/html/rfc7252#section-5.2.2)) so that the device's transport-level ACK can serve as the delivery confirmation:

```
IoT Device (Client)               Akenza Server                        Queue
    |                                   |                                |
    |--- DTLS Handshake --------------->|                                |
    |--- CON GET /v3/commands --------->|                                |
    |                                   |--- Fetch & Lock (Device ID) -->|
    |                                   |<-- Head command (IN_FLIGHT) ---|
    |<-- Empty ACK (0.00) --------------|                                |
    |<-- CON 2.05 Content (new MID) ----|   (retransmitted w/ backoff)   |
    |--- Empty ACK -------------------->|                                |
    |                                   |--- Purge command ------------->|
    | (Execute Command & Sleep)         |                                |
```

#### Piggy-Backed Execution Flow

A piggy-backed response travels inside the CoAP ACK itself, so **no transport-level delivery confirmation exists**. The command stays `IN_FLIGHT` until the device confirms it with a `completed_tx_id` in a later uplink (or the command expires):

```
IoT Device (Client)               Akenza Server                        Queue
    |                                   |                                |
    |--- DTLS Handshake --------------->|                                |
    |--- CON POST /v3/capture --------->|                                |
    |                                   |--- Fetch & Lock (Device ID) -->|
    |                                   |<-- Head command (IN_FLIGHT) ---|
    |<-- ACK 2.05 Content (piggy-back) -|                                |
    | (Execute Command & Sleep)         |                                |
    |                                   |                                |
    |--- POST /v3/capture with -------->|                                |
    |    completed_tx_id                |--- Purge command ------------->|
```

Unconfirmed command pulls (`confirmed=false`) are delivered as a **piggy-backed response** and also require a separate acknowledgement in a later uplink (not recommended):

```
IoT Device (Client)               Akenza Server                        Queue
    |                                   |                                |
    |--- DTLS Handshake --------------->|                                |
    |--- CON GET /v3/commands --------->|                                |
    |                                   |--- Fetch & Lock (Device ID) -->|
    |                                   |<-- Head command (IN_FLIGHT) ---|
    |<-- ACK 2.05 Content (piggyback) --|                                |
    | (Execute Command & Sleep)         |                                |
    |                                   |                                |
    |--- POST /v3/capture with -------->|                                |
    |    completed_tx_id                |--- Purge command ------------->|
```

#### Transport Reliability & Response Mechanics

The server uses the two response patterns:

* **Piggy-backed response:** response code and payload travel inside the ACK, sharing the request's Message ID (1 RTT). Used for empty-queue replies, `confirmed=false` commands, and all piggy-backed uplink deliveries. Fast, but provides no delivery confirmation. Use this strategy for fleets that require the 1-RTT/RAI pattern and acknowledge at the application layer.
* **Separate response:** used for confirmed commands (`confirmed=true`) on the pull endpoint. This is a deliberate choice to obtain a delivery confirmation.
  * **Empty ACK (`0.00`):** sent immediately with the request's Message ID; the device stops its request retransmission timer. The ACK is a promise that a response follows.
  * **Separate CON delivery:** the command follows in a new CON message with a **new Message ID** and the **original Token**, retransmitted with standard Stop-and-Wait ARQ and exponential backoff until the device acknowledges or rejects it.
  * **Delivery confirmation:** the device answers the CON with an empty ACK of its own - this is what purges the command from the queue.

> **Critical requirement:** devices must stay awake after receiving an Empty ACK. The follow-up CON leaves the server within milliseconds; a device that sleeps immediately misses it (and its retransmissions), and the command is requeued after the backoff.

#### Failure Handling & Crash Recovery

* **Re-queuing:** any failed delivery attempt returns the command to `QUEUED` for the next pull or uplink window. All three transport failure signals are handled: retransmission **timeout**, device **rejection** (`RST` the server stops retransmitting immediately, per RFC 7252), and **send errors** (e.g. the DTLS session is gone).
* **In-flight lock:** fetching a command moves it from `QUEUED` to `IN_FLIGHT` with an attempt counter and a 60-second lock. An `IN_FLIGHT` head is intentionally re-delivered on the device's next request: if the server restarts mid-delivery, the device's retransmitted request receives the same command again. Devices must therefore track received `tx_id`s and suppress duplicates.
* **Expiry:** commands not delivered within the item TTL (default 7 days) are dropped.
* **Application-level ACK (`completed_tx_id`):** independent of all transport events (retransmissions, DTLS re-handshakes, server restarts), a device can purge a command by reporting its transaction ID at the root of a subsequent uplink payload. This is the **only** way to clear a piggy-backed / unconfirmed command:

```json
{
  "completed_tx_id": "tx_987654",
  ...
}
```

## Queuing a CoAP Downlink

```
curl --location 'https://api.akenza.io/v3/devices/{{akenzaDeviceId}}/downlink' \
--header 'Content-Type: application/json' \
--header 'x-api-key: <your-api-key>' \
--data '{
        "coapDownlink": {
            "payload": {
                ...
            }, 
            "contentType": "JSON",
            "confirmed": true,
            "clearQueue": true
        },
        "raw": false
}'
```

#### Body Parameters

<table><thead><tr><th width="245">Name</th><th>Type</th><th>Description</th></tr></thead><tbody><tr><td>coapDownlink.payload</td><td>object</td><td>A json object containing the command payload. If a device type encoder is present this will be first encoded.</td></tr><tr><td>coapDownlink.clearQueue</td><td>boolean</td><td>Whether to replace any pending downlinks.</td></tr><tr><td>coapDownlink.contentType</td><td>string</td><td>The command encoding type. Can be one of JSON (default), CBOR or XML.</td></tr><tr><td>coapDownlink.confirmed</td><td>boolean</td><td>Whether the downlink is confirmable (only relevant for command pull).</td></tr><tr><td>raw</td><td>boolean</td><td>Whether to skip the device type encoder, this value has no effect for passthrough data flows.</td></tr></tbody></table>

## Additional devices

akenza provides dedicated support for [Efento](https://docs.akenza.io/akenza.io/get-started/your-data-flow/device-connector/coap/efento) devices.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.akenza.io/akenza.io/get-started/your-data-flow/device-connector/coap.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
