Skip to main content

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:

  1. POST /api/noauth/authorization
  2. POST /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",
}
note

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:

  • startTime as a Unix timestamp in milliseconds
  • endTime as a Unix timestamp in milliseconds
  • limit
  • page
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:

  • 0 only available on the device
  • 1 requested from the server
  • 2 only available on the server
  • 3 available on both the device and the server
info

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.

note

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:

  1. POST /api/device/{device_id}/data-files/CLIENT_SCOPE/download
  2. GET /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
info

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:

  • startTime
  • endTime
  • limit
  • page
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:

  • startTime in ISO-8601 format
  • endTime in ISO-8601 format
  • tag, often preview, 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}'
info

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 Accept header 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.