How an IoT device gets onto a Sutraatman CSE and starts sending data. Four steps, in this order:

  1. Register the device — create an <AE> (application entity). This is the device's account on the CSE.
  2. Create a container — create a <container> under it. This is where one kind of reading is stored.
  3. Send data — create a <contentInstance> in that container. One reading, one instance.
  4. Subscribe — create a <subscription> so the CSE pushes new readings to whoever needs them.

Every example below is a real request against a running Sutraatman 1.0.0, with the response the CSE actually sent. HTTP is shown first and in full; the same four steps over MQTT, WebSocket and CoAP are at the end.


1. Before you start

You need three things from whoever runs the platform:

What Example Where it comes from
the address of the CSE http://10.0.0.5:8080 the infrastructure node, or a middle node directly
the resource path to register under /~/plant1-mncse/plant1-mn the middle node the device belongs to
a credential, if the deployment uses them device-secret-9f2c the administrator, per device (see Credentials)

Where to address the request

Every request is sent to the address of the parent resource. Three spellings reach the same place:

Form Example Use it when
through the infrastructure node http://<in>:8080/~/plant1-mncse/plant1-mn the usual case: devices only need to know the INCSE
straight to the middle node http://<mn>:8101/~/plant1-mncse/plant1-mn the device is on the same site as the middle node
on the infrastructure node itself http://<in>:8080/~/Sutraatman/cdac_incse devices that register centrally, with no middle node

/~/<CSE-ID>/<resourceName>/… is the SP-relative form and is what the rest of this guide uses. The INCSE forwards anything addressed to a registered middle node, so a device can use one address for its whole life even if the middle node moves to another host.

A fresh installation already has mumbai-1 and mumbai-2 with five units each, so
/~/mumbai-1-mncse/mumbai-1-mn works out of the box for trying this out.


2. The request envelope

Headers the device sends

Header Required What it is
X-M2M-Origin yes, except on the very first registration the originator: the device's AE-ID, e.g. CTempSensor07
X-M2M-RI yes request identifier, unique per request; it comes back on the response so you can match them
X-M2M-RVI yes release version, 2
Content-Type on create application/json;ty=<resource type> — the ty is what makes it a create
Accept optional application/json (JSON is the only serialisation here)
X-Sutraatman-Key only when the device has a credential the device's key, or the administrator's key for CAdmin

ty values used in this guide: 2 AE, 3 container, 4 contentInstance, 23 subscription. Without a ty parameter a POST is read as a notification, not a create, and comes back 5001 notify target not supported — this is the single most common mistake.

Headers the CSE sends back

Header What it is
X-M2M-RSC the oneM2M response status code — this is the real result, e.g. 2001 created
X-M2M-RI the request identifier you sent
X-M2M-RVI 2
Content-Location address of the resource that was created
Content-Type application/json when there is a body

The HTTP status code is a mapping of X-M2M-RSC (TS-0009), so 201, 403, 409 and so on are a summary.
Always read X-M2M-RSC: several different oneM2M codes map onto the same HTTP status. HTTP header names are case-insensitive, so a client may see them as x-m2m-rsc.


3. Step 1 — register the device

An <AE> is created under the CSEBase. Two attributes are mandatory:

Attribute Meaning Rule
api App-ID, which kind of application this is must start with R (registered) or N (non-registered)
rr requestReachability: can the CSE send requests to this device? true or false, no default

Useful optional ones: rn (resource name, how it appears in the tree; A-Z a-z 0-9 _ - . @, up to 128 characters, and not la, ol, fopt), poa (points of access where the CSE can reach the device — a list, even for one entry),
lbl (labels; the management console groups units by a product:<name> label).

Who chooses the AE-ID

You send You get
no X-M2M-Origin (or just C) the CSE assigns one, e.g. Cmv415hr1mlfwxs9y
X-M2M-Origin: CTempSensor07 that is your AE-ID, if nothing else has taken it
X-M2M-Origin: CAdmin + admin key, with "aei" in the body the AE-ID you put in aei (administrator only)

Read the aei out of the response and use it as X-M2M-Origin for everything afterwards.

The request

POST /~/mumbai-1-mncse/mumbai-1-mn HTTP/1.1
Host: 10.0.0.5:8080
X-M2M-Origin: CTempSensor07
X-M2M-RI: req-0002
X-M2M-RVI: 2
Content-Type: application/json;ty=2
Accept: application/json

{"m2m:ae":{"rn":"TempSensor07","api":"Nsensor.acme.com","rr":false,
           "poa":["http://10.0.0.21:8000/"],
           "lbl":["product:WaterPump","site:plant-1"]}}

The response

HTTP/1.1 201 Created
X-M2M-RSC: 2001
X-M2M-RI: req-0002
X-M2M-RVI: 2
Content-Location: /mumbai-1-mn/TempSensor07
Content-Type: application/json

{"m2m:ae":{"api":"Nsensor.acme.com","rr":false,"poa":["http://10.0.0.21:8000/"],
 "lbl":["product:WaterPump","site:plant-1"],"aei":"CTempSensor07",
 "ri":"mv415hu4lrogrj1b","rn":"TempSensor07","ty":2,"pi":"mumbai-1-mncse",
 "ct":"20261011T162103","lt":"20261011T162103","acpi":["mv415hu4jwjj2enz"]}}

The CSE also created an access control policy (acpi) that lets this AE and the administrator do everything with this AE's subtree, and nobody else. Registration is logged on the node and the device appears in the console.

When it goes wrong

X-M2M-RSC HTTP m2m:dbg Cause
4000 400 api (App-ID) is mandatory and must start with 'R' or 'N' api missing or malformed
4000 400 rr (requestReachability) is mandatory for AE rr missing
4105 409 AE-ID already registered: CTempSensor07 that AE-ID is taken — reuse it instead of registering again
4103 403 AE credential mismatch the AE has a key and it was wrong or missing
6100 403 capacity the node is at its IoT-unit limit (1500 by default)

A device that restarts does not register again: it keeps its AE-ID and carries on at step 3. 4105 on start-up is the normal sign that it is already registered.

Credentials

By default any originator may claim any unused AE-ID, which is fine on a closed network. To tie an AE-ID to a secret, the administrator sets x_key on the AE:

curl -X POST 'http://10.0.0.5:8080/~/mumbai-1-mncse/mumbai-1-mn' 
  -H 'X-M2M-Origin: CAdmin' -H 'X-Sutraatman-Key: <admin key>' 
  -H 'X-M2M-RI: 1' -H 'X-M2M-RVI: 2' -H 'Content-Type: application/json;ty=2' 
  -d '{"m2m:ae":{"rn":"LockedSensor","aei":"CLockedSensor","api":"Nsensor.acme.com",
                 "rr":false,"x_key":"device-secret-9f2c"}}'

From then on every request from CLockedSensor must carry X-Sutraatman-Key: device-secret-9f2c, on every path — straight to the middle node or forwarded through the infrastructure node — or it is refused with 4103 AE credential mismatch. The key is never returned by a read. It can be changed later with an UPDATE of the AE and cleared by setting it to "".


4. Step 2 — create a container

A <container> holds one series of readings. Create one per measurement stream (telemetry, alarms, status).

Attribute Meaning Default
rn resource name generated if absent
mni maxNrOfInstances — how many readings to keep 1000
mbs maxByteSize — bytes of content to keep; a reading bigger than this is refused unlimited
mia maxInstanceAge in seconds the CSE's retention default (30 days)
lbl labels —

mni/mbs/mia must be non-negative integers. The container is a ring buffer: when it is full the oldest instances are deleted automatically, so a device never has to clean up after itself.

POST /~/mumbai-1-mncse/mumbai-1-mn/TempSensor07 HTTP/1.1
X-M2M-Origin: CTempSensor07
X-M2M-RI: req-0006
X-M2M-RVI: 2
Content-Type: application/json;ty=3

{"m2m:cnt":{"rn":"telemetry","mni":500,"mbs":65536,"lbl":["unit:degC"]}}
HTTP/1.1 201 Created
X-M2M-RSC: 2001
Content-Location: /mumbai-1-mn/TempSensor07/telemetry

{"m2m:cnt":{"mni":500,"mbs":65536,"lbl":["unit:degC"],"cni":0,"cbs":0,"st":0,
 "ri":"mv415hxzzihb5l41","rn":"telemetry","ty":3,"pi":"mv415hu4lrogrj1b",
 "ct":"20261011T162103","lt":"20261011T162103"}}

cni (how many instances it holds), cbs (how many bytes) and st (state tag, bumped on every change) are maintained by the CSE — do not send them.

X-M2M-RSC Cause
4000 mni must be a non-negative integer a negative or non-numeric mni/mbs/mia
4105 resource already exists that rn is taken under this parent
4103 the originator may not create below this resource

5. Step 3 — send a reading

One <contentInstance> is one reading. They are immutable: create them, never update them.

Attribute Meaning
con mandatory — the content. A string (JSON encoded as a string, CSV, a number) or a JSON object
cnf contentInfo, <media type>:<encoding>; defaults to text/plain:0. Use application/json:0 for JSON

The CSE fills in cs (content size in bytes), et (expiry time, from the container's mia or the node default),
st and the generated rn.

POST /~/mumbai-1-mncse/mumbai-1-mn/TempSensor07/telemetry HTTP/1.1
X-M2M-Origin: CTempSensor07
X-M2M-RI: req-0008
X-M2M-RVI: 2
Content-Type: application/json;ty=4

{"m2m:cin":{"cnf":"application/json:0","con":"{"temperature":31.4,"flow":62}"}}
HTTP/1.1 201 Created
X-M2M-RSC: 2001
Content-Location: /mumbai-1-mn/TempSensor07/telemetry/cin_mv415hztrd3vq1co

{"m2m:cin":{"cnf":"application/json:0","con":"{"temperature":31.4,"flow":62}",
 "cs":30,"et":"20261110T162103","ri":"mv415hztrd3vq1co","rn":"cin_mv415hztrd3vq1co",
 "ty":4,"pi":"mv415hxzzihb5l41","ct":"20261011T162103","lt":"20261011T162103","st":1}}

This is the request a device repeats for the rest of its life. It is also what makes the device count as active in the console: a registered device with no readings shows as inactive.

Reading data back

Address Returns
…/telemetry/la the latest instance
…/telemetry/ol the oldest one still kept
…/telemetry the container, with cni and cbs
…/telemetry?rcn=4 the container together with its instances
GET /~/mumbai-1-mncse/mumbai-1-mn/TempSensor07/telemetry/la HTTP/1.1
X-M2M-Origin: CTempSensor07
X-M2M-RI: req-0010
X-M2M-RVI: 2
X-M2M-RSC Cause
4000 con is mandatory for contentInstance no con
5207 the content is larger than the container's mbs
4108 the target is not a container — a contentInstance can only be a child of a container
4004 the container does not exist (check the path and the AE's rn)

6. Step 4 — subscribe

A <subscription> under a resource tells the CSE to push a notification whenever something happens to it.

Attribute Meaning
nu mandatory — notificationURI, a list of targets (see below)
enc.net which events; default [1]. 3 is the one you want for new readings
nct notification content: 1 the whole resource (default), 3 only its identifier
rl rate limit {"mnn":<max notifications>,"tww":<seconds>}
bn batch {"num":<count>,"dur":<seconds>} — send them in groups
exc expiration counter: delete the subscription after this many notifications

Event types (enc.net): 1 the resource itself was updated · 2 it was deleted · 3 a child was created (a new reading) · 4 a child was deleted.

Subscribe to the container, not to a contentInstance: instances are immutable and not subscribable, and
…/la is a virtual resource that refuses creates (4005).

What nu may contain

Value Where the notification goes
http://host:port/path an HTTP POST to that URL
coap://host:port/path a confirmable CoAP POST
an AE-ID, e.g. CTempSensor07 the AE's poa; or, if it has none, the MQTT or WebSocket channel that AE has open on the CSE that hosts the resource

That last rule matters: the subscription is served by the CSE the container lives on. If the container is on
mumbai-1 and your device's MQTT connection is to the infrastructure node, the hosting node cannot reach it. Either give the AE an HTTP poa, or connect to the middle node's own MQTT/WebSocket port. When a target resolves to nothing the CSE writes a warning to its log (source notification) and discards the notification, so check the node's log on the console's Log management page if a subscription looks silent.

Creating it

POST /~/mumbai-1-mncse/mumbai-1-mn/TempSensor07/telemetry HTTP/1.1
X-M2M-Origin: CTempSensor07
X-M2M-RI: req-0012
X-M2M-RVI: 2
Content-Type: application/json;ty=23

{"m2m:sub":{"rn":"to-analytics","nu":["http://127.0.0.1:7000/notify"],
            "nct":1,"enc":{"net":[3]}}}
HTTP/1.1 201 Created
X-M2M-RSC: 2001
Content-Location: /mumbai-1-mn/TempSensor07/telemetry/to-analytics

{"m2m:sub":{"nu":["http://127.0.0.1:7000/notify"],"nct":1,"enc":{"net":[3]},
 "cr":"CTempSensor07","ri":"mv415i4ejj68qyn9","rn":"to-analytics","ty":23,
 "pi":"mv415hxzzihb5l41","ct":"20261011T162103","lt":"20261011T162103"}}

The verification request

Before the subscription is created the CSE posts a verification request to every target and refuses the whole subscription if one does not answer. The receiver must be listening before you subscribe. It is skipped when
nu is the subscriber's own AE-ID.

POST /notify HTTP/1.1
X-M2M-Origin: /mumbai-1-mncse
X-M2M-RI: mv415i48j1nqgxbi
X-M2M-RVI: 2
Content-Type: application/json

{"m2m:sgn":{"vrq":true,"sur":"mumbai-1-mn/TempSensor07/telemetry/to-analytics",
            "cr":"CTempSensor07"}}

Answer 200 with X-M2M-RSC: 2000. Anything else and the subscription is refused with
5204 verification failed for <url>.

The notification

Every new reading then arrives like this:

POST /notify HTTP/1.1
X-M2M-Origin: /mumbai-1-mncse
X-M2M-RI: mv415i7odl3rfvyh
X-M2M-RVI: 2
Content-Type: application/json

{"m2m:sgn":{"nev":{"net":3,
  "rep":{"m2m:cin":{"cnf":"application/json:0","con":"{"temperature":32.1,"flow":58}",
         "cs":30,"et":"20261110T162103","ri":"mv415i6zfn71rzwh",
         "rn":"cin_mv415i6zfn71rzwh","ty":4,"pi":"mv415hxzzihb5l41",
         "ct":"20261011T162103","lt":"20261011T162103","st":2}}},
  "sur":"mumbai-1-mn/TempSensor07/telemetry/to-analytics","cr":"CTempSensor07"}}

nev.net is the event, nev.rep the resource (with nct:3, only {"m2m:uri":"<id>"}), sur the subscription it came from. Deleting the subscription sends a last notification with "sud":true. Notifications that are not answered go to a retry queue.

X-M2M-RSC Cause
4000 nu (notificationURI) is mandatory for subscription nu missing or empty
4000 invalid notificationEventType an enc.net value outside 1–8
5204 the target did not answer the verification request
5203 the parent is not subscribable (a contentInstance, another subscription)
4005 you aimed at la/ol — subscribe to the container instead

7. The same four steps on the other bindings

MQTT, WebSocket and CoAP carry the same primitive, so only the envelope changes. In JSON: op (1 create, 2 retrieve, 3 update, 4 delete, 5 notify), to, fr (originator), rqi (request id), rvi, ty, pc (the body). A credential goes in sutraatmanKey instead of a header. These bindings are off unless the node enables them.

MQTT

Topics: the device sends on /oneM2M/req/<AE-ID>/<CSE-ID>/json and reads its answers on
/oneM2M/resp/<AE-ID>/<CSE-ID>/json. Registration may use reg_req / reg_resp in place of req / resp.
fr must equal the originator in the topic.

{"op":1,"to":"/~/Sutraatman/cdac_incse","fr":"CMqttSensor02","rqi":"m1","rvi":"2","ty":2,
 "pc":{"m2m:ae":{"rn":"MqttSensor02","api":"Nsensor.acme.com","rr":true}}}
{"rsc":2001,"rqi":"m1","rvi":"2","fr":"/Sutraatman",
 "pc":{"m2m:ae":{"api":"Nsensor.acme.com","rr":true,"aei":"CMqttSensor02", … }}}

Steps 2–4 are the same message with ty 3, 4 and 23 and the matching pc, sent on the req topic.

Notifications arrive with the roles swapped — the CSE is the originator and the device the receiver, so they come on /oneM2M/req/<CSE-ID>/<AE-ID>/json, and the device answers on /oneM2M/resp/<CSE-ID>/<AE-ID>/json. Subscribing to your own req topic is the usual reason a device never sees its notifications.

{"op":5,"to":"CMqttSensor02","fr":"/Sutraatman","rqi":"mv41qmx0a23q53ug","rvi":"2",
 "pc":{"m2m:sgn":{"nev":{"net":3,"rep":{"m2m:cin":{ … }}},
       "sur":"cdac_incse/MqttSensor02/telemetry/to-me","cr":"CMqttSensor02"}}}

WebSocket

Connect with sub-protocol oneM2M.R2.0.json. One primitive per text frame, wrapped as {"m2m:rqp":{…}}, answered with {"m2m:rsp":{…}} carrying the same rqi.

{"m2m:rqp":{"op":1,"to":"/~/Sutraatman/cdac_incse","fr":"CWsSensor02","rqi":"w1","rvi":"2","ty":2,
            "pc":{"m2m:ae":{"rn":"WsSensor02","api":"Nsensor.acme.com","rr":true}}}}

The connection becomes that AE's push channel after its first successful request, so a subscription with
"nu":["CWsSensor02"] needs no poa: the notification arrives on the same socket as a request frame ({"m2m:rqp":{"op":5,…}}) and the device answers with an {"m2m:rsp":{"rsc":2000,"rqi":…}} frame.

CoAP (UDP)

POST + option oneM2M-TY creates, GET/PUT/DELETE retrieve/update/delete. The target goes in Uri-Path (~, <CSE-ID>, <resourceName>, …), a credential in Uri-Query as sutraatmanKey=<key>.

Option Number Carries
oneM2M-FR 256 the originator
oneM2M-RQI 257 the request identifier
oneM2M-RSC 265 the response status code
oneM2M-TY 267 the resource type on a create

Content-format 10001 (application/vnd.onem2m-res+json). A create answers 2.01 with the address in
Location-Path and oneM2M-RSC: 2001; a retrieve answers 2.05 with 2000:

POST  ~/Sutraatman/cdac_incse/CoapSensor02/telemetry   oneM2M-TY: 4
  →  2.01   oneM2M-RSC 2001
     Location-Path /cdac_incse/CoapSensor02/telemetry/cin_mv41rdxcyxe8f4mq

No block-wise transfer, so one reading must fit in a datagram (~60 kB); no DTLS.


8. Status codes you will meet

RSC HTTP Meaning
2000 200 read or updated
2001 201 created
2002 200 deleted
4000 400 the request or an attribute is malformed — read m2m:dbg
4004 404 no such resource: wrong path
4005 405 not allowed on this resource (e.g. creating under la)
4103 403 not authorised: wrong key, or the ACP does not allow it
4105 409 already exists — AE-ID or resource name taken
4108 403 wrong parent for this resource type
5203 403 the parent cannot be subscribed to
5204 500 the notification target failed verification
5207 406 content larger than the container's mbs
6100 403 the node is full (max IoT units)

Errors carry a human-readable reason in the body as {"m2m:dbg":"…"} and are written to the node's log, which the console shows under Log management with the originator and the address.


9. Troubleshooting

What you see Why
5001 notify target not supported on a create ty= is missing from Content-Type, so the POST was read as a notification
4103 on every request from a device that used to work the AE was given a key; send X-Sutraatman-Key
4105 at start-up the device is already registered — reuse the AE-ID and carry on
4004 although the AE exists the path uses the AE-ID instead of its rn; address resources by resource name
5103 target not reachable the middle node in the path is not running or has not registered with the INCSE
subscription created, no notifications enc.net is [1] (updates) instead of [3] (new children); or the target cannot be reached from the hosting CSE — look for a notification warning in that node's log
notifications stop after a while exc ran out, or the rate limit rl is clipping them
the device shows as inactive in the console it is registered but has sent no contentInstance yet
readings disappear after a while the container's mni/mbs ring buffer, or et expiry — raise mni, mbs or mia

10. The whole thing as a script

#!/bin/bash
CSE=http://10.0.0.5:8080
BASE=/~/mumbai-1-mncse/mumbai-1-mn
AE=CTempSensor07
h=(-H "X-M2M-Origin: $AE" -H 'X-M2M-RVI: 2')

# 1. register (4105 simply means it is already registered)
curl -s -o /dev/null -w 'register    %{http_code}n' -X POST "$CSE$BASE" "${h[@]}" 
  -H 'X-M2M-RI: 1' -H 'Content-Type: application/json;ty=2' 
  -d '{"m2m:ae":{"rn":"TempSensor07","api":"Nsensor.acme.com","rr":false,"lbl":["product:WaterPump"]}}'

# 2. container
curl -s -o /dev/null -w 'container   %{http_code}n' -X POST "$CSE$BASE/TempSensor07" "${h[@]}" 
  -H 'X-M2M-RI: 2' -H 'Content-Type: application/json;ty=3' 
  -d '{"m2m:cnt":{"rn":"telemetry","mni":500}}'

# 3. subscribe (the receiver must already be listening)
curl -s -o /dev/null -w 'subscribe   %{http_code}n' -X POST "$CSE$BASE/TempSensor07/telemetry" "${h[@]}" 
  -H 'X-M2M-RI: 3' -H 'Content-Type: application/json;ty=23' 
  -d '{"m2m:sub":{"rn":"to-analytics","nu":["http://10.0.0.30:7000/notify"],"enc":{"net":[3]}}}'

# 4. send a reading every 5 seconds
while true; do
  curl -s -o /dev/null -w 'reading     %{http_code}n' -X POST "$CSE$BASE/TempSensor07/telemetry" "${h[@]}" 
    -H "X-M2M-RI: $(date +%s%N)" -H 'Content-Type: application/json;ty=4' 
    -d "{"m2m:cin":{"cnf":"application/json:0","con":"{\"temperature\":$((20 + RANDOM % 18))}"}}"
  sleep 5
done

To try all of this without writing any device code, the IoT unit simulator does exactly these four steps over any of the four bindings and is configured from a web page — see SIMULATOR.md.