API
API
optiCLOUD provides several technical interfaces for connecting devices, reading data, and automating processes. In everyday use, two types of access are particularly relevant:
- the REST API for authentication, device management, file access, and queries via HTTP
- the MQTT API for direct data exchange between the device and the platform
This page summarizes the basic API workflows and serves as a technical introduction for integrations and scripts.
Classification
The two APIs serve different purposes:
- The REST API is suitable for backend scripts, evaluations, file downloads, device searches, and management operations.
- The MQTT API is suitable for devices, gateways, and embedded systems that need to send or retrieve telemetry or attributes directly to the platform.
Additionally, the API Description can be accessed via the User Menu. Available REST endpoints are documented interactively there.
REST API Basics
Authentication
For REST access, a session is first established. The typical process consists of two steps:
POST /api/noauth/authorizationPOST /api/auth/login
In the first step, the username and password are sent to the platform. The response returns a list of tenants to which the user has access. Next, the authorityId is retrieved from the desired tenant assignment and sent to the login endpoint along with the username and password.
As a result, the platform returns a token, which is included in the header X-Authorization: Bearer <token> for subsequent requests.
A possible Python example:
import requests
BASEURL = "https://example.opticloud.io"
CREDENTIALS = {"username": "user@example.com", "password": "secret"}
TENANT = "MyTenant"
def login(baseurl, credentials, tenant):
auth_response = requests.post(
baseurl + "/api/noauth/authorization",
json=credentials,
verify=True,
)
auth_response.raise_for_status()
selected = None
for entry in auth_response.json():
try:
if entry["tenant"]["name"] == tenant:
selected = entry
break
except KeyError:
continue
if selected is None:
raise RuntimeError("Tenant not found")
login_body = {
"username": credentials["username"],
"password": credentials["password"],
"authorityId": selected["authorityId"]["id"],
}
login_response = requests.post(
baseurl + "/api/auth/login",
json=login_body,
verify=True,
)
login_response.raise_for_status()
token = login_response.json()["token"]
return {
"X-Authorization": f"Bearer {token}",
"Accept": "text/html, application/json, application/xhtml+xml, application/xml;q=0.9",
}
Selecting a specific tenant is only necessary if a user has access to multiple tenants.
Retrieving a Tenant's Device List
To list a tenant’s devices, use the endpoint GET /api/tenants/{tenant_id}/devices. Typically, the parameters page=0 and limit=-1 are set to retrieve all devices in a single response.
def get_devices(baseurl, header, tenant_id):
response = requests.get(
baseurl + f"/api/tenants/{tenant_id}/devices",
headers=header,
params={"page": 0, "limit": -1},
verify=True,
)
response.raise_for_status()
return response.json()["data"]
This returns, among other things, the device IDs required for further API calls.
Retrieving a Device's OSF Files
Processed OSF files or those already known to the server can be retrieved via GET /api/devices/{device_id}/data-files/original.
Typical parameters:
startTimeas a Unix timestamp in millisecondsendTimeas a Unix timestamp in millisecondslimitpage
def get_processed_osf_files(baseurl, header, device_id, start_time, end_time):
response = requests.get(
baseurl + f"/api/devices/{device_id}/data-files/original",
headers=header,
params={
"startTime": start_time,
"endTime": end_time,
"limit": -1,
"page": 0,
},
verify=True,
)
response.raise_for_status()
return response.json()["data"]
Retrieve the complete file list and storage status
If you need not only the files available on the server but all known files including their status, you can also use the endpoint /API/filelist/list/....
There are a few special considerations to note:
- The endpoint returns XML instead of JSON.
- The URL uses
/API/in uppercase. - Time values are expected here in seconds, not milliseconds.
Additionally, the device token is required first. This can be retrieved via GET /api/device/{deviceId}/credentials. The relevant field there is credentialsId.
import xmltodict
def get_listed_files(baseurl, header, device_id, start_time, end_time):
credentials_response = requests.get(
baseurl + f"/api/device/{device_id}/credentials",
headers=header,
verify=True,
)
credentials_response.raise_for_status()
credentials_id = credentials_response.json()["credentialsId"]
list_response = requests.get(
baseurl + f"/API/filelist/list/deviceid/{credentials_id}/StartTime/{start_time}/{end_time}",
headers=header,
verify=True,
)
list_response.raise_for_status()
list_dict = xmltodict.parse(list_response.content)
return list_dict["FileList"]["GeoLogFile"]
Possible status values:
0only available on the device1requested from the server2only available on the server3available on both the device and the server
An Accept header with XML support should be set for this XML endpoint. Without the appropriate header, the platform may respond with 406 Not Acceptable.
To read device credentials, the user account requires the appropriate permissions, specifically for viewing devices and device credentials.
Downloading OSF Files
Downloading files typically occurs in two steps:
POST /api/device/{device_id}/data-files/CLIENT_SCOPE/downloadGET /api/device/{device_id}/data-files/CLIENT_SCOPE/token/{download_token}
In the first step, the desired file is requested by filename. The response contains a download token. In the second step, the actual file is downloaded using this token.
import os
def download_processed_osf_file(baseurl, header, device_id, filename, savedir):
response = requests.post(
baseurl + f"/api/device/{device_id}/data-files/CLIENT_SCOPE/download",
headers=header,
json=[filename],
verify=True,
)
response.raise_for_status()
download_token = response.text
file_response = requests.get(
baseurl + f"/api/device/{device_id}/data-files/CLIENT_SCOPE/token/{download_token}",
headers=header,
verify=True,
)
file_response.raise_for_status()
target = os.path.join(savedir, filename.split("/")[-1])
with open(target, "wb") as handle:
handle.write(file_response.content)
return target
If multiple files are requested at the same time, the response is usually a ZIP archive rather than a single .osfz file.
Retrieving a device's latest telemetry data
To retrieve current values, use GET /api/plugins/telemetry/DEVICE/{device_id}/values/timeseries. In many cases, startTs and endTs are simply set to the current time if only the latest values are needed.
import time
def get_latest_telemetry(baseurl, header, device_id):
now_ms = int(time.time() * 1000)
response = requests.get(
baseurl + f"/api/plugins/telemetry/DEVICE/{device_id}/values/timeseries",
headers=header,
params={"startTs": now_ms, "endTs": now_ms},
verify=True,
)
response.raise_for_status()
return response.json()
The result contains the current values for each data channel along with timestamps.
Retrieving Device Communication
Communication events for a device can be retrieved via GET /api/devices/{deviceId}/communication/data. Depending on the device and platform configuration, these include entries such as PING, ATTN, or file responses.
Required parameters:
startTimeendTimelimitpage
def get_device_communication(baseurl, header, device_id, start_ts, end_ts):
response = requests.get(
baseurl + f"/api/devices/{device_id}/communication/data",
headers=header,
params={
"startTime": start_ts,
"endTime": end_ts,
"limit": 2147483647,
"page": 0,
},
verify=True,
)
response.raise_for_status()
return response.json()
Sending Commands to a Device
Some REST endpoints are used to queue commands for a device. These endpoints typically do not establish a direct online connection to the device. Instead, the command is processed the next time the device connects.
One example is listing files via LIST_FILES. This is done using POST /api/devices/{deviceId}/request-list.
The request body typically contains:
startTimein ISO-8601 formatendTimein ISO-8601 formattag, oftenpreview, for example
def request_list(baseurl, header, device_id, start_time, end_time, tag):
response = requests.post(
baseurl + f"/api/devices/{device_id}/request-list",
headers=header,
json={
"startTime": start_time,
"endTime": end_time,
"tag": tag,
},
verify=True,
)
response.raise_for_status()
MQTT API Basics
The MQTT interface is intended for devices that communicate directly with optiCLOUD. Typical tasks include:
- Sending telemetry
- Uploading client attributes
- Requesting shared or client attributes
- Subscribing to shared attributes
Authentication is typically performed using the device access token as the MQTT username.
Supported JSON Format
By default, the MQTT API uses a JSON-based key-value format. Keys are strings; values can be strings, Boolean values, numbers, or even nested JSON objects.
{
"stringKey": "value1",
"booleanKey": true,
"doubleKey": 42.0,
"longKey": 73,
"jsonKey": {
"someNumber": 42,
"someArray": [1, 2, 3],
"someNestedObject": {
"key": "value"
}
}
}
Sending Telemetry via MQTT
To upload telemetry data, publish to the topic v1/devices/me/telemetry.
Simple format without a custom timestamp:
{"temperature": 42}
Alternatively, an array of objects is also possible:
[{"key1": "value1"}, {"key2": true}]
If no timestamp is included, the platform uses the server-side timestamp.
If the device can record timestamps itself, the following format is usually used:
{
"ts": 1451649600512,
"values": {
"temperature": 42,
"humidity": 55
}
}
Here, ts is a Unix timestamp in milliseconds.
An example using mosquitto_pub:
mosquitto_pub -d -q 1 \
-h "demo.opticloud.io" \
-t "v1/devices/me/telemetry" \
-u "$ACCESS_TOKEN" \
-m '{"temperature":42}'
Replace demo.opticloud.io with your host and $ACCESS_TOKEN with the device's access token.
Sending Attributes via MQTT
Client-side device attributes are published to the topic v1/devices/me/attributes.
Example:
{
"attribute1": "value1",
"attribute2": true
}
Example using mosquitto_pub:
mosquitto_pub -d \
-h "demo.opticloud.io" \
-t "v1/devices/me/attributes" \
-u "$ACCESS_TOKEN" \
-m '{"attribute1":"value1","attribute2":true}'
Requesting attributes from the server
Devices can also actively request client and shared attributes from the server. To do this, a request is published to a request topic:
v1/devices/me/attributes/request/$request_id
At the same time, the device must subscribe to the corresponding response topic:
v1/devices/me/attributes/response/+
Since publishing and subscribing must occur within the same MQTT session, this is often implemented using an MQTT client library. A minimal example using mqtt.js:
var mqtt = require('mqtt');
var client = mqtt.connect('mqtt://demo.opticloud.io', {
username: process.env.TOKEN
});
client.on('connect', function () {
client.subscribe('v1/devices/me/attributes/response/+');
client.publish(
'v1/devices/me/attributes/request/1',
'{"clientKeys":"attribute1,attribute2","sharedKeys":"shared1,shared2"}'
);
});
client.on('message', function (topic, message) {
console.log('response.topic: ' + topic);
console.log('response.body: ' + message.toString());
client.end();
});
To run the example:
export TOKEN=$ACCESS_TOKEN
node mqtt-js-attributes-request.js
Practical Notes
The following points are particularly important for production integrations:
- REST and MQTT have different responsibilities and should be combined strategically.
- Some endpoints operate in milliseconds, others in seconds.
- For XML endpoints, a suitable
Acceptheader is important. - Commands sent to devices are often only queued and not executed immediately.
- Additional permissions are required for sensitive endpoints such as device credentials.
Classification
The API forms the technical foundation for external integrations, device software, automation scripts, and customer-specific evaluations related to optiCLOUD.
For operation via the user interface, the Control Center, Dashboards, and Automation sections are particularly relevant. The API complements these sections when data or functions need to be processed outside the standard user interface.