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
API Documentation
Interactive API documentation is available via Swagger UI:
http://localhost:8556/swagger-ui.html
OpenAPI specification:
http://localhost:8556/v3/api-docs
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
|
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 |
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/configmaskspskKey,authKeyStorePass,trustStorePass,aliasPassanddbPassword -
GET /api/protocolOptionsmasks the value of every free-text option flaggedsecret -
GET /api/storedDevicesmasks the four template secrets and every secretprotocolOptionsentry
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 -
409ALREADY_INITIALIZED— the emulator is already initialized; stop it first -
422INIT_FAILED— the session could not be built (an unreadable or invalid device tree, for example).messagecarries the reason the emulator logged; nothing is left running.
Start Emulator
Start device simulation.
POST /api/start
Responses:
-
204— started -
409NOT_INITIALIZED— initialize the device tree first -
409ALREADY_STARTED— the emulator is already started -
422START_FAILED— the devices refused to start
Stop Emulator
Stop device simulation.
POST /api/stop
Responses:
-
204— stopped -
409NOT_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 -
409NOT_INITIALIZED— there is nothing to reset
|
|
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": falseselects NoSec: the device uses plain CoAP, whateversecurityModesays and whatever an earlier request set. The keystore, trust store and PSK fields are kept for a later secure configuration. -
"secure": trueor nosecurefield keeps the security mode, or sets the one insecurityMode. -
GET /api/configreturnssecureas 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
Set Protocol
POST /api/protocol/{protocolName}
POST /api/protocol/{protocolName}?keepOptions=true
Query parameters:
-
keepOptions(optional, defaultfalse) — 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. WithkeepOptions=true, which the Web GUI sends, re-selecting the current protocol changes nothing and a switch to another protocol restores the options saved ingui_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 |
|---|---|
|
The device’s server accepted it — the protocol’s own confirmation, not merely an open socket. |
|
The device is not connected to its server now. |
|
The device cannot tell. A device whose state cannot be read is listed as |
What each protocol counts as its server’s confirmation, and what clears it:
| Protocol | connection |
|---|---|
LWM2M |
|
USP |
|
MQTT, FSH_HUB, DUSUN |
|
COAP |
Always |
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 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 |
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 -
404DEVICE_NOT_FOUND— no such device -
404PARAMETER_NOT_FOUND— the device has no parameter by that name -
409NOT_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. -
409PARAMETER_NOT_UPDATABLE— a rule has frozen the parameter (updatable = falsein a rule script). The value is left unchanged and nothing is sent to the device. Such a parameter is listed with"writable": falsebyGET /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 asGET /api/devices/{clientId}) -
400DEVICE_CREATION_FAILED— the device could not be created or started;messagesays why -
400BAD_REQUEST—protocolnames none of the emulator’s protocols. The name is matched without regard to case (mqttisMQTT) and is checked before anything else is read. The same holds for every template ofPOST /api/devices/batch. -
400BAD_REQUEST—parameterFileis 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 aslwm2m_resources.xml, is what is expected. The same holds for every template ofPOST /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.
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 |
|
The device does not use the LwM2M protocol. |
409 |
|
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 |
|
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 |
|
The effective |
409 |
|
The device does not use the X.509 security mode. |
409 |
|
The device uses LwM2M over TCP. |
409 |
|
An effective EST option has an invalid value. |
409 |
|
The factory keystore is missing or has no usable entry. |
409 |
|
The CN of the factory certificate does not equal the endpoint name. |
409 |
|
The factory key is not an EC key. |
502 |
|
The enrollment failed. Blank parts are omitted. Example: |
503 |
|
The EST server answered CoAP 5.03. |
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 |
|
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 |
|---|---|---|
|
boolean |
|
|
string, instant |
Data of the EST certificate. All five are |
|
list of string |
Subject names of the stored trust anchors. The list is empty when there are none. |
|
string |
The effective |
|
string |
|
|
string |
The effective |
|
string |
|
|
string |
The summary of the last failure of a finished operation, |
|
instant |
The time of the next automatic operation, in ISO-8601. |
|
string |
|
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/protocolOptionsapplies 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
409at the next enrollment.
| Status | Body | Meaning |
|---|---|---|
204 |
(none) |
The emulator applied the options. |
400 |
|
The key is not allowed, or the value is |
400 |
|
The fault value is not valid. The emulator applied nothing. |
MTP (USP only)
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; }"
}
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, neverZ), so a container running in UTC sends the same shape as a workstation. time-
Deprecated — read
timestampinstead. Kept unchanged for one release so existing readers keep working. It carries the time of day only, with no date and no offset, formattedH:m:ss:SSS: hours and minutes are not zero-padded and the milliseconds follow a colon, so 09:05:03.123 is sent as9: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.
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.
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 |
|---|---|---|
|
409 |
|
|
409, 400 |
|
|
400 |
|
|
400 |
|
|
400 |
|
|
404 |
|
|
404 |
|
|
404 |
|
|
400 |
|
|
400 |
|
|
400 |
|
|
400 |
|
|
400 |
|
|
404 |
|
|
409 |
|
|
400 |
|
|
500 |
|
|
409 |
|
|
422 |
|
|
422 |
|
|
409 |
|
|
409 |
|
|
409 |
|
|
409 |
|
|
501 |
|
|
404 |
|
|
422 |
|
|
501 |
|
|
400 |
A malformed body, an unconvertible parameter, a device template naming an unknown protocol or a
|
|
404, 405, 415 |
Raised by the framework before the endpoint runs |
|
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.