REST API Reference

The IoT Emulator provides a comprehensive REST API for automation and integration.

From Java, use the typed client described in Java Control Client instead of writing the HTTP calls by hand.

Overview

Base URL

http://localhost:8556/api

API Documentation

Interactive API documentation is available via Swagger UI:

http://localhost:8556/swagger-ui.html

OpenAPI specification:

http://localhost:8556/v3/api-docs

Response Format

All responses are in JSON format.

Error Body

A call that fails inside the emulator answers with one body, whatever the endpoint:

{
  "code": "NOT_INITIALIZED",
  "message": "Initialize the device tree before starting.",
  "details": null
}

code is the stable half a client branches on; message is the half meant for a person and is safe to show in a UI; details is null unless an endpoint has structured context to add. The Content-Type is application/json. message is never null or blank: a failure that arrived without a reason of its own is described by its code, so DEVICE_STOP_FAILED reads Device stop failed.

When a failure is a file the emulator could not open, read, write or delete — a device’s keystore or truststore, a device tree, a configuration file being uploaded or deleted — the message names that file by its file name only, and the full text, path included, is written to the server log. This applies to every message of the error body. Only the path that the file failure itself names is cut; the rest of the message stays as the emulator wrote it.

Status codes:

Status Meaning

400

Invalid input — a malformed body, an unconvertible query parameter, a missing multipart part, a value an endpoint checks itself. Also the answer of the endpoints that have always refused with 400: a setting changed while the emulator is not stopped, a device created before initialization, a device that could not be created, started, stopped or deleted

404

Not found — a device, a stored device, a parameter, the snapshot

409

State conflict — already initialized, not initialized, the device is not started, the file is in use, the parameter is frozen, no LwM2M device is running

422

The emulator accepted the request and the operation failed

501

The feature is not available on this runtime

500

A device tree that could not be written or deleted, and anything else

Every failure of the endpoints this document lists answers with this body. The exceptions are POST /api/rule, whose 400 for a script that does not compile is the compile result described there, and the EST endpoints, which answer as their section describes (a plain-text reason, or no body). POST /api/devices/{clientId}/reboot and POST /api/devices/{clientId}/bootstrap, used by the ACS e2e rig, answer 404 and 409 without a body.

Until this release a number of endpoints answered a failure outside this body: a bodyless 404 for an unknown device, parameter or stored device, a plain-text 400 reason from most configuration and per-device endpoints, a bodyless 409 from POST /api/lwm2m/deregister and /api/lwm2m/update, and the result object with success: false from a failed upload or delete of a device tree. They all answer with the error body now, and with the status they answered before. A client that read the body as text gets JSON instead, and the reason it showed is the message.

Secrets

The API has no authentication, so anything a GET returns is readable by anyone who can reach the port. Stored secrets are therefore never returned:

  • GET /api/config masks pskKey, authKeyStorePass, trustStorePass, aliasPass and dbPassword

  • GET /api/protocolOptions masks the value of every free-text option flagged secret

  • GET /api/storedDevices masks the four template secrets and every secret protocolOptions entry

A stored secret is reported as *. A secret that is not set is passed through untouched — empty stays empty, absent stays absent — so a form shows an empty field rather than a mask over nothing.

The mask travels the other way too. Posting back means "keep the stored value", which is what a form sends when the user saved without touching the password field. An empty string clears the secret and any other value replaces it. On a device template, where a secret is optional and absent means "inherit the configured value", is dropped instead: the field is left unset and the device inherits.

Endpoints

Status & Control

Get Status

Returns the current emulator status.

GET /api/status

Response:

{
  "protocol": "LWM2M",
  "initialized": true,
  "started": true,
  "deviceCount": 5
}

Get Version Info

Returns build and version metadata, backed by Spring Boot BuildProperties and GitProperties. Git fields are best-effort and may be null when the JAR is built outside a Git working tree.

GET /api/version

Response:

{
  "version": "v.7.0.5 b.0.0.26",
  "productVersion": "7.0.5",
  "buildVersion": "0.0.26",
  "buildTime": "2026-06-05T10:15:30Z",
  "name": "iot-emulator",
  "artifact": "gui",
  "group": "com.friendly.emulator",
  "commit": "1fc5cf5e...",
  "commitShort": "1fc5cf5",
  "commitTime": "2026-06-04T22:53:00Z",
  "branch": "dev"
}

Initialize Emulator

Initialize the device tree.

POST /api/init

Responses:

  • 204 — initialized

  • 409 ALREADY_INITIALIZED — the emulator is already initialized; stop it first

  • 422 INIT_FAILED — the session could not be built (an unreadable or invalid device tree, for example). message carries the reason the emulator logged; nothing is left running.

Start Emulator

Start device simulation.

POST /api/start

Responses:

  • 204 — started

  • 409 NOT_INITIALIZED — initialize the device tree first

  • 409 ALREADY_STARTED — the emulator is already started

  • 422 START_FAILED — the devices refused to start

Stop Emulator

Stop device simulation.

POST /api/stop

Responses:

  • 204 — stopped

  • 409 NOT_INITIALIZED — there is nothing to stop

The stop also stops the devices added to the session one by one (POST /api/devices, POST /api/devices/batch and the stored devices /api/init recreates) and frees their ports; only devices started on their own with POST /api/devices/startNew or POST /api/storedDevices/{clientId}/start keep running.

Reset Emulator

Reset to initial state.

POST /api/reset

Responses:

  • 204 — reset

  • 409 NOT_INITIALIZED — there is nothing to reset

POST /api/stop and POST /api/reset used to answer 204 unconditionally, whatever state the emulator was in. They now answer 409 NOT_INITIALIZED when it is not initialized. A client that stops unconditionally to reach a known state has to treat that 409 as "already stopped" rather than as a failure.

Configuration

Get Configuration

GET /api/config

The API has no authentication, so a stored secret is never handed back: pskKey, authKeyStorePass, trustStorePass, aliasPass and dbPassword are reported as * when something is stored, and as the empty string when nothing is. See Secrets.

Response:

{
  "protocol": "USP",
  "serverIp": "192.168.1.100",
  "serverPort": 5683,
  "clientId": "device-001",
  "serialNumber": "SN12345",
  "manufacturer": "MyCompany",
  "modelName": "Model1",
  "clientIp": "0.0.0.0",
  "clientPort": 56830,
  "clientsCount": 1,
  "concurrentClients": 1,
  "delaySec": 1,
  "secure": false,
  "securityMode": "NONE",
  "pskId": "",
  "pskKey": "",
  "authKeyStorePath": "",
  "authKeyStorePass": "",
  "trustStorePath": "",
  "trustStorePass": "",
  "authCertAlias": "",
  "aliasPass": "",
  "mtp": "STOMP",
  "guiRenderingType": "FULL",
  "paramUpdateEnabled": true,
  "loggingEnabled": true,
  "failResponse": false,
  "dbAddress": "localhost",
  "dbPort": 3306,
  "dbLogin": "root",
  "dbPassword": "",
  "dbCatalog": "acs",
  "dbCpeSerial": ""
}

Update Configuration

Persists the supplied fields to gui_connection_options.properties. Rejected with 400 EMULATOR_NOT_STOPPED while the emulator is initialized or started.

For the five secret fields listed under Secrets, * means "keep the stored value" — which is what a form posts back when the password field was not touched. An empty string clears the secret, anything else replaces it.

  • "secure": false selects NoSec: the device uses plain CoAP, whatever securityMode says and whatever an earlier request set. The keystore, trust store and PSK fields are kept for a later secure configuration.

  • "secure": true or no secure field keeps the security mode, or sets the one in securityMode.

  • GET /api/config returns secure as it follows from the current security mode.

POST /api/config
Content-Type: application/json

{
  "serverIp": "192.168.1.100",
  "serverPort": 5683,
  ...
}

Update Runtime Options

Sub-set of /api/config that is allowed while the emulator is running. Covers the footer toggles (rendering type, logging, fail response, param update) and is also called from onchange handlers so changes take effect immediately.

POST /api/config/runtime
Content-Type: application/json

{
  "guiRenderingType": "FULL",
  "paramUpdateEnabled": true,
  "loggingEnabled": true,
  "failResponse": false
}

guiRenderingType must be one of FULL, LAZY, OFF (matches the desktop GuiRenderingType enum).

Protocols

List All Protocols

GET /api/protocols

List Available Protocols

GET /api/protocols/available

Set Protocol

POST /api/protocol/{protocolName}
POST /api/protocol/{protocolName}?keepOptions=true

Query parameters:

  • keepOptions (optional, default false) — without it every call clears the protocol options, also when the protocol is already selected, so options changed for an earlier run do not carry over to the next. Test harnesses rely on that between cases. With keepOptions=true, which the Web GUI sends, re-selecting the current protocol changes nothing and a switch to another protocol restores the options saved in gui_connection_options.properties.

Responses: 204 set · 400 EMULATOR_NOT_STOPPED while the emulator is initialized or running · 400 PROTOCOL_NOT_FOUND for a protocol the emulator has not loaded.

Get Protocol Options

GET /api/protocolOptions

Each option carries a secret flag, taken from the $<key>.secret=true line of the protocol’s <protocol>_gui.properties. A free-text option flagged secret — the MQTT password, the USP secret, the LwM2M OSCORE master secret — reports its value as * instead of what is stored. An option with a fixed list of choices is not masked: it publishes every one of its choices anyway, so masking the selected one would hide nothing and leave the control matching none of them.

Update Protocol Options

POST /api/protocolOptions
Content-Type: application/json

{
  "optionKey": "optionValue"
}

For a key flagged secret, means "keep the stored value", an empty string clears it and anything else replaces it. For every other key is stored literally — swallowing it there would be a silent edit. Answers 204, or 400 BAD_REQUEST when no option map arrives or an option value is null; nothing is applied then.

Get Transport Configuration

Returns the transport (or MTP) selector of the current protocol together with the ports declared for each transport — this is what the Single Emulator form renders above Server Port. Protocols without a transport selector (MQTT, FSH_HUB, DUSUN) return an empty object.

GET /api/transportConfig

Response (LWM2M):

{
  "key": "transport",
  "options": ["UDP", "TCP"],
  "selected": "UDP",
  "selectedPort": "5683",
  "ports": {
    "UDP": ["5683", "5681", "5682", "5680", "5684", "5685"],
    "TCP": ["5883", "5881", "5882", "5880", "5884", "5885"]
  }
}

For USP the key is mtp and the options are STOMP, CoAP, WebSocket and MQTT.

Devices

List Devices

GET /api/devices

Response:

[
  {
    "clientId": "device-001",
    "serialNumber": "SN12345",
    "protocol": "LWM2M",
    "status": "SN12345->registered",
    "connection": "CONNECTED",
    "clientPort": 56830,
    "manufacturer": "MyCompany",
    "modelName": "Model1"
  }
]

connection says whether the device is connected to its server now, as the device’s own protocol reports it. It is on every entry of this list and on GET /api/devices/{clientId}, and takes one of three values:

Value Meaning

CONNECTED

The device’s server accepted it — the protocol’s own confirmation, not merely an open socket.

DISCONNECTED

The device is not connected to its server now.

UNKNOWN

The device cannot tell. A device whose state cannot be read is listed as UNKNOWN too, rather than failing the whole list.

What each protocol counts as its server’s confirmation, and what clears it:

Protocol connection

LWM2M

CONNECTED when the server accepts the Register — with late registration too, before resource discovery has run — or an update. DISCONNECTED when a registration or an update fails or times out, when the device deregisters, and when it stops, until the server accepts one again: a timed-out update is retried once, and an accepted retry makes it CONNECTED again. If the client has to register again and that Register times out as well, the emulator stops the device’s client, and the device stays DISCONNECTED.

USP

CONNECTED once the controller has shown it knows the agent: its NotifyResp to the OnBoardRequest, its Get of Device. that the agent answers with late registration, or any request it sends the agent (a USP request from the configured server_endpoint). Cleared by a registration or MTP send error, stop, restart, and any reboot or factory reset the agent performs — a Reboot() or FactoryReset() the controller operates, or the reboot that activates a downloaded firmware image — until the controller confirms the agent again. Unconfirmed, it is DISCONNECTED while onboarding (usp.onboarding) or late registration (usp.late_registration) is on, and UNKNOWN when both are off: then only the controller’s first request confirms the agent, and before it only the MTP connection is known — for the CoAP MTP, a local server starting. A dropped MTP connection with no further traffic is not detected: CONNECTED lasts until one of the events above.

MQTT, FSH_HUB, DUSUN

CONNECTED while the broker has accepted the MQTT connection and the session is live; DISCONNECTED before start, after stop and once the session is lost.

COAP

Always UNKNOWN: the device has no handshake with its server.

status is the device’s own status line as its protocol stack writes it (SN12345→registered, dev-1 → no connection, an exception message): free text that reads differently in every protocol, kept for people to read. To tell whether a device is connected, read connection, not status — an MQTT device’s status goes on reading connected after the broker has dropped it.

Get Device by Client ID

Returns detailed information about a single device, including its full parameter list. Responds 404 DEVICE_NOT_FOUND when no device with the given clientId is active.

GET /api/devices/{clientId}

Response:

{
  "clientId": "device-001",
  "serialNumber": "SN12345",
  "clientPort": 56830,
  "manufacturer": "MyCompany",
  "modelName": "Model1",
  "status": "SN12345->registered",
  "connection": "CONNECTED",
  "protocol": "LWM2M",
  "parameters": [
    { "name": "Device.DeviceInfo.Manufacturer", "value": "MyCompany", "type": "string" }
  ]
}

Send Heartbeat to All

POST /api/devices/heartbeat

Send Heartbeat to Device

POST /api/devices/{clientId}/heartbeat

Responses: 204 sent · 404 DEVICE_NOT_FOUND.

Get Device Parameters

GET /api/devices/{clientId}/parameters

Responses: 200 the parameters · 404 DEVICE_NOT_FOUND.

Deregister LWM2M Devices

Sends a LwM2M deregistration (CoAP DELETE /rd) for every running LwM2M device and stops it. Use this to let an external test (for example the ACS e2e rig) validate the ACS deregister handling on demand. Without this endpoint, the only way to stop devices was a process teardown, which may not send a clean DELETE /rd.

Request: no body.

POST /api/lwm2m/deregister

Example:

curl -X POST http://localhost:8556/api/lwm2m/deregister
Status Meaning

204 No Content

At least one LwM2M device sent DELETE /rd and stopped. The response has no body.

409 Conflict

No running LwM2M device. Other devices are left running.

With ?details=true (POST /api/lwm2m/deregister?details=true) a successful call answers 200 OK with the devices it deregistered instead of 204; nothing else changes, and with no running LwM2M device it answers the same 409:

{ "deregistered": ["cpe-1", "cpe-2"], "count": 2 }
After deregistration the device session is torn down. To register again, restart the device with POST /api/start (all devices) or POST /api/devices/{clientId}/start (one device).

Update Device Parameter Value

PUT /api/devices/{clientId}/parameters/{paramName}
Content-Type: application/json

{
  "value": "42"
}

Responses:

  • 204 — the value is set and sent to the device

  • 404 DEVICE_NOT_FOUND — no such device

  • 404 PARAMETER_NOT_FOUND — the device has no parameter by that name

  • 409 NOT_STARTED — the device has not been started. A device builds its parameters when it starts, so there is nothing to write before that; start it and try again.

  • 409 PARAMETER_NOT_UPDATABLE — a rule has frozen the parameter (updatable = false in a rule script). The value is left unchanged and nothing is sent to the device. Such a parameter is listed with "writable": false by GET /api/devices/{clientId}/parameters.

Get Autoupdate Config

Returns the current autoupdate configuration for a parameter.

GET /api/devices/{clientId}/parameters/{paramName}/autoupdate

Response:

{
  "autoupdatable": true,
  "global": false,
  "min": 0,
  "max": 1000,
  "step": 1,
  "stepType": "INCREASE",
  "period": 3000,
  "deviation": 0,
  "randomizeStepType": false,
  "dataset": ""
}

Responds 404 PARAMETER_NOT_FOUND when the device has no such parameter, or there is no such device.

Apply Autoupdate Config

Schedules / reschedules / removes the autoupdater for a single parameter (or for every device when global=true).

POST /api/devices/{clientId}/parameters/{paramName}/autoupdate
Content-Type: application/json

{
  "autoupdatable": true,
  "global": false,
  "min": 0,
  "max": 1000,
  "step": 1,
  "stepType": "INCREASE",
  "period": 3000,
  "deviation": 0,
  "randomizeStepType": false,
  "dataset": "10|20|30"
}

When dataset is non-empty it overrides min/max/step/stepType. stepType is one of INCREASE, DECREASE, RANDOM.

The parameter is found on the device itself, so this works in the web service and for devices started from Single mode. A parameter is rescheduled at once when its device is running; otherwise the setting is kept for the next start.

Responses: 204 applied · 400 BAD_REQUEST — no body, an invalid value, or no such device or parameter · 400 AUTOUPDATE_FAILED — the autoupdater could not be applied.

Get Parameter History

The values a parameter has taken on the device, oldest first, up to the last 50, each with the time it was recorded. Values are recorded while Param rendering is FULL or LAZY; with OFF nothing is recorded and recorded is false.

GET /api/devices/{clientId}/parameters/{paramName}/history

Response:

{
  "name": "/3/0/9",
  "recorded": true,
  "entries": [
    { "time": "2026-09-15T11:02:03.456Z", "value": "41" },
    { "time": "2026-09-15T11:02:06.456Z", "value": "42" }
  ]
}

Responses: 200 the history · 404 DEVICE_NOT_FOUND — no such device · 404 PARAMETER_NOT_FOUND — the device has no parameter by that name.

Per-Device Lifecycle

These endpoints back Single Emulator mode (/single): they create, start, stop and delete devices one at a time, each with its own target server, parameter file, protocol and security settings. Unlike POST /api/init + POST /api/start, they leave the other devices untouched.

Create and Start a Device

Creates, initializes and starts one device from a template and persists it to storage. The emulator does not have to be initialized first.

POST /api/devices/startNew

Request body (DeviceTemplateDto) — only clientId is required; every other field falls back to the global configuration:

{
  "clientId": "device-001",
  "serialNumber": "SN123456",
  "serverIp": "192.168.1.100",
  "serverPort": 5683,
  "parameterFile": "lwm2m_resources.xml",
  "protocol": "LWM2M",
  "securityMode": "PSK",
  "pskId": "device-psk-id",
  "pskKey": "00112233445566778899aabbccddeeff",
  "protocolOptions": { "transport": "UDP", "lifetime": "3600" }
}

For securityMode: "X509" use authKeyStorePath, authKeyStorePass, trustStorePath, trustStorePass, authCertAlias and aliasPass instead of the PSK pair. protocolOptions keys match the protocol’s <protocol>_gui.properties.

pskKey, authKeyStorePass, trustStorePass, aliasPass and any secret protocolOptions entry accept *, which leaves the field unset so the device inherits the configured value — see Secrets. The same applies to POST /api/devices/batch.

Responses:

  • 200 — the created device (DeviceDto, same shape as GET /api/devices/{clientId})

  • 400 DEVICE_CREATION_FAILED — the device could not be created or started; message says why

  • 400 BAD_REQUEST — protocol names none of the emulator’s protocols. The name is matched without regard to case (mqtt is MQTT) and is checked before anything else is read. The same holds for every template of POST /api/devices/batch.

  • 400 BAD_REQUEST — parameterFile is not the bare name of a file in the configuration directory. A path separator (/ or \), .., a drive letter or an absolute path is refused before any file is read; the value the Device tree (XML) picker sends, such as lwm2m_resources.xml, is what is expected. The same holds for every template of POST /api/devices/batch.

curl -X POST http://localhost:8556/api/devices/startNew \
  -H 'Content-Type: application/json' \
  -d '{"clientId":"device-001","serialNumber":"SN123456","serverIp":"192.168.1.100","serverPort":5683}'

Start a Device

Starts a device that was stopped.

POST /api/devices/{clientId}/start

Responses: 204 started · 400 DEVICE_START_FAILED · 404 DEVICE_NOT_FOUND.

Stop a Device

Stops one running device without affecting the others. Its stored configuration is kept.

POST /api/devices/{clientId}/stop

Responses: 204 stopped · 400 DEVICE_STOP_FAILED · 404 DEVICE_NOT_FOUND.

Delete a Device

Stops the device and removes it from both the active list and the persistent storage. A client ID that is only present in storage can be deleted too.

DELETE /api/devices/{clientId}

Responses: 204 deleted · 400 DEVICE_DELETE_FAILED · 404 DEVICE_NOT_FOUND, a client ID known neither as an active nor as a stored device.

Update a Device’s Target Server

Points a single device at a different server. Only non-empty fields are applied (serverPort must be greater than 0).

PUT /api/devices/{clientId}/server
{
  "serverIp": "192.168.1.100",
  "serverPort": 5683
}

Responses: 204 updated · 404 DEVICE_NOT_FOUND.

Create a Device (without starting)

Creates one device in an already initialized emulator, optionally against its own server. The device starts with the next POST /api/start.

POST /api/devices
{
  "clientId": "device-002",
  "serialNumber": "SN123457",
  "serverIp": "192.168.1.100",
  "serverPort": 5683
}

Responses: 200 the created device · 400 NOT_INITIALIZED, DEVICE_ALREADY_EXISTS or DEVICE_CREATION_FAILED.

Create Devices from Templates

Creates several devices at once, each from its own DeviceTemplateDto (same fields as Create and Start a Device). The emulator must be initialized.

POST /api/devices/batch
[
  { "clientId": "device-003", "serialNumber": "SN1", "protocol": "LWM2M", "serverIp": "10.0.0.5" },
  { "clientId": "device-004", "serialNumber": "SN2", "protocol": "MQTT",  "serverIp": "10.0.0.6" }
]

Responses: 200 the list of created devices · 400 NOT_INITIALIZED or DEVICE_CREATION_FAILED (and BAD_REQUEST for a template, as for Create and Start a Device).

Stored Devices

Devices created through the per-device endpoints are persisted to devices.json in the configuration directory and are recreated automatically when the emulator session is initialized.

List Stored Devices

GET /api/storedDevices

Returns the list of stored DeviceTemplateDto entries, with their secrets masked: pskKey, authKeyStorePass, trustStorePass, aliasPass, and any protocolOptions entry whose key the template’s protocol declares secret, all come back as *.

A stored record whose protocol names none of the emulator’s protocols does not fail the list. Its protocol’s option declarations cannot be read, so its protocolOptions are masked by name alone: a key containing password, passwd, psw, secret or pskkey, in any case, comes back as *.

Start a Stored Device

Loads a configuration from storage, recreates the device and starts it.

POST /api/storedDevices/{clientId}/start

Responses: 200 the started device · 400 DEVICE_START_FAILED · 404 STORED_DEVICE_NOT_FOUND.

Delete a Stored Device

DELETE /api/storedDevices/{clientId}

Responses: 204 removed · 404 STORED_DEVICE_NOT_FOUND. The running device with the same ID, if any, is not touched.

Clear Stored Devices

Empties devices.json.

DELETE /api/storedDevices

Response: 204.

EST Enrollment (LwM2M only)

These endpoints run and inspect the EST-coaps enrollment of one LwM2M device. See EST enrollment for the behavior, the options, and the known limits.

The emulator REST API has no authentication. GET /api/devices/{clientId}/parameters returns the EST private key in the Security resource /0/x/5. Never publish port 8556.

All four endpoints resolve the device first.

Status Body Meaning

404

(none)

The device does not exist.

409

not-lwm2m

The device does not use the LwM2M protocol.

409

endpoint-case-collision

Another device uses an endpoint name that differs only in letter case.

Enroll

Runs /crts and /sen with the factory certificate of the device. The emulator cancels the pending automatic task and plans the next task from the result. After a successful enrollment, a running device restarts once to connect with the EST certificate.

POST /api/devices/{clientId}/est/enroll

The request has no body.

Response 200: the EST status JSON. See EST status.

Status Body Meaning

409

not-attached

The device is not started. The device never started, or you stopped it with the lifecycle stop. A REST enrollment on such a device always gives this token.

409

est-disabled

The effective lwm2m.est.uri is empty.

409

not-x509

The device does not use the X.509 security mode.

409

tcp-unsupported

The device uses LwM2M over TCP.

409

invalid-uri, invalid-pin, invalid-fault

An effective EST option has an invalid value.

409

no-keystore

The factory keystore is missing or has no usable entry.

409

factory-cn-mismatch

The CN of the factory certificate does not equal the endpoint name.

409

unsupported-factory-key

The factory key is not an EC key.

502

<kind> <code> <reason>

The enrollment failed. Blank parts are omitted. Example: REFUSED 4.03 identity-mismatch.

503

<reason>, plus the header Retry-After

The EST server answered CoAP 5.03. Retry-After is the Max-Age in seconds. No other failure gives 503.

A VALIDATION failure can have the reason leaf-expired. The server issued a leaf that is already past notAfter by the clock of the emulator. The emulator keeps the current certificate and the pending key.

The kind is one of REFUSED, UNAVAILABLE, HANDSHAKE, PIN_MISMATCH, TIMEOUT, CONFIG, VALIDATION. A CONFIG failure gives 409 with the reason token only. The reason contains only the characters A-Z, a-z, 0-9, ., _, -, and at most 64 characters. An unexpected internal error gives 502 TIMEOUT unexpected-error.

Re-enroll

Runs /crts and /sren with the current EST certificate. A successful re-enrollment restarts a running device once.

POST /api/devices/{clientId}/est/reenroll

The request has no body. The response is the same as for Enroll, with one more token.

Status Body Meaning

409

no-credential

The device has no valid EST certificate for the effective EST address. The emulator sends no request.

Get EST Status

Returns the EST state of the endpoint. The state survives a restart of the device, stop, and the re-creation of the device.

GET /api/devices/{clientId}/est

Response 200:

{
  "enrolled": true,
  "serialHex": "1a2b3c",
  "subject": "CN=emulator-0001",
  "issuer": "CN=EST CA",
  "notBefore": "2026-10-01T08:00:00Z",
  "notAfter": "2026-10-31T08:00:00Z",
  "caCertificates": ["CN=EST CA"],
  "estUri": "coaps://est.example.com:5694",
  "trustMode": "PINNED",
  "fault": "none",
  "lastOperation": "SEN",
  "lastError": null,
  "nextAttemptAt": "2026-10-16T12:00:00Z",
  "nextOperation": "SREN"
}
Field Type Description

enrolled

boolean

true when a valid EST certificate exists for the effective EST address.

serialHex, subject, issuer, notBefore, notAfter

string, instant

Data of the EST certificate. All five are null without a valid certificate. Instants use ISO-8601.

caCertificates

list of string

Subject names of the stored trust anchors. The list is empty when there are none.

estUri

string

The effective lwm2m.est.uri. An empty string means EST is off. The value is null when the device never started and has no recorded settings.

trustMode

string

PINNED, EXPLICIT_TA, or UNPINNED. null without a valid certificate.

fault

string

The effective lwm2m.est.fault. The value is null when the device never started and has no recorded settings.

lastOperation

string

SEN or SREN. The last finished operation. An operation in progress is never shown. null when no operation finished.

lastError

string

The summary of the last failure of a finished operation, <kind> <code> <reason>. An operation in progress is never shown. null after a success.

nextAttemptAt

instant

The time of the next automatic operation, in ISO-8601. null without a plan.

nextOperation

string

SEN or SREN. null without a plan.

Answer 200 also for a device that never started. Most fields are then null or empty. The emulator computes the status when you send the request. The fields enrolled and the certificate fields use the current effective EST address, the current pin, and the current clock. A new certificate appears in the status only together with its operation, outcome, CA list, and next plan. If the fleet address or pin changes, or if notAfter passes, the status shows not enrolled without a restart. The status of a stopped device shows the est/options overrides immediately.

Set EST Options

Overrides EST options for this device only. The emulator sends nothing and does not restart the device.

POST /api/devices/{clientId}/est/options
Content-Type: application/json

{
  "lwm2m.est.uri": "coaps://est.example.com:5694",
  "lwm2m.est.server_cert_sha256": "",
  "lwm2m.est.fault": "none"
}

The body is a JSON map with string values. Allowed keys:

  • lwm2m.est.uri

  • lwm2m.est.server_cert_sha256

  • lwm2m.est.fault

Rules:

  • An empty value removes the override. The fleet value from POST /api/protocolOptions applies again.

  • The request is all or nothing. If one entry is invalid, the emulator applies none.

  • The overrides are not saved. They are lost when the emulator process restarts.

  • The emulator does not validate the address or the pin here. An invalid value gives 409 at the next enrollment.

Status Body Meaning

204

(none)

The emulator applied the options.

400

unknown EST option or null value: <key>

The key is not allowed, or the value is null. The emulator applied nothing.

400

unknown EST fault: <value>

The fault value is not valid. The emulator applied nothing.

MTP (USP only)

Get MTP Configuration

Returns available MTP types and their port suggestions.

GET /api/mtp

Get Ports for MTP Type

GET /api/mtp/{mtpType}/ports

Set Active MTP

POST /api/mtp/{mtpType}

mtpType is one of STOMP, CoAP, WebSocket, MQTT. NOTE: The Web GUI sets MTP through the Protocol Options tab (/api/protocolOptions) — this endpoint is primarily for scripting / Swagger.

Responses: 200 the ports of that MTP type · 400 EMULATOR_NOT_STOPPED while the emulator is initialized or running · 400 INVALID_MTP_TYPE for a type with no ports configured.

Snapshot

Save Snapshot

Writes the current device list and parameter values to configuration/snapshot.json.

POST /api/snapshot/save

Returns 204 on success, 400 NOTHING_TO_SAVE when there are no devices to snapshot.

Load Snapshot

Restores the device list and parameter tree from configuration/snapshot.json and re-initializes the emulator session. The emulator must be stopped/reset before calling.

POST /api/snapshot/load

Returns 204 on success, 404 SNAPSHOT_NOT_FOUND when the snapshot file is missing, 400 BAD_REQUEST when the emulator is not idle.

Rules (JS hook on parameter updates)

Get Current Rule

Returns the JavaScript body of function onParamUpdated(param, device, ctx) persisted in configuration/rules.js, together with engineAvailable — whether this runtime has a JavaScript engine at all.

status is error, with error, when the rules.js found at startup did not compile — no rule is active then. lastRuntimeError is the last failure of the active rule while it ran.

GET /api/rule

Response:

{
  "code": "var p = device.params.get('Foo'); p.updatable = false;",
  "status": "ok",
  "engineAvailable": true,
  "lastRuntimeError": null
}

Compile & Save Rule

Compiles the supplied JavaScript via the emulator’s rule engine and writes it to configuration/rules.js. Returns 200 with status: "ok" on success, 400 with status: "error" and a populated error field on compilation failure.

Returns 501 with code RULES_UNAVAILABLE only when the runtime has no JavaScript engine. The emulator ships the standalone Nashorn engine, so this means its jar is missing from the classpath.

POST /api/rule
Content-Type: application/json

{
  "code": "if (param.name == 'X') { device.params.get('Y').updatable = false; }"
}

Get Rule Help

Returns the human-readable help text shown by Help in the Rules dialog and in the desktop Rules tab.

GET /api/rule/help

ACS Database

Extract a Device Tree

Reads the parameters of the CPE with the given serial, for the selected protocol, from the ACS database and writes them over the active device tree file. Initialize tree then loads them. dbPassword "*" means the stored password.

POST /api/acs/extract
Content-Type: application/json

{
  "dbAddress": "10.0.0.5",
  "dbPort": 3306,
  "dbLogin": "ftacs",
  "dbPassword": "***",
  "dbCatalog": "ftacs",
  "dbCpeSerial": "SN0001"
}

Response:

{ "fileName": "usp_resources.xml", "parameterCount": 412 }

Responses: 200 written · 400 BAD_REQUEST — DB address or CPE serial missing, or no protocol selected · 400 EMULATOR_NOT_STOPPED — the emulator is initialized or running · 404 CPE_NOT_FOUND · 422 EXTRACT_FAILED — the database could not be read or the file could not be written · 501 EXTRACT_UNAVAILABLE — the emulator cannot read this protocol’s trees from the database (CoAP, DUSUN).

Logs

The Logs endpoints expose the in-memory ring buffer (capacity ≈ 500 entries) of raw protocol messages observed by the emulator.

Poll Logs

Returns entries with sequence number strictly greater than since, plus the highest seq present so the client can advance its cursor.

GET /api/logs?since=0

Response:

{
  "lastSeq": 42,
  "entries": [
    {
      "seq": 42,
      "timestamp": "2026-09-14T09:05:03.123+03:00",
      "time": "9:5:03:123",
      "deviceName": "device-001",
      "direction": "IN",
      "event": "registration",
      "messageOptions": "GET /3/0/0 block1 {empty}",
      "payload": "{...}",
      "rawMessage": "..."
    }
  ]
}
timestamp

When the message was observed: ISO-8601 with the date, milliseconds and the emulator’s UTC offset, e.g. 2026-09-14T09:05:03.123+03:00. The offset is always numeric (+00:00, never Z), so a container running in UTC sends the same shape as a workstation.

time

Deprecated — read timestamp instead. Kept unchanged for one release so existing readers keep working. It carries the time of day only, with no date and no offset, formatted H:m:ss:SSS: hours and minutes are not zero-padded and the milliseconds follow a colon, so 09:05:03.123 is sent as 9:5:03:123.

For incremental polling, call with since=<lastSeq> from the previous response. On an empty batch, lastSeq is still advanced to the current server seq so the next poll skips already-seen entries.

Clear Logs

Empties the server-side ring buffer. The seq counter is not reset — instead the response returns the current seq so the client can resync.

POST /api/logs/clear

Response:

{ "lastSeq": 42, "entries": [] }

Configuration Files

On success the upload and delete endpoints answer 200 with a result object:

{
  "success": true,
  "message": "File replaced successfully",
  "filename": "my_config.xml",
  "replaced": true
}

replaced is meaningful on upload only: true when a file of that name was already there and has been overwritten, false for a new file. message follows it (File replaced successfully against File uploaded successfully), so a client can tell the user which of the two happened.

A failure answers with the error body described under Error Body: 400 BAD_REQUEST for an empty file, a name that does not end in .xml, a missing filename or a name that is not a device tree; 400 CONFIG_FILE_NOT_FOUND for a file that is not there; 409 for the two conflicts below; 500 CONFIG_FILE_UPLOAD_FAILED or CONFIG_FILE_DELETE_FAILED when the file could not be written or deleted.

List Config Files

GET /api/configFiles

Upload Config File

POST /api/uploadConfigFile
Content-Type: multipart/form-data

file: <XML file>

A file of the same name is overwritten — except when it is the device tree of an initialized session, which answers 409 CONFIG_FILE_IN_USE: that tree is held in memory and re-read per device, so replacing it would leave the session and the file describing two different device trees under one name. Stop the emulator first.

Delete Config File

POST /api/deleteConfigFile
Content-Type: application/json

{
  "filename": "my_config.xml"
}

Only a device tree can be deleted: filename is the bare name of an .xml file in the configuration directory, as GET /api/configFiles lists it. Anything else answers 400 BAD_REQUEST in the error body before any file is looked up — another kind of file kept there (devices.json, rules.js, a <protocol>_gui.properties), a path separator (/ or \), .., a drive letter, or . and .. themselves.

Deleting the file the emulator loads as its device tree answers 409 CONFIG_FILE_ACTIVE; select another file first. The check is made on the name before the file is looked up, so it applies whether or not the emulator is initialized.

Error Handling

See Error Body for the body and the status codes. The error codes an endpoint can answer with are listed with that endpoint; the ones this document names are:

Code Status Raised by

ALREADY_INITIALIZED

409

POST /api/init when the emulator is already initialized

NOT_INITIALIZED

409, 400

POST /api/start, /api/stop, /api/reset before initialization (409); POST /api/devices and /api/devices/batch before initialization (400)

EMULATOR_NOT_STOPPED

400

POST /api/protocol/{name}, /api/config, /api/mtp/{type}, /api/activeConfigFile and /api/acs/extract while the emulator is initialized or running

PROTOCOL_NOT_FOUND

400

POST /api/protocol/{name} for a protocol the emulator has not loaded

INVALID_MTP_TYPE

400

POST /api/mtp/{type} for a type with no ports configured

DEVICE_NOT_FOUND

404

GET /api/devices/{clientId} and /api/devices/{clientId}/parameters, /api/devices/{clientId}/parameters/{paramName}/history, PUT /api/devices/{clientId}/parameters/{paramName} and /api/devices/{clientId}/server, POST /api/devices/{clientId}/heartbeat, /start and /stop, for a client ID no live device has; DELETE /api/devices/{clientId} for one known neither as a live nor as a stored device. The autoupdate endpoints answer an unknown device with PARAMETER_NOT_FOUND (GET) or BAD_REQUEST (POST) instead

PARAMETER_NOT_FOUND

404

GET /api/devices/{clientId}/parameters/{paramName}/autoupdate and /api/devices/{clientId}/parameters/{paramName}/history, and PUT /api/devices/{clientId}/parameters/{paramName} for a parameter the device does not have

STORED_DEVICE_NOT_FOUND

404

POST /api/storedDevices/{clientId}/start, DELETE /api/storedDevices/{clientId}

DEVICE_ALREADY_EXISTS

400

POST /api/devices for a client ID that is already running

DEVICE_CREATION_FAILED

400

POST /api/devices/startNew, /api/devices and /api/devices/batch when a device could not be created

DEVICE_START_FAILED, DEVICE_STOP_FAILED, DEVICE_DELETE_FAILED

400

POST /api/devices/{clientId}/start (and /api/storedDevices/{clientId}/start), /api/devices/{clientId}/stop, DELETE /api/devices/{clientId} when the device refused

AUTOUPDATE_FAILED

400

POST /api/devices/{clientId}/parameters/{paramName}/autoupdate when the autoupdater could not be applied

NOTHING_TO_SAVE

400

POST /api/snapshot/save with no devices

SNAPSHOT_NOT_FOUND

404

POST /api/snapshot/load with no snapshot file

NO_RUNNING_LWM2M_DEVICE

409

POST /api/lwm2m/deregister and /api/lwm2m/update when no LwM2M device is running

CONFIG_FILE_NOT_FOUND

400

POST /api/deleteConfigFile and /api/activeConfigFile for a file the configuration directory does not have

CONFIG_FILE_UPLOAD_FAILED, CONFIG_FILE_DELETE_FAILED

500

POST /api/uploadConfigFile, /api/deleteConfigFile when the file could not be written or deleted

ALREADY_STARTED

409

POST /api/start when it is already running

INIT_FAILED

422

POST /api/init when the session could not be built

START_FAILED

422

POST /api/start when the devices refused to start

CONFIG_FILE_IN_USE

409

POST /api/uploadConfigFile for the device tree of an initialized session

CONFIG_FILE_ACTIVE

409

POST /api/deleteConfigFile for the active device tree

PARAMETER_NOT_UPDATABLE

409

PUT /api/devices/{clientId}/parameters/{paramName} for a parameter a rule has frozen

NOT_STARTED

409

PUT /api/devices/{clientId}/parameters/{paramName} for a device that has not been started

RULES_UNAVAILABLE

501

POST /api/rule on a runtime with no JavaScript engine

CPE_NOT_FOUND

404

POST /api/acs/extract for a serial the ACS database has no CPE with

EXTRACT_FAILED

422

POST /api/acs/extract when the database could not be read or the device tree file could not be written

EXTRACT_UNAVAILABLE

501

POST /api/acs/extract for a protocol the emulator cannot read device trees from the ACS database for (CoAP, DUSUN)

BAD_REQUEST

400

A malformed body, an unconvertible parameter, a device template naming an unknown protocol or a parameterFile that is a path, a POST /api/deleteConfigFile naming anything but a device tree, and a value an endpoint checks itself — an empty or non-.xml upload, a missing filename, a missing or null protocol option, a missing autoupdate body

NOT_FOUND, METHOD_NOT_ALLOWED, UNSUPPORTED_MEDIA_TYPE

404, 405, 415

Raised by the framework before the endpoint runs

INTERNAL_ERROR

500

An unhandled failure

A client should read a failed body as JSON and fall back to the raw text: a proxy in front of a stopped emulator answers with a page of its own, and an emulator older than this release answers some failures with plain text or no body.