NAV
javascript python (2.x) cURL C++

Introduction

The iTwin IoT API provides direct access to device and sensor information, as well as raw and processed observation data that exist in our Cloud application or your Enterprise server.

We use the socket protocol to establish and authenticate all API requests and provide a two-way, real-time data connection between your client and our server.

We have included working code samples in JavaScript and Python (2.x) to demonstrate our API in action. You are not limited to using Javascript or Python in your application and can use any language that supports sockets.

Authentication

Our API uses API keys or IMS JWT Tokens to authenticate socket connections and REST API calls.

You can generate and view your unique API key by doing the following:

  1. Log in to our Cloud application (app.sensemetrics.com) or your Enterprise server.
  2. Hover over the settings icon (gear icon) at the top right.
  3. Select Development from the menu to view or re-generate your API key.

API keys do not expire, however you can re-generate your key if you believe the old one has been compromised.

Also note that your API key will allow you to access only the objects and data that is accessible to you within our Cloud application or your Enterprise server. Our API cannot be used to modify access permissions at this time.

For iTwin documentation and obtaining IMS JWT Tokens please see here: https://developer.bentley.com/apis/overview/authorization

Managing Connections

Opening a Connection

Open and authenticate a new socket connection, listen for messages and events:

var socket,
    connected,
    authenticating,
    nextId = 0,
    host = "iiot.bentley.com",
    port = 4201,
    apiKey = "YourAPIKey";

// Open a new socket connection
socket = new WebSocket("wss://" + host + ":" + port + "/");

// Listen for socket open events
socket.onopen = function(event) {

    console.log("Socket opened with " + host + ":" + port);

    // Perform socket authentication
    authenticating = true;
    sendRequest("authenticate", {
        "code": apiKey
    }, function(result) {

        console.log("Authentication successful");

        // Save the next request id
        nextId = result.nextId;

        // Set connection flags
        connected = true;
        authenticating = false;
    });
};

// Listen for socket close events
socket.onclose = function() {

    console.log("Socket closed");

    // Set connection flags
    connected = authenticating = false;

    // Clear the socket object and reset request id
    socket = undefined;
    nextId = 0;
};

// Listen for socket messages
socket.onmessage = function(event) {
    onMessage(event.data);
};
import socket, json
from dateutil import parser

host = "iiot.bentley.com"
port = 4200
apiCode = "YourAPIKey"

# Open a new socket connection
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))

# Send the authentication request
handshake = json.dumps({
    "jsonrpc": "2.0",
    "method": "authenticate",
    "params": {
        "code" : apiCode
    },
    "id": 0
})
s.send(handshake)

# Parse the response
jsonFrame = decodeOneJsonFrame(s)
response = json.loads(jsonFrame)
print("\r\nHandshake Exchange:\r\n" + "     --> " + handshake + "\r\n" + "     <-- " + jsonFrame)

# Close the socket connection
s.close()

Our API is built on the JSON-RPC 2.0 specification, a simple and lightweight remote procedure call protocol.

Its general mechanism consists of two peers establishing a data connection. During the lifetime of a connection, peers may invoke methods provided by the other peer. To invoke a remote method, a request is sent. Unless the request is a notification, it must be replied to with a response.

Before using any API calls, you must establish and authenticate a socket connection between your client and our server using your unique API key or JWT Token.

You will need the following to open a connection:

Parameter Example Description
host iiot.bentley.com The URL of the server that you are connecting to.
port 4201 The socket connection port. Should be 4201 for WebSocket connections and 4200 for all other socket connections.
code A8345HJBWW45939KUIYB Your unique API key or JWT Token.

After the connection has been established and authenticated, you can begin sending API requests and receiving responses from the server.

Sending Requests

A generic function for sending requests via a socket:

var callbacks = {}

function sendRequest(request, params, callback) {

    // Get the request id to use
    var requestId = nextId;

    // Send the request message
    socket.send(JSON.stringify({
        "jsonrpc": "2.0",
        "method": request,
        "id": requestId,
        "params": params
    }));

    // Save the response callback for later
    callbacks[requestId] = callback;

    // Increment the id for future requests
    nextId++;

    return requestId;
};
callbacks = {}

def sendRequest(request, params, callback):

    # Get the request id to use
    requestId = nextId

    # Send the request message
    socket.send(json.dumps({
        "jsonrpc": "2.0",
        "method": request,
        "id": requestId,
        "params": params
    }))

    # Save the response callback for later
    callbacks[requestId] = callback

    # Increment the id for future requests
    nextId = nextId + 1

    return requestId;

Once a connection has been established and authenticated, you can begin to send requests to our server.

Each request should be in valid JSON format and contain the following properties:

Property Example Description
jsonrpc 2.0 The JSON-RPC protocol version. We use version 2.0.
method getSensors The name of the API request being sent.
id 5 The sequential API request id.
params {"sensors": ["sensorId1"]} Any required or optional API request parameters.

The method and params fields will change depending on the API request being performed. Some API requests have required parameters, others have optional parameters.

The numeric id field should be sequentially incremented with each new API request. When you authenticate a new connection, the response will contain a nextId field that should become the starting id for all future requests through that connection.

The responses you will receive from our server will include the original request id, allowing you to correctly route response messages to their request callbacks.

Receiving Responses

A generic function for processing socket responses:

function onMessage(message) {

    // Parse the JSON message body
    var response = JSON.parse(message);

    if ("result" in response) {

        // Request succeeded without errors
        callbacks[response.id](response.result);

    } else if ("error" in response) {

        // Request resulted in an error
        if ("id" in response) {

            // Send error information to original callback for handling
            callbacks[response.id](response.error);

        } else {

            // Log all other socket errors
            console.error("Received API error message: ", response.error);
        }
    }
};
# Gets one JSON frame from the TCP stream
# Extremely slow and not reliable, shown for example only
def decodeOneJsonFrame(socket):
    frame = ""
    count = 0
    while True:
        byte = socket.recv(1)
        if byte == "{":
            count = count + 1
        elif byte == "}":
            count = count - 1
        frame = frame + byte
        if count == 0:
            return frame;

API responses which you receive back from the server will be in JSON format and will contain a data object with the following properties:

Property Example Description
id 5 The id of the original API request. Can be used for routing responses to their request callbacks.
result {...} The data returned by the API response. The contents of this will depend on the original API request.
error {...} If the API request resulted in error, this optional field will contain the error code and information.

Working With Entities

Introduction

Example connection (node) object:

{
    "id": "/fn/6C4F6D/node",
    "type": "THREAD",
    "props": {
        "ACCESSIBLE": true,
        "DEVICE_GROUP": "/fn/6C4F6D/node",
        "GATEWAY": "/fn/6B652F/node",
        "LAST_COMM": "2016-10-18T22:26:24.818Z",
        "LOCATION": {
            "coordinates": [
                -53.900861,
                30.602073
            ]
        },
        "NAME": "North Sector THREAD #1",
        "OBSERVATION_COUNT": 16521,
        "POWER_MODE": "HIGH_POWER",
        "PROJECT_ID": "60F8B30928A37A575ECEE061"
    }
}

Example device object:

{
    "id": "/fn/6C4F6D/node/dgsimlogger/032432/device",
    "type": "DGSI_M_LOGGER",
    "props": {
        "ACCESSIBLE": true,
        "EXTERNAL_PORT": 1,
        "LAST_OBSERVATION": {
            "timestamp": "2016-06-24T18:01:00.000Z"
        },
        "LOCATION": {
            "coordinates": [
                -53.900861,
                30.602073,
                -17.24
            ]
        },
        "MODE": "IPI_BIAXIAL",
        "NAME": "North Sector Logger 2",
        "NODE_ID": "/fn/6C4F6D/node",
        "OBSERVATION_COUNT": 8832,
        "SAMPLING_RATE_MAX": 0.0011111111111111111,
        "SENSOR_GROUP": "/fn/6C4F6D/node/dgsimlogger/032432/device",
        "SIZE": 4,
        "ZERO_DATE": "2016-03-22T07:00:00.000Z",
        "PROJECT_ID": "60F8B30928A37A575ECEE061"
    }
}

Example sensor object:

{
    "id": "/fn/6C4F6D/node/dgsimlogger/032432/device/tilt4/sensor",
    "device": "/fn/6C4F6D/node/dgsimlogger/032432/device",
    "type": "TILT_BIAXIAL_DGSI",
    "props": {
        "ACCESSIBLE": true,
        "LAST_OBSERVATION": {
            "sensorId": "/fn/6C4F6D/node/dgsimlogger/032432/device/tilt4/sensor",
            "timestamp": "2016-06-24T18:01:00.000Z",
            "values": [
                -0.20226,
                0.17345,
                0.70659
            ]
        },
        "LOCATION": {
            "coordinates": [
                -53.900861,
                30.602073,
                -17.24
            ]
        },
        "MUX_INDEX": 4,
        "NAME": "North Sector Tilt 2-2",
        "NODE_ID": "/fn/6C4F6D/node",
        "OBSERVATION_COUNT": 2208,
        "SAMPLING_RATE_MAX": 0.0011111111111111111,
        "SIZE": 4,
        "ZERO_DATE": "2016-03-22T07:00:00.000Z",
        "PROJECT_ID": "60F8B30928A37A575ECEE061"
    }
}

An iTwin IoT network consists of a hierarchy of connections (also called nodes), devices and sensors. Together, we often refer to these objects as entities:

Type Example Description
connection (node) THREAD Establishes a connection to a data source or collects data from devices & sensors.
device DGSI M Logger A physical device that provides connectivity to sensors or a representation of groups of sensors.
sensor Biaxial Tilt A single physical sensor capable of recording observations.

All entities always exist in a network in a hierarchical structure:

Each entity object will typically include the following properties:

Property Example Description
id /fn/6C4F6D/node The unique id of the entity in the network. Used for retrieving, subscribing to and modifying entities.
type DGSI_M_LOGGER The type of the connection, device or sensor.
props {...} An object containing key/value pairs with configuration parameters for the entity.

API Requests

The following example illustrates retrieving a sensor object from the server and subscribing to its updates:

var sensor,
    sensorId = "/fn/6C4F6D/node/dgsimlogger/032432/device/tilt4/sensor",
    subscriptionId;

// Retrieve sensor from the server
sendRequest("getSensors", {
    "sensors": [sensorId]
}, function(result) {

    if (result.length > 0 && result[0].id === sensorId) {

        // Sensor retrieved, subscribe to its updates
        sensor = result[0];
        subscriptionId = sendRequest("subscribeSensors", {
            "sensors": [sensorId]
        }, function(updates) {

            if ("data" in updates) {

                // Update the local sensor object
                sensor = updates.data;
            }
        });

    } else {

        // Sensor could not be retrieved
        console.error("Error retrieving sensor", sensorId, ":", result);
    }
});
nextId = nextId + 1
sensor = {}
sensorId = "/fn/6C4F6D/node/dgsimlogger/032432/device/tilt4/sensor"

# Retrieve sensor object from server
s.send(json.dumps({
    "jsonrpc": "2.0",
    "method": "getSensors",
    "id": nextId,
    "params": {
        "sensors" : [sensorId]
    }
}))

# Decode server response
response = json.loads(decodeOneJsonFrame(s))
firstSensorId = response["result"][0]["id"]
if firstSensorId == sensorId:

    # Save sensor object locally
    sensor = response["result"][0]

    # Subscribe to sensor updates
    nextId = nextId + 1
    s.send(json.dumps({
        "jsonrpc": "2.0",
        "method": "subscribeSensors",
        "id": nextId,
        "params": {
            "sensors" : [sensorId]
        }
    }))

    # Handle incoming updates asynchronously
    while True:

        # Decode response
        response = json.loads(decodeOneJsonFrame(s))

        # Store the incoming observations
        if "data" in response["result"]:
            sensor = response["result"]["data"]

Several API requests are available to retrieve, subscribe to, modify, and create entities:

Type Example Description
retrieval getNodes
getDevices
getSensors
Retrieves all connection, device or sensor objects in their current state that match your query parameters.
subscriptions subscribeNodes
subscribeDevices
subscribeSensors
Establishes a subscription to any changes in the state of specified connections, devices or sensors. You will receive a new response message when any of the configuration parameters change. Returned subscriptionId can be used to later unsubscribe from updates.
subscription terminations unsubscribeNodes
unsubscribeDevices
unsubscribeSensors
Unsubscribe from updates using the subscriptionId returned by the subscription API request.
modification updateNode
updateDevice
updateSensor
Overwrite existing configuration parameters of a single connection, device or sensor.
creation createNode
createDevice
createSensor
Creates node, device or sensor in the iTwin IoT platform. If the node, device or sensor already exists, this will return an error. See the entity object example above.

Creating Entities

Creates a connection (node), device or sensor in the iTwin IoT platform. If the connection, device or sensor already exists, an error will be returned.

Create Node

Example createNode request/response:

{
   "jsonrpc": "2.0",
   "method": "createNode",
   "id": "2",
   "params": {
      "node": "/fn/02DDXX/node",
      "type": "THREAD",
      "props": {
         "NAME": "Sample thread",
         "SERIAL_NO": "02DDXX",
         "VERSION": "v4_6"
      }
   }
}

{
   "jsonrpc": "2.0",
   "id": 2,
   "result": {
      "id": "/fn/02DDXX/node",
      "type": "THREAD",
      "props": {
         "NAME": "Sample thread",
         "NODE_ID": "/fn/02DDXX/node",
         "ACCESSIBLE": true
      }
   }
}
Property Example Description
method createNode The name of method.
id 2 The sequential API request id.
params.node /fn//node/ The connection id to be created, the example format should be followed.
params.type THREAD The connection type.
params.props.SERIAL_NO 022DDXX Serial number of the connection.
params.props.VERSION v4_6 Version of the client software. Required field.

Create Device

Example createDevice request/response:

{
   "jsonrpc": "2.0",
   "method": "createDevice",
   "id": "3",
   "params": {
      "device": "/fn/02DDC5/node/demo/032432/device",
      "type": "DEMO",
      "props": {
         "NAME": "Demo device",
         "NODE_ID": "/fn/02DDC5/node"
      }
   }
}

{
   "jsonrpc": "2.0",
   "id": 3
   "result": {
      "id": "/fn/02DDC5/node/demo/032432/device",
      "type": "DEMO",
      "props": {
         "NAME": "Demo device",
         "NODE_ID": "/fn/02DDC5/node",
         "ACCESSIBLE": true
      }
   }
}
Property Example Description
method createDevice The name of method.
id 3 The sequential API request id.
params.device /fn/02DDC5/node/demo/032432/device/ The device id to be created, the example format should be followed.
params.type DEMO The device type.
params.props.NODE_ID 022DDXX Connection id to which this device should be attached. Required field.
params.props.NAME Demo device Name of the device.

Create Sensor

Example createSensor request/response:

{
   "jsonrpc": "2.0",
   "method": "createSensor",
   "id": "4",
   "params": {
      "sensor": "/instantel/3E216E/node/demo_sensor/UM9169/device/demo-1/sensor",
      "observationType": "DEMO_SENSOR",
      "type": "DEMO_SENSOR",
      "props": {
         "NAME": "Demo Sensor"
      }
   }
}

{
   "jsonrpc": "2.0",
   "id": 4
   "result": {
      "type": "DEMO_SENSOR",
      "device": "/instantel/3E216E/node/demo_sensor/UM9169/device",
      "id": "/instantel/3E216E/node/demo_sensor/UM9169/device/demo-1/sensor",
      "props": {
         "ACCESSIBLE": true,
         "SERIAL_NO": "UM9169",
         "NODE_ID": "/instantel/3E216E/node",
         "NAME": "Demo Sensor"
      }
   }
}
Property Example Description
method createSensor The name of method.
id 4 The sequential API request id.
params.sensor /instantel/3E216E/node/demo_sensor/UM9169/device/demo-1/sensor/ The sensor id to be created, the example format should be followed.
params.type DEMO_SENSOR Observation/Sensor type.
params.observationType DEMO_SENSOR Observation/Sensor type.
params.props.NAME Demo Sensor Name of the sensor.

Working with Projects

Introduction

Project Management allows users to efficiently manage multiple projects from a single user account. Instead of users owning connection/devices/sensors, these assets are now owned by the project.

This means in order to retrieve assets from a specific Project you must provide the appropriate projectId in the API request.

See here for more information about Projects.

REST Requests

In general, our REST API endpoints to retrieve assets (i.e. nodes/devices/sensors) utilize an optional projectId query parameter. Whenever a projectId is not provided, these endpoints will assume the organization’s Default Project is being requested. Example endpoints include:

Http Method Endpoint Description
GET /nodes/all?projectIds={projectId} Return all nodes for the given project
GET /devices/all?projectIds={projectId} Return all devices for the given project
GET /sensors/all?projectIds={projectId} Return all sensors for the given project
GET /documents?projectIds={projectId} Return all documents for the given project
GET /profiles?projectIds={projectId} Return all profiles for the given project

However, when making requests on projects themselves, projectIds will be required as a part of the url resource path. Examples endpoints include:

Http Method Endpoint Description
GET /projects/{projectId} Return a project
DELETE /projects/{projectId} Delete a project
GET /projects/{projcetId}/dataUsage Returns data usage for a project

Websocket Requests

Example getNodes request/response:

{
    "jsonrpc":"2.0",
    "method":"getNodes",
    "id":"1",
    "params": {
       "nodes":["/fn/6C954F/node"],
       "projectIds":["60D90DF0B2BD096E970C2824"]
    }
}
{
    "jsonrpc":"2.0",
    "id": 1,
    "result": [{
      "type": "DYNAMIC",
      "id": "/fn/6C954F/node",
      "props": {
        "PROJECT_ID": "60D90DF0B2BD096E970C2824",
        "ACCESSIBLE": false,
        "MODEL": "v11",
        "NODE_ID": "/fn/6C954F/node",
        "PORT_PROXY": 28957,
        "POWER_MODE": "HIGH_POWER",
        "LAST_MODIFIED_BY": "support",
        "LAST_MODIFIED_DATE": "2021-09-01T17:37:14.582Z",
        "LOCATION": {"type":"Point","coordinates":[null,null,null]},
        "SERIAL_NO": "6C954F",
        "NAME": "Thread 6C954F",
        "LAST_COMM": "2021-09-16T18:00:58.460Z",
        "INTEGRATION_ID": "THREAD_SDE",
        "CREATION_DATE": "2021-09-01T17:32:57.556Z",
        "VERSION": "v8_1-SNAPSHOT",
        "DEVICE_GROUP": "/fn/6C954F/node",
        "OBSERVATION_COUNT": 2519,
        "TYPE": "XC"
      },
      "customProps": {}
    }]
}

Our Websocket API endpoints also require projectIds, albeit in the form of an additional param.projectIds array (optional and falls back to organization’s Default Project). The projectIds can also be supplied with an iTwin asset ID provided you are using a JWT token from IMS.

Project FAQ

Q: Can I supply multiple projectIds to retrieve assets that span across multiple projects?
A: Currently only a single projectId is supported per request. Implementing support for multiple projectIds is currently on our road map.

Q: How to pull data from multiple projects since the PM release?
A: Currently only a single projectId is supported per request. Implementing support for multiple projectIds is currently on our road map.

Q: How do I know which Project my asset is in?
A: Every asset now contains a PROJECT_ID property which specifies which project the asset is managed/owned by.

Q: Where can I find a Project’s projectId?
A. ProjectIds can be found on the sensemetrics Platform under Project Settings see here for more details

Working with Observations

Introduction

Example data series:

{
    "2016-12-15T23:38:07.000Z": 77.53769759239933,
    "2016-12-16T07:13:07.000Z": 77.79861178632439,
    "2016-12-29T01:26:04.000Z": 77.68848770690741
}

Each sensor in the network is capable of recording observations that are stored on the server.

Observations are recorded and stored as raw numerical values in the sensor’s native metrics and units. The raw observations can often be converted to various derivative metric & unit combinations and retrieved via the API.

Each retrieved data point will be an object with the key representing the observation time stamp and the value representing the observation value:

Property Example Description
key 2016-12-15T23:38:07.000Z The time stamp of the data point, in ISO date format.
value 77.9759239933 The observation value in the metric & unit specified by the API request parameters.

If your API request found multiple observations that match your query parameters, they will be returned as a single object with the keys representing the timestamps of the observations.

Metrics

The following example illustrates retrieving metrics and units for a specific sensor:

var allMetrics = [],
    allUnits = {},
    sensorId = "/fn/6C4F6D/node/dgsimlogger/032432/device/tilt4/sensor";

// Retrieve metrics for this sensor
sendRequest("getMetrics", {
    "sensors": [sensorId]
}, function(result) {
    if (!("code" in result) && sensorId in result) {

        // Save all metrics
        allMetrics = result[sensorId];
        var firstMetric = allMetrics[0].id;

        // Retrieve units for first available metric
        sendRequest("getUnits", {
            "metrics": [firstMetric]
        }, function(result) {

            // Save the retrieved units
            allUnits[firstMetric] = result[firstMetric];
        });

    } else {
        console.warn('Error retrieving metrics:', result);
    }
});
nextId = nextId + 1
allMetrics = []
allUnits = {}
sensorId = "/fn/6C4F6D/node/dgsimlogger/032432/device/tilt4/sensor"

# Retrieve metrics for this sensor
s.send(json.dumps({
    "jsonrpc": "2.0",
    "method": "getMetrics",
    "id": nextId,
    "params": {
        "sensors" : [sensorId]
    }
}))

# Decode server response and save the metrics
response = json.loads(decodeOneJsonFrame(s))
allMetrics = response["result"][sensorId]
firstMetric = allMetrics[0]["id"]

# Retrieve the units for the first metric
nextId = nextId + 1
s.send(json.dumps({
    "jsonrpc": "2.0",
    "method": "getUnits",
    "id": nextId,
    "params": {
        "metrics" : [firstMetric]
    }
}))

# Decode server response and save the units
response = json.loads(decodeOneJsonFrame(s))
allUnits[firstMetric] = response["result"][firstMetric]

Example metric object:

{
    "defaultUnit": "m",
    "displayName": "∆ X",
    "id": "dx",
    "name": "displacement X",
    "notation": "∆x",
    "params": [
        {
            "id": "dt",
            "mandatory": false
        }, {
            "id": "START_DATE",
            "mandatory": false
        }, {
            "id": "END",
            "mandatory": false
        }, {
            "id": "ZERO_DATE",
            "mandatory": false
        }
    ]
}

Example unit object:

{
    "id": "m",
    "name": "meter",
    "notation": "m"
}

Before requesting observation data for a sensor, you must know the metrics and units applicable to that sensor.

All sensors have a set of raw metrics & units that are used to record raw observations output by the sensor. Many sensors also have derivative metrics & units that can be calculated from the sensor’s raw observations.

You can retrieve the metrics and units for each sensor using the following API requests:

Request Params Description
getRawMetrics “sensors” [array]
“types” [array]
Retrieves available raw metrics for an array of sensor id’s or sensor types. You will typically use the getMetrics API request described below in favor of this one.
getMetrics “sensors” [array] Retrieves all available metrics for an array of sensor id’s.
getUnits “metrics” [array] Retrieves all available units for an array of metric id’s.

Metric Parameters

Example metric parameters specifying reference date and sensor azimuth:

{
    "AZIMUTH_XY": {
        "value": 90
    },
    "ZERO_DATE": {
        "value": "2017-03-01T22:00:00.000Z"
    }
}

As mentioned above, derivative metrics & units are calculated from the sensor’s raw observation readings.

Some derivative metrics have required or optional parameters that can be used to fine-tune how these metrics are calculated. These parameters will be included in the params property within each metric object retrieved via the getRawMetrics or getMetrics API requests.

Each calculation parameter will be an object with the following properties:

Property Example Description
id ZERO_DATE The unique id of the metric parameter.
mandatory false Whether this metric parameter is required for the metric to be calculated correctly.

Retrieving Data

The following example illustrates retrieving data for a specific sensor:

var sensorId = "/fn/6C4F6D/node/dgsimlogger/032432/device/tilt4/sensor",
    metricId = "dx",
    unitId = "m";

// Create a local data object
var series = {
    id: sensorId,
    metric: metricId,
    data: {}
};

// Create a set of parameters for data retrieval
var retrievalParams = {
    "sensor": sensorId,
    "metric": metricId,
    "unit": unitId,
    "params": {
        "ZERO_DATE": {
            "value": "2017-03-01T22:00:00.000Z"
        }
    },
    "startDate": "2017-01-01T23:17:10.305Z",
    "endDate": "2017-01-20T23:17:10.305Z"
};

// Retrieve data from the server
sendRequest("getData", retrievalParams, function(result) {

    if ("requestStatus" in result && result.requestStatus === "finished") {

        // Data retrieval finished

    } else if ("data" in result) {

        // Save each data point locally
        for (var date in result.data) {
            if (result.data.hasOwnProperty(date)) {
                series.data[date] = result.data[date];
            }
        }

    } else if ("code" in result) {
        console.error("Error retrieving data with params", params, ":", result);
    }
});
nextId = nextId + 1
metricId = "dx"
unitId = "m"
sensorId = "/fn/6C4F6D/node/dgsimlogger/032432/device/tilt4/sensor"

# Create a local data object
series = {
    "id": sensorId,
    "metric": metricId,
    "data": {}
}

# Create a set of parameters for data retrieval
retrievalParams = {
    "sensor": sensorId,
    "metric": metricId,
    "unit": unitId,
    "params": {
        "ZERO_DATE": {
            "value": "2017-03-01T22:00:00.000Z"
        }
    },
    "startDate": "2017-01-01T23:17:10.305Z",
    "endDate": "2017-01-20T23:17:10.305Z"
};

# Retrieve data from server
s.send(json.dumps({
    "jsonrpc": "2.0",
    "method": "getData",
    "id": nextId,
    "params": retrievalParams
}))

# Handle incoming observations asynchronously
while True:

    # Decode response
    response = json.loads(decodeOneJsonFrame(s))

    # Store the incoming observations
    if "data" in response["result"]:
        for date in response["result"]["data"]:
            series["data"][date] = response["result"]["data"][date];

    # Close the socket when all observations are received
    if "requestStatus" in response["result"] and response["result"]["requestStatus"] == "finished":
        s.close()
        break

To retrieve observation data, you must always specify a sensor id, a metric id and a unit id. In addition, you can optionally include the following parameters with your API request:

Parameter Example Description
startDate 2017-01-01T23:17:10.305Z The start date for the data query, in ISO date format. If omitted, the data returned will start with the first observation.
endDate 2017-01-20T23:17:10.305Z The end date for the data query, in ISO date format. If omitted, the data returned will end with the most recent observation.
params {dt: {value: 604800}} Any mandatory or optional metric parameters, as described above. Metric parameter id’s should be used as keys of this object. Each metric parameter must have a value property and an optional unit property.
limit {function: "last", params: {size: 1}}
{function: "first", params: {size: 1}}
A configuration object that limits the number of observations returned. We currently only support retrieving a single first or last value.

Once you compile a list of data query parameters, you can retrieve the actual observations or statistical information about them using the following API requests:

Request Params Description
getData described above Retrieves a series of observations for a specific sensor that match the data query parameters.
getObservations described above Retrieves a series of observations for a specific sensor that match the data query parameters in the sensor’s raw metrics & units. Not recommended in most cases.
getObservationsSummary described above Retrieves statistical information about a series of observations. Currently returns the first and last observations that match the data query parameters.

See Below for our end to end example

Let’s assume we have acquired the sensorId of /fn/xxxxx/node/dynamic/xxxxx/device/rugged_troll_insitu_sde/sensor from the Sensor configuration page. If we want to obtain the full data model of our sensor we can execute the following getSensors request

from websocket import create_connection

ws = create_connection("wss://iiot.bentley.com:4201")
json = r'{"jsonrpc":"2.0","method":"authenticate","id":"0","params":{"code":"<REPLACE_WITH_YOUR_API_CODE_OR_JWT_TOKEN>"}}'
print(json)
ws.send(json)
result = ws.recv()
print("authenticate response: " + result)

json = '''{
   "jsonrpc":"2.0",
   "method":"getSensors",
   "id":1,
   "params":{
      "sensors": ["/fn/xxxxx/node/dynamic/xxxxx/device/rugged_troll_insitu_sde/sensor"],
      "projectIds":[
         "60F8B30928A37A575ECEE061"
      ]
   }
}'''
print(json)
ws.send(json)
result = ws.recv()
print("response: " + result)

NOTE: Because sensor: /fn/xxxxx/node/dynamic/xxxxx/device/rugged_troll_insitu_sde/sensor is in our default Project the projectIds param was not required.

Our request will result in a response that looks something like the response below. Make note of the PROJECT_ID field it will be needed later

{
   "jsonrpc":"2.0",
   "result":[
     {
       "type": "DYNAMIC",
       "device": "/fn/xxxxx/node/dynamic/xxxxx/device",
       "id": "/fn/xxxxx/node/dynamic/xxxxx/device/rugged_troll_insitu_sde/sensor",
       "props": {
         "MODEL": "RUGGED_TROLL_200",
         "NAME": "My Cool Rugged troll",
         "NODE_ID": "/fn/xxxxx/node",
         "PROJECT_ID": "60F8B30928A37A575ECEE061"
       }
     }
   ],
   "id":1
}

Next we can run the following code to find or metric/units of interest.

from websocket import create_connection

ws = create_connection("wss://iiot.bentley.com:4201")
json = r'{"jsonrpc":"2.0","method":"authenticate","id":"0","params":{"code":"<REPLACE_WITH_YOUR_API_CODE_OR_JWT_TOKEN>"}}'
print(json)
ws.send(json)
result = ws.recv()
print("authenticate response: " + result)

json = '''{
   "jsonrpc":"2.0",
   "method":"getMetrics",
   "id":1,
   "params":{
      "sensors": ["/fn/xxxxx/node/dynamic/xxxxx/device/rugged_troll_insitu_sde/sensor"]
   }
}'''
print(json)
ws.send(json)
result = ws.recv()
print("response: " + result)

Which will result in a response that looks something like

{
   "jsonrpc":"2.0",
   "result":{
      "/fn/xxxxx/node/dynamic/xxxxx/device/rugged_troll_insitu_sde/sensor":[
         {
            "id":"dT",
            "name":"delta temperature",
            "displayName":"∆ Temperature",
            "defaultUnit":"C",
            "notation":"∆T",
            "hidden":false,
            "params":[
               {
                  "id":"dt",
                  "mandatory":false
               },
               {
                  "id":"START_DATE",
                  "mandatory":false
               },
               {
                  "id":"ZERO_DATE",
                  "mandatory":false
               }
            ]
         },
         {
            "id":"T",
            "name":"temperature",
            "displayName":"Temperature",
            "defaultUnit":"C",
            "notation":"T",
            "hidden":false,
            "params":[

            ]
         },
         {
            "id":"P_raw",
            "name":"raw pressure",
            "displayName":"Raw Pressure",
            "defaultUnit":"Pa",
            "notation":"P_raw",
            "hidden":false,
            "params":[

            ]
         }
      ]
   },
   "id":1
}

Let’s assume we are interested in raw pressure which has a metricId of P_raw and default unit of Pa (if a different unit is desired, you can run the getUnits API request to view a list of the available units for your desired metric). We now have our metric, unit, and PROJECT_ID (captured from the getSensors response above) we are now ready to make our getData call.

import json
from datetime import datetime
from websocket import create_connection

now = datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z'
print('Using timestamp: ' + now)

ws = create_connection("wss://iiot.bentley.com:4201")
jsonRequest = r'{"jsonrpc":"2.0","method":"authenticate","id":"0","params":{"code":"<REPLACE_WITH_YOUR_API_CODE_OR_JWT_TOKEN>"}}'
print(jsonRequest)
ws.send(jsonRequest)
result = ws.recv()
print("authenticate response: " + result)

jsonRequest = '''{
   "jsonrpc":"2.0",
   "method":"getData",
   "id":1,
   "params":{
      "sensor":"/fn/xxxxx/node/dynamic/xxxxx/device/rugged_troll_insitu_sde/sensor",
      "metric":"P_raw",
      "unit":"Pa",
      "params":{

      },
      "startDate":"2021-06-24T10:56:11.832Z",
      "endDate":"2021-07-22T00:38:05.477Z",
      "projectIds":[
         "60F8B30928A37A575ECEE061"
      ]
   }
}'''
print(jsonRequest)
ws.send(jsonRequest)

start = datetime.utcnow()

while True:
    result = json.loads(ws.recv())
    print(result)

    if "requestStatus" in result["result"] and result["result"]["requestStatus"] == "finished":
        ws.close()
        break

done = datetime.utcnow()

delta = done - start
print delta.total_seconds() * 1000

Subscribing to New Data

The following example illustrates subscribing to a sensor’s new observations:

var sensorId = "/fn/6C4F6D/node/dgsimlogger/032432/device/tilt4/sensor",
    metricId = "dx",
    unitId = "m";

// Create a local data object
var series = {
    id: sensorId,
    metric: metricId,
    data: {}
};

// Create a set of parameters for a data subscription
var subscriptionParams = {
    "sensors": [sensorId],
    "metric": metricId,
    "unit": unitId,
    "params": {
        "ZERO_DATE": {
            "value": "2017-03-01T22:00:00.000Z"
        }
    }
};

// Subscribe to new sensor observations
var subscriptionId = sendRequest("subscribeData", subscriptionParams, function(result) {

    if ("data" in result) {

        // Save each data point locally
        for (var date in result.data) {
            if (result.data.hasOwnProperty(date)) {
                series.data[date] = result.data[date];
            }
        }
    }
});
nextId = nextId + 1
metricId = "dx"
unitId = "m"
sensorId = "/fn/6C4F6D/node/dgsimlogger/032432/device/tilt4/sensor"

# Create a local data object
series = {
    "id": sensorId,
    "metric": metricId,
    "data": {}
}

# Create a set of parameters for data subscription
subscriptionParams = {
    "sensors": [sensorId],
    "metric": metricId,
    "unit": unitId,
    "params": {
        "ZERO_DATE": {
            "value": "2017-03-01T22:00:00.000Z"
        }
    }
};

# Subscribe to new sensor observations
s.send(json.dumps({
    "jsonrpc": "2.0",
    "method": "subscribeData",
    "id": nextId,
    "params": subscriptionParams
}))

# Handle incoming updates asynchronously
while True:

    # Decode response
    response = json.loads(decodeOneJsonFrame(s))

    # Store the incoming observations
    if "data" in response["result"]:
        for date in response["result"]["data"]:
            series["data"][date] = response["result"]["data"][date];

In addition to retrieving observation data, you can subscribe to a sensor’s data stream to receive all newly recorded observations.

The data subscription API request can be performed with most of the same parameters as the observation retrieval request, so any newly recorded observations will be passed to you in the metrics & units specified (instead of the raw metrics & units they are recorded in).

You can subscribe and unsubscribe from a sensor’s observation stream using the following API requests:

Request Params Description
subscribeData “sensors” [array]
“metric” [string]
“unit” [string]
“params” [object]
Establishes a subscription to any new sensor observations. You will receive a new response message when a new observation is recorded in the metrics & units specified by the API request. Returned subscriptionId can be used to later unsubscribe from updates.
unsubscribeData “id” [integer] Unsubscribe from new observation updates using the subscriptionId returned by the subscription API request.

Uploading Data

The following example illustrates how to upload new observations for a single sensor:


var uploadDataParams = {
    "data": [
         {
            "sensorId": "/trimble4d/nodeId/node/deviceId/device/sensorId/sensor",
            "timestamp": "2018-06-04T19:35:35.400Z",
            "values": {
               "e": 100.0,
               "e_adjusted": 101.0,
               "n": 100.0,
               "n_adjusted": 101.0,
               "h": 100.0,
               "h_adjusted": 101.0,
               "ha": 45.0,
               "za": 60.0,
               "sd": 100.0,
               "sd_corrected": 101.0,
               "T": 23.0,
               "P": 101325
            }
         }
      ]
};

sendRequest("uploadData", uploadDataParams, function(result) {
    console.log("uploadData result: ", result);
});
nextId = nextId + 1

s.send(json.dumps({
    "jsonrpc": "2.0",
    "method": "uploadData",
    "id": nextId,
    "params": {
      "data": [
         {
            "sensorId": "/trimble4d/nodeId/node/deviceId/device/sensorId/sensor",
            "timestamp": "2018-06-04T19:35:35.400Z",
            "values": {
               "e": 100.0,
               "e_adjusted": 101.0,
               "n": 100.0,
               "n_adjusted": 101.0,
               "h": 100.0,
               "h_adjusted": 101.0,
               "ha": 45.0,
               "za": 60.0,
               "sd": 100.0,
               "sd_corrected": 101.0,
               "T": 23.0,
               "P": 101325
            }
         }
      ]
   }
}))

response = json.loads(decodeOneJsonFrame(s))
print("\r\n" + response["result"])

Example response object:

{
   "jsonrpc": "2.0",
   "result": {
      "successfullyProcessedObs": 1
   },
   "id": 4
}

In addition to retrieving observation data, you can upload new observation data for a particular sensor given it’s unique sensorId.

In order to upload new observation data for a sensor in the iTwin IoT platform, that sensor must already exist in the system. See the createSensor API call described above if you need to create a new sensor first.

The values specified in the data object should have a key-value pair for each raw metric defined for this type of sensor. You can obtain the raw metric keys for a given sensor using the getRawMetrics request described above.

The response has a key successfullyProcessedObs that reflects the actual number of observations that were inserted. There is an example of this response object on the right.

Request Params Description
uploadData “data” [array]
This is an array of objects that represent a sensorId, timestamp, and the associated metrics and their values. See example object on the right.

data Object Description (all fields are mandatory):

Parameter Example Description
sensorId /trimble4d/nodeId/node/deviceId/device/sensorId/sensor The sensorId that represents the sensor you are uploading observation data for. This sensor must already exist in the system.
timestamp 2018-06-04T23:17:10.305Z The timestamp for this observation, in ISO date format.
values {"e": 100.0, "n": 200.0, "h": 300.0, ...} The key is the metric name, and the value is the value for that metric for this observation.

Utilities

Retrieve help information for all available API requests:

sendRequest("help", {}, function(result) {
    console.log("Available API requests:", result);
});
nextId = nextId + 1
s.send(json.dumps({
    "jsonrpc": "2.0",
    "method": "help",
    "id": nextId
}))
response = json.loads(decodeOneJsonFrame(s))
print("\r\n" + response["result"])

Keep a connection alive with periodic time requests:

setInterval(function() {

    // Check if a connection host has been set
    if (host !== undefined) {

        // Check if a connection is already open
        if (socket === undefined) {

            // If no connection, establish a new one

        } else {

            // Make sure we are no actively authenticating
            if (!authenticating) {

                // Refresh the connection by sending a "time" request
                sendRequest("time", {}, function() {
                    connected = true;
                });
            }
        }

    } else {

        // Set connection flags
        connected = authenticating = false;
    }
}, 5000);
nextId = nextId + 1
s.send(json.dumps({
    "jsonrpc": "2.0",
    "method":  "time",
    "id": nextId
}))
response = json.loads(decodeOneJsonFrame(s))
print("\r\nThe server time is " + parser.parse(response["result"]["time"]).strftime('%c'))

There are three utility API requests available for your convenience:

Request Parameters Description
authenticate “code” [string] Authenticates a socket connection using your unique API key or JWT Token. This must be the first API request after opening a new connection.
help None Retrieves information on all available API requests and their parameters.
time None Retrieves the current time from the server.

iTwin IoT Platform REST API

The iTwin IoT platform REST API is a standard REST API available to all clients. The full API suite is available here.

Getting Started

To begin using the REST API you will first need to obtain your iTwin IoT ApiKey or JWT Token (See the Authentication section above for details). The ApiKey/Token is required on every request, and must be supplied to the Authorization header.

See the cURL tab for a sample request to get all sensors on the specified Project.

For ApiKey:

curl --location --request GET 'https://iiot.bentley.com/api/sensors/all?pageSize=100&projectIds=60F8B30928A37A575ECEE061' \
--header 'Authorization: ApiKey <apikey>' \
--header 'Content-Type: application/json'

For JWT Token (token emitted for brevity):

curl --location --request GET 'https://iiot.bentley.com/api/sensors/all?pageSize=100&projectIds=026a6db1-68c6-4416-9bd0-1f83e191ec31' \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json'

iTwin IoT Edge REST API

The iTwin IoT Edge REST API is a standard REST API available to client applications running on a iTwin IoT Device. All REST API requests and responses use the Content-Type “application/json”.

GET Serial Number

get Serial Number

curl http://localhost/api/serialNumber

Get the serial number of this iTwin IoT THREAD. This is otherwise known as the THREAD Connect Code. It is a 6-character hex string.

GET Time

get Time

curl http://localhost/api/time
#include <cpprest/http_client.h>
#include <cpprest/filestream.h>

pplx::task<void> getSampleCall(const char *fileName, const char *urlPath, const char *queryParams);

using namespace utility;                    // Common utilities like string conversions
using namespace web;                        // Common features like URIs.
using namespace web::http;                  // Common HTTP functionality
using namespace web::http::client;          // HTTP client features
using namespace concurrency::streams;       // Asynchronous streams

int main(int argc, char *argv[]) {

    pplx::task<void> timeTask = getSampleCall("time", "/time", "");
    // Wait for all the outstanding I/O to complete and handle any exceptions
        try {
            timeTask.wait();
        }
        catch (const std::exception &e) {
            printf("Error exception:%s\n", e.what());
        }
        return 0;
}

pplx::task<void> getSampleCall(const char *fileName, const char *urlPath, const char *queryParams) {
    auto fileStream = std::make_shared<ostream>();
    //auto fileStream = std::__1::make_shared<ostream>();

    // Open stream to output file.
    pplx::task<void> requestTask = fstream::open_ostream(U(fileName)).then([=](ostream outFile) {
                *fileStream = outFile;

                // Create http_client to send the request.
                http_client client(U("http://localhost/api/"));

                // Build request URI and start the request.
                uri_builder builder(U(urlPath));
                builder.append(U(queryParams));

                const string_t &fragment = builder.to_string();
                return client.request(methods::GET, fragment);
            })
                    // Handle response headers arriving.
            .then([=](http_response response) {
                printf("Received response status code:%u from:%s\n", response.status_code(), U(urlPath));

                // Write response body into the file.
                return response.body().read_to_end(fileStream->streambuf());
            })

                    // Close the file stream.
            .then([=](size_t) {
                return fileStream->close();
            });
    return requestTask;
}

Get the current time on the Device

POST Time

Example Body

{
  "time" : "2019-11-19T15:41:30.000Z"
}

post time

curl -X POST http://localhost/api/time -d '{"time":"2019-11-19T15:41:30.000Z"}'
#include <cpprest/http_client.h>
#include <cpprest/filestream.h>

pplx::task<void> getSampleCall(const char *fileName, const char *urlPath, const char *queryParams);

using namespace utility;                    // Common utilities like string conversions
using namespace web;                        // Common features like URIs.
using namespace web::http;                  // Common HTTP functionality
using namespace web::http::client;          // HTTP client features
using namespace concurrency::streams;       // Asynchronous streams

int main(int argc, char *argv[]) {

    pplx::task<void> timeTask = getSampleCall("time", "/time", "");
    // Wait for all the outstanding I/O to complete and handle any exceptions
        try {
            timeTask.wait();
        }
        catch (const std::exception &e) {
            printf("Error exception:%s\n", e.what());
        }
        return 0;
}

pplx::task<void> getSampleCall(const char *fileName, const char *urlPath, const char *queryParams) {
    auto fileStream = std::make_shared<ostream>();
    //auto fileStream = std::__1::make_shared<ostream>();

    // Open stream to output file.
    pplx::task<void> requestTask = fstream::open_ostream(U(fileName)).then([=](ostream outFile) {
                *fileStream = outFile;

                // Create http_client to send the request.
                http_client client(U("http://localhost/api/"));

                // Build request URI and start the request.
                uri_builder builder(U(urlPath));
                builder.append(U(queryParams));

                const string_t &fragment = builder.to_string();
                return client.request(methods::GET, fragment);
            })
                    // Handle response headers arriving.
            .then([=](http_response response) {
                printf("Received response status code:%u from:%s\n", response.status_code(), U(urlPath));

                // Write response body into the file.
                return response.body().read_to_end(fileStream->streambuf());
            })

                    // Close the file stream.
            .then([=](size_t) {
                return fileStream->close();
            });
    return requestTask;
}

Set the Device’s time to the provided date time.

Model

Key Type Description
time ISO 8601 Date Time Date Time to set on Device

GET Time Accurate

Check if the clock synchronization process between the on-board RTC and OS clock has finished

curl http://localhost/api/timeAccurate

Check if the clock synchronization process between the on-board RTC and OS clock has finished. Returns ‘true’ if it is complete, 'false’ otherwise.

GET Input Voltage

get input voltage

curl http://localhost/api/chargeInputVoltage
#include <cpprest/http_client.h>
#include <cpprest/filestream.h>

pplx::task<void> getSampleCall(const char *fileName, const char *urlPath, const char *queryParams);

using namespace utility;                    // Common utilities like string conversions
using namespace web;                        // Common features like URIs.
using namespace web::http;                  // Common HTTP functionality
using namespace web::http::client;          // HTTP client features
using namespace concurrency::streams;       // Asynchronous streams

int main(int argc, char *argv[]) {

    pplx::task<void> chargeInputVoltageTask = getSampleCall("chargeInputVoltage", "/chargeInputVoltage", "");
    // Wait for all the outstanding I/O to complete and handle any exceptions
        try {
            chargeInputVoltageTask.wait();
        }
        catch (const std::exception &e) {
            printf("Error exception:%s\n", e.what());
        }
        return 0;
}

pplx::task<void> getSampleCall(const char *fileName, const char *urlPath, const char *queryParams) {
    auto fileStream = std::make_shared<ostream>();
    //auto fileStream = std::__1::make_shared<ostream>();

    // Open stream to output file.
    pplx::task<void> requestTask = fstream::open_ostream(U(fileName)).then([=](ostream outFile) {
                *fileStream = outFile;

                // Create http_client to send the request.
                http_client client(U("http://localhost/api/"));

                // Build request URI and start the request.
                uri_builder builder(U(urlPath));
                builder.append(U(queryParams));

                const string_t &fragment = builder.to_string();
                return client.request(methods::GET, fragment);
            })
                    // Handle response headers arriving.
            .then([=](http_response response) {
                printf("Received response status code:%u from:%s\n", response.status_code(), U(urlPath));

                // Write response body into the file.
                return response.body().read_to_end(fileStream->streambuf());
            })

                    // Close the file stream.
            .then([=](size_t) {
                return fileStream->close();
            });
    return requestTask;
}

Response

{
  "value":14.5288
}

Get the Device’s input voltage in volts

GET Input Current

get input current

curl http://localhost/api/chargeInputCurrent
#include <cpprest/http_client.h>
#include <cpprest/filestream.h>

pplx::task<void> getSampleCall(const char *fileName, const char *urlPath, const char *queryParams);

using namespace utility;                    // Common utilities like string conversions
using namespace web;                        // Common features like URIs.
using namespace web::http;                  // Common HTTP functionality
using namespace web::http::client;          // HTTP client features
using namespace concurrency::streams;       // Asynchronous streams

int main(int argc, char *argv[]) {

    pplx::task<void> chargeInputCurrentTask = getSampleCall("chargeInputCurrent", "/chargeInputCurrent", "");
    // Wait for all the outstanding I/O to complete and handle any exceptions
        try {
            chargeInputCurrentTask.wait();
        }
        catch (const std::exception &e) {
            printf("Error exception:%s\n", e.what());
        }
        return 0;
}

pplx::task<void> getSampleCall(const char *fileName, const char *urlPath, const char *queryParams) {
    auto fileStream = std::make_shared<ostream>();
    //auto fileStream = std::__1::make_shared<ostream>();

    // Open stream to output file.
    pplx::task<void> requestTask = fstream::open_ostream(U(fileName)).then([=](ostream outFile) {
                *fileStream = outFile;

                // Create http_client to send the request.
                http_client client(U("http://localhost/api/"));

                // Build request URI and start the request.
                uri_builder builder(U(urlPath));
                builder.append(U(queryParams));

                const string_t &fragment = builder.to_string();
                return client.request(methods::GET, fragment);
            })
                    // Handle response headers arriving.
            .then([=](http_response response) {
                printf("Received response status code:%u from:%s\n", response.status_code(), U(urlPath));

                // Write response body into the file.
                return response.body().read_to_end(fileStream->streambuf());
            })

                    // Close the file stream.
            .then([=](size_t) {
                return fileStream->close();
            });
    return requestTask;
}

Response

{
  "value":0.0225
}

Get the Device’s input current in amps

GET Battery Voltage

get battery voltage

curl http://localhost/api/batteryVoltage
#include <cpprest/http_client.h>
#include <cpprest/filestream.h>

pplx::task<void> getSampleCall(const char *fileName, const char *urlPath, const char *queryParams);

using namespace utility;                    // Common utilities like string conversions
using namespace web;                        // Common features like URIs.
using namespace web::http;                  // Common HTTP functionality
using namespace web::http::client;          // HTTP client features
using namespace concurrency::streams;       // Asynchronous streams

int main(int argc, char *argv[]) {

    pplx::task<void> batteryVoltageTask = getSampleCall("batteryVoltage", "/batteryVoltage", "");
    // Wait for all the outstanding I/O to complete and handle any exceptions
        try {
            batteryVoltageTask.wait();
        }
        catch (const std::exception &e) {
            printf("Error exception:%s\n", e.what());
        }
        return 0;
}

pplx::task<void> getSampleCall(const char *fileName, const char *urlPath, const char *queryParams) {
    auto fileStream = std::make_shared<ostream>();
    //auto fileStream = std::__1::make_shared<ostream>();

    // Open stream to output file.
    pplx::task<void> requestTask = fstream::open_ostream(U(fileName)).then([=](ostream outFile) {
                *fileStream = outFile;

                // Create http_client to send the request.
                http_client client(U("http://localhost/api/"));

                // Build request URI and start the request.
                uri_builder builder(U(urlPath));
                builder.append(U(queryParams));

                const string_t &fragment = builder.to_string();
                return client.request(methods::GET, fragment);
            })
                    // Handle response headers arriving.
            .then([=](http_response response) {
                printf("Received response status code:%u from:%s\n", response.status_code(), U(urlPath));

                // Write response body into the file.
                return response.body().read_to_end(fileStream->streambuf());
            })

                    // Close the file stream.
            .then([=](size_t) {
                return fileStream->close();
            });
    return requestTask;
}

Response

{
  "value":13.7456
}

Get the Device’s battery voltage in volts

GET Battery State of Charge

Reports the battery percentage 0-100%

curl http://localhost/api/stateOfCharge

Reports the battery percentage 0-100%

GET Battery Saver

Get the currently configured battery saver percentages

curl -X GET http://localhost/api/batterySaver

Response

{
  "value": false,
  "low": 0,
  "high": 100
}

Get the currently configured battery saver percentages

PUT Battery Saver

Example Body

{
  "value" : true,
  "low": 10,
  "high": 100
}

Set the charge controller to stop charging at 80% charge and shut off at 20%

curl -X PUT http://localhost/api/batterySaver -d '{ "value": true, "low": 20, "high": 80}'

Sets the charge controller to stop charging at a desired high %charge and shut off at a desired low %charge. Low must be between 0-40 inclusive. High must be between 60-100 inclusive.

If 'value’ is set to true and no low/high value is given, it will set it to low 10 high 100.

Both low/high must be in the request payload or both must be absent from the request payload. You cannot set only 1 of the 2 low/high values.

Model

Key Type Description
value boolean true to enable battery saver, false to disable battery saver
low int %charge to shutoff. Must be between 0-40 inclusive.
high int %charge to stop charging. Must be between 60-100 inclusive.

GET Device Port Voltage and Current

Get the voltage and current for a given device port. Valid ports are 1 & 2 & 3 valid options are 0 (voltage) & 1 (current)

curl 'http://localhost/api/deviceAdcRead?port=1&option=0'

Get the voltage and current for a given device port. Valid ports are 1 & 2 & 3 valid options are 0 (voltage) & 1 (current)

POST Device Voltage Output

Example Body

{
  "portNumber" : 1,
  "value" : 1
}

set device voltage on the specified device port

curl -X POST http://localhost/api/deviceVoltage -d '{ "portNumber": 1, "value": 1}'
#include <cpprest/http_client.h>
#include <cpprest/filestream.h>

pplx::task<void> postDeviceVoltage(const char *fileName, const char *urlPath);

using namespace utility;                    // Common utilities like string conversions
using namespace web;                        // Common features like URIs.
using namespace web::http;                  // Common HTTP functionality
using namespace web::http::client;          // HTTP client features
using namespace concurrency::streams;       // Asynchronous streams

int main(int argc, char *argv[]) {

    pplx::task<void> deviceVoltageTask = postDeviceVoltage("deviceVoltage", "/deviceVoltage");
    // Wait for all the outstanding I/O to complete and handle any exceptions
        try {
            deviceVoltageTask.wait();
        }
        catch (const std::exception &e) {
            printf("Error exception:%s\n", e.what());
        }
        return 0;
}

pplx::task<void> postDeviceVoltage(const char *fileName, const char *urlPath) {
    auto fileStream = std::make_shared<ostream>();

    // Open stream to output file.
    pplx::task<void> requestTask = fstream::open_ostream(U(fileName)).then([=](ostream outFile) {
                *fileStream = outFile;

                // Create http_client to send the request.
                http_client client(U("http://localhost/api/"));

                // Build request URI and start the request.
                uri_builder builder(U(urlPath));
                const string_t &fragment = builder.to_string();

                web::json::value json_v ;
                json_v["portNumber"] = web::json::value::number(1);
                json_v["value"] = web::json::value::number(1);

                return client.request(methods::POST, U(urlPath), json_v);
            })
                    // Handle response headers arriving.
            .then([=](http_response response) {
                printf("Received response status code:%u from:%s\n", response.status_code(), U(urlPath));

                // Write response body into the file.
                return response.body().read_to_end(fileStream->streambuf());
            })

                    // Close the file stream.
            .then([=](size_t) {
                return fileStream->close();
            });
    return requestTask;
}

Set the voltage (0 = off, 1= 12v, 2 = 15v) on the specified device port (1, 2, 3)

Model

Key Type Description
portNumber integer The port number which the power will be set on. valid values are 1 and 2 and 3
value integer The voltage to set on the specified port; valid values are 0 (off), 1 (12v), and 2 (15v)

POST Device Sleep

Example Body

{
  "duration" : 10000
}

set sleep duration

curl -X POST http://localhost/api/sleep -d '{"duration":10000}'
#include <cpprest/http_client.h>
#include <cpprest/filestream.h>

pplx::task<void> postSleep(const char *fileName, const char *urlPath);

using namespace utility;                    // Common utilities like string conversions
using namespace web;                        // Common features like URIs.
using namespace web::http;                  // Common HTTP functionality
using namespace web::http::client;          // HTTP client features
using namespace concurrency::streams;       // Asynchronous streams

int main(int argc, char *argv[]) {

    pplx::task<void> sleepTask = postSleep("sleep", "/sleep");
    // Wait for all the outstanding I/O to complete and handle any exceptions
        try {
            sleep.wait();
        }
        catch (const std::exception &e) {
            printf("Error exception:%s\n", e.what());
        }
        return 0;
}

pplx::task<void> postSleep(const char *fileName, const char *urlPath) {
    auto fileStream = std::make_shared<ostream>();

    // Open stream to output file.
    pplx::task<void> requestTask = fstream::open_ostream(U(fileName)).then([=](ostream outFile) {
                *fileStream = outFile;

                // Create http_client to send the request.
                http_client client(U("http://localhost/api/"));

                // Build request URI and start the request.
                uri_builder builder(U(urlPath));
                const string_t &fragment = builder.to_string();

                web::json::value json_v ;
                json_v["duration"] = web::json::value::number(10000);

                return client.request(methods::POST, U(urlPath), json_v);
            })
                    // Handle response headers arriving.
            .then([=](http_response response) {
                printf("Received response status code:%u from:%s\n", response.status_code(), U(urlPath));

                // Write response body into the file.
                return response.body().read_to_end(fileStream->streambuf());
            })

                    // Close the file stream.
            .then([=](size_t) {
                return fileStream->close();
            });
    return requestTask;
}

Put the Device in low power mode for the specified duration of time in milliseconds

Model

Key Type Description
duration integer The amount of time in milliseconds to put the Device in low power mode.

GET Device Temperature

get Device temperature celsius

curl http://localhost/api/tempRead
#include <cpprest/http_client.h>
#include <cpprest/filestream.h>

pplx::task<void> getSampleCall(const char *fileName, const char *urlPath, const char *queryParams);

using namespace utility;                    // Common utilities like string conversions
using namespace web;                        // Common features like URIs.
using namespace web::http;                  // Common HTTP functionality
using namespace web::http::client;          // HTTP client features
using namespace concurrency::streams;       // Asynchronous streams

int main(int argc, char *argv[]) {

    pplx::task<void> tempReadTask = getSampleCall("tempRead", "/tempRead", "");
    // Wait for all the outstanding I/O to complete and handle any exceptions
        try {
            tempReadTask.wait();
        }
        catch (const std::exception &e) {
            printf("Error exception:%s\n", e.what());
        }
        return 0;
}

pplx::task<void> getSampleCall(const char *fileName, const char *urlPath, const char *queryParams) {
    auto fileStream = std::make_shared<ostream>();
    //auto fileStream = std::__1::make_shared<ostream>();

    // Open stream to output file.
    pplx::task<void> requestTask = fstream::open_ostream(U(fileName)).then([=](ostream outFile) {
                *fileStream = outFile;

                // Create http_client to send the request.
                http_client client(U("http://localhost/api/"));

                // Build request URI and start the request.
                uri_builder builder(U(urlPath));
                builder.append(U(queryParams));

                const string_t &fragment = builder.to_string();
                return client.request(methods::GET, fragment);
            })
                    // Handle response headers arriving.
            .then([=](http_response response) {
                printf("Received response status code:%u from:%s\n", response.status_code(), U(urlPath));

                // Write response body into the file.
                return response.body().read_to_end(fileStream->streambuf());
            })

                    // Close the file stream.
            .then([=](size_t) {
                return fileStream->close();
            });
    return requestTask;
}

Response

{
  "value":36.75
}

Get the Device’s temperature in celsius

GET Atmospheric Pressure

get atmospheric pressure

curl http://localhost/api/pressRead
#include <cpprest/http_client.h>
#include <cpprest/filestream.h>

pplx::task<void> getSampleCall(const char *fileName, const char *urlPath, const char *queryParams);

using namespace utility;                    // Common utilities like string conversions
using namespace web;                        // Common features like URIs.
using namespace web::http;                  // Common HTTP functionality
using namespace web::http::client;          // HTTP client features
using namespace concurrency::streams;       // Asynchronous streams

int main(int argc, char *argv[]) {

    pplx::task<void> pressReadTask = getSampleCall("pressRead", "/pressRead", "");
    // Wait for all the outstanding I/O to complete and handle any exceptions
        try {
            pressReadTask.wait();
        }
        catch (const std::exception &e) {
            printf("Error exception:%s\n", e.what());
        }
        return 0;
}

pplx::task<void> getSampleCall(const char *fileName, const char *urlPath, const char *queryParams) {
    auto fileStream = std::make_shared<ostream>();
    //auto fileStream = std::__1::make_shared<ostream>();

    // Open stream to output file.
    pplx::task<void> requestTask = fstream::open_ostream(U(fileName)).then([=](ostream outFile) {
                *fileStream = outFile;

                // Create http_client to send the request.
                http_client client(U("http://localhost/api/"));

                // Build request URI and start the request.
                uri_builder builder(U(urlPath));
                builder.append(U(queryParams));

                const string_t &fragment = builder.to_string();
                return client.request(methods::GET, fragment);
            })
                    // Handle response headers arriving.
            .then([=](http_response response) {
                printf("Received response status code:%u from:%s\n", response.status_code(), U(urlPath));

                // Write response body into the file.
                return response.body().read_to_end(fileStream->streambuf());
            })

                    // Close the file stream.
            .then([=](size_t) {
                return fileStream->close();
            });
    return requestTask;
}

Response

{
  "value":101788.50996
}

Get atmospheric pressure surrounding the Device in daPa (decapascal). Multiply by 10 to get pascals.

For V11 and older THREADs the unit returned is hPa (hectopascal).

GET Sensor current

get sensor current

curl -X GET 'http://localhost/api/deviceAdcRead420?port=1'
#include <cpprest/http_client.h>
#include <cpprest/filestream.h>

pplx::task<void> getSampleCall(const char *fileName, const char *urlPath, const char *queryParams);

using namespace utility;                    // Common utilities like string conversions
using namespace web;                        // Common features like URIs.
using namespace web::http;                  // Common HTTP functionality
using namespace web::http::client;          // HTTP client features
using namespace concurrency::streams;       // Asynchronous streams

int main(int argc, char *argv[]) {

    pplx::task<void> deviceAdcRead420Task = getSampleCall("deviceAdcRead420", "/deviceAdcRead420", "?port=1");
    // Wait for all the outstanding I/O to complete and handle any exceptions
        try {
            deviceAdcRead420Task.wait();
        }
        catch (const std::exception &e) {
            printf("Error exception:%s\n", e.what());
        }
        return 0;
}

pplx::task<void> getSampleCall(const char *fileName, const char *urlPath, const char *queryParams) {
    auto fileStream = std::make_shared<ostream>();
    //auto fileStream = std::__1::make_shared<ostream>();

    // Open stream to output file.
    pplx::task<void> requestTask = fstream::open_ostream(U(fileName)).then([=](ostream outFile) {
                *fileStream = outFile;

                // Create http_client to send the request.
                http_client client(U("http://localhost/api/"));

                // Build request URI and start the request.
                uri_builder builder(U(urlPath));
                builder.append(U(queryParams));

                const string_t &fragment = builder.to_string();
                return client.request(methods::GET, fragment);
            })
                    // Handle response headers arriving.
            .then([=](http_response response) {
                printf("Received response status code:%u from:%s\n", response.status_code(), U(urlPath));

                // Write response body into the file.
                return response.body().read_to_end(fileStream->streambuf());
            })

                    // Close the file stream.
            .then([=](size_t) {
                return fileStream->close();
            });
    return requestTask;
}

Response

{
  "value":-0.00429
}

Get the current from the specified port on the Device in amps. This will temporarily configure the port for 4-20mA support, take a reading, and then set the port back to default

Query params

Key Type Description
port integer The port number to read from. Valid values are 1 and 2 and 3

POST Device Port Mode

Example Body

{
  "portNumber" : 1,
  "content" : "RS232"
}

set device interface protocol on the specified device port

curl -X POST http://localhost/api/deviceRsSel -d '{ "portNumber": 1, "content": "RS232"}'
#include <cpprest/http_client.h>
#include <cpprest/filestream.h>

pplx::task<void> postDeviceRsSel(const char *fileName, const char *urlPath);

using namespace utility;                    // Common utilities like string conversions
using namespace web;                        // Common features like URIs.
using namespace web::http;                  // Common HTTP functionality
using namespace web::http::client;          // HTTP client features
using namespace concurrency::streams;       // Asynchronous streams

int main(int argc, char *argv[]) {

    pplx::task<void> deviceRsSelTask = postDeviceRsSel("deviceRsSel", "/deviceRsSel");
    // Wait for all the outstanding I/O to complete and handle any exceptions
        try {
            deviceRsSelTask.wait();
        }
        catch (const std::exception &e) {
            printf("Error exception:%s\n", e.what());
        }
        return 0;
}

pplx::task<void> postDeviceRsSel(const char *fileName, const char *urlPath) {
    auto fileStream = std::make_shared<ostream>();

    // Open stream to output file.
    pplx::task<void> requestTask = fstream::open_ostream(U(fileName)).then([=](ostream outFile) {
                *fileStream = outFile;

                // Create http_client to send the request.
                http_client client(U("http://localhost/api/"));

                // Build request URI and start the request.
                uri_builder builder(U(urlPath));
                const string_t &fragment = builder.to_string();

                web::json::value json_v ;
                json_v["portNumber"] = web::json::value::number(1);
                json_v["content"] = web::json::value::string("RS232");

                return client.request(methods::POST, U(urlPath), json_v);
            })
                    // Handle response headers arriving.
            .then([=](http_response response) {
                printf("Received response status code:%u from:%s\n", response.status_code(), U(urlPath));

                // Write response body into the file.
                return response.body().read_to_end(fileStream->streambuf());
            })

                    // Close the file stream.
            .then([=](size_t) {
                return fileStream->close();
            });
    return requestTask;
}

Set the interface mode (RS232, RS485, ANALOG, USB, OFF) on the specified device port (1, 2, 3). Note USB is only supported on port 1.

Model

Key Type Description
portNumber integer The port number which to set the specified interface type.
content string The interface protocol; valid values are RS232, RS485, ANALOG, USB, and OFF

POST LED Flash

Flash the LED

curl -X POST http://localhost/api/observation
#include <cpprest/http_client.h>
#include <cpprest/filestream.h>

pplx::task<void> postNoBodySampleCall(const char *fileName, const char *urlPath);

using namespace utility;                    // Common utilities like string conversions
using namespace web;                        // Common features like URIs.
using namespace web::http;                  // Common HTTP functionality
using namespace web::http::client;          // HTTP client features
using namespace concurrency::streams;       // Asynchronous streams

int main(int argc, char *argv[]) {

    pplx::task<void> observationTask = postNoBodySampleCall("observation", "/observation");
    // Wait for all the outstanding I/O to complete and handle any exceptions
        try {
            observationTask.wait();
        }
        catch (const std::exception &e) {
            printf("Error exception:%s\n", e.what());
        }
        return 0;
}

pplx::task<void> postNoBodySampleCall(const char *fileName, const char *urlPath) {
    //auto fileStream = std::__1::make_shared<ostream>();
    auto fileStream = std::make_shared<ostream>();

    // Open stream to output file.
    pplx::task<void> requestTask = fstream::open_ostream(U(fileName)).then([=](ostream outFile) {
                *fileStream = outFile;

                // Create http_client to send the request.
                http_client client(U("http://localhost/api/"));

                // Build request URI and start the request.
                uri_builder builder(U(urlPath));

                const string_t &fragment = builder.to_string();
                return client.request(methods::POST, fragment);
            })
                    // Handle response headers arriving.
            .then([=](http_response response) {
                printf("Received response status code:%u from:%s\n", response.status_code(), U(urlPath));

                // Write response body into the file.
                return response.body().read_to_end(fileStream->streambuf());
            })

                    // Close the file stream.
            .then([=](size_t) {
                return fileStream->close();
            });
    return requestTask;
}

Flash the Device LED.

GET Device Port Properties

Get Device configuration on the specified port (1, 2, 3)

curl -X GET 'http://localhost/api/configure?port=1'

Get Device configuration on the specified port (1, 2, 3)

Model

Key Type Description
port integer The port number which to get the Device Port properties.

PUT Device Port Properties

Example Body

{
  "COMM_TYPE" : "RS232",
  "BAUD_RATE" : 9600,
  "DELIMITERS_HEX" : ["0D0A"],
  "PORT" : 1234,
  "HOST" : "127.0.0.1"
}

Set configuration properties on the specified port (1, 2, 3)

curl -X PUT 'http://localhost/api/configure?port=1' -H 'Content-Type: application/json' -d '{ "COMM_TYPE": "USB", "PORT": 1212, "HOST": "192.168.254.3"}'

Set configuration properties on the specified port (1, 2, 3). 1 to N number of configuration properties can be set with a single call.

Here is a table of Device Port properties that can be set.

Key Type Description Examples
COMM_TYPE string Serial communication interface type. RS232, RS485, USB
BAUD_RATE integer Baud rate of the connection. 230400, 115200, 57600, 38400, 19200, 9600, 4800, 1200
DATA_BITS integer Data bits. 8, 7, 6, 5
PARITY_BIT string Parity bit. NONE, ODD, EVEN, MARK, SPACE
DELIMITERS_HEX string End of message delimiter marker. 0D0A, 0D
PORT string Only used in USB for RNDIS driver. 1212
HOST string Only used in USB for RNDIS driver. 192.168.254.3

DELETE Device Port Properties

Remove device configuration properties on the specified port (1, 2, 3)

curl -X DELETE 'http://localhost/api/configure?port=1' -d '{"resources" : ["DELIMITERS_HEX"] }'
curl -X DELETE 'http://localhost/api/configure?port=1' -d '{"resources" : ["HOST", "PORT"] }'

Remove device configuration properties on the specified port (1, 2, 3)

GET Interface Priority

get Interface Priority

curl http://localhost/api/interfacePriority

Response

{
  "content":"eth0"
}

Returns the network interface that is currently the preferred route.

PUT Cellular Interface Prioritized

Set cellular to be the preferred network interface to use if available

curl -X PUT 'http://localhost/api/interfacePriority/cellular'

Set cellular to be the preferred network interface to use if available. This sets ppp0 to be used as the default network route.

PUT Ethernet Interface Prioritized

Set ethernet to be the preferred network interface to use if available

curl -X PUT 'http://localhost/api/interfacePriority/ethernet'

Set ethernet to be the preferred network interface to use if available. This sets eth0 to be used as the default network route.

POST Message

Example Body

{
  "portNumber" : 1,
  "message" : "hello world"
}

send a message on the specified device port

curl -X POST http://localhost/api/message -d '{ "portNumber": "1", "message": "hello world"}'
#include <cpprest/http_client.h>
#include <cpprest/filestream.h>

pplx::task<void> postMsg(const char *fileName, const char *urlPath);

using namespace utility;                    // Common utilities like string conversions
using namespace web;                        // Common features like URIs.
using namespace web::http;                  // Common HTTP functionality
using namespace web::http::client;          // HTTP client features
using namespace concurrency::streams;       // Asynchronous streams

int main(int argc, char *argv[]) {

    pplx::task<void> messageTask = msg("message", "/message");
    // Wait for all the outstanding I/O to complete and handle any exceptions
        try {
            messageTask.wait();
        }
        catch (const std::exception &e) {
            printf("Error exception:%s\n", e.what());
        }
        return 0;
}

pplx::task<void> postMsg(const char *fileName, const char *urlPath) {
    auto fileStream = std::make_shared<ostream>();

    // Open stream to output file.
    pplx::task<void> requestTask = fstream::open_ostream(U(fileName)).then([=](ostream outFile) {
                *fileStream = outFile;

                // Create http_client to send the request.
                http_client client(U("http://localhost/api/"));

                // Build request URI and start the request.
                uri_builder builder(U(urlPath));
                const string_t &fragment = builder.to_string();

                web::json::value json_v ;
                json_v["portNumber"] = web::json::value::number(1);
                json_v["content"] = web::json::value::string("hello world");

                return client.request(methods::POST, U(urlPath), json_v);
            })
                    // Handle response headers arriving.
            .then([=](http_response response) {
                printf("Received response status code:%u from:%s\n", response.status_code(), U(urlPath));

                // Write response body into the file.
                return response.body().read_to_end(fileStream->streambuf());
            })

                    // Close the file stream.
            .then([=](size_t) {
                return fileStream->close();
            });
    return requestTask;
}

Send a message on the specified device port (1, 2, 3)

Model

Key Type Description
portNumber integer The device port to send the message.
message string The message to send.

GET Application Version

Get application version.

curl -X GET 'http://localhost/api/version'

Get application version.

GET Firmware Version

Get firmware version.

curl -X GET 'http://localhost/api/firmware'

Get firmware version.

GET Hardware Version

Get hardware version.

curl -X GET 'http://localhost/api/hardware'

Get hardware version.

POST Update

Update application version.

# update to the latest cloud version
curl -X POST 'http://localhost/api/update'
# update to a specific cloud version
curl -X POST 'http://localhost/api/update?version=7.18.0'
# update from a local file on the THREAD filesystem using the full absolute local path
curl -X POST 'http://localhost/api/update?file=/tmp/snoopy-1.2.3.jar'

Update application to latest version or supply optional query parameter version or file to update to a specific version
(e.g. ?version=7.18.0 or ?file=/tmp/snoopy-1.2.3.jar)

POST LED (Accessible)

Update the color of the LED on the exterior of the THREAD to either green (true) or red (false)

# set to red
curl -X POST http://localhost/api/accessible -d '{ "value": "false"}'
 # set to green
curl -X POST http://localhost/api/accessible -d '{ "value": "true"}'

Update the color of the LED on the exterior of the THREAD to either green (true) or red (false)
It should be noted that the cloud connection must be set to false to take advantage of this. If the cloud connection is still enabled (true), then the health of that connection overrides this setting.
This setting is ephemeral and will not survive across a reboot or factory reset. After reboot the LED will be red

GET Cloud Connection

Show the status of the cloud connection enabled (true) or disabled (false)

curl http://localhost/api/cloudConnection

Show the status of the cloud connection enabled (true) or disabled (false)

PUT Cloud Connection

Enable or disable connection to iTwin IoT cloud

 # disable connection
curl -X PUT http://localhost/api/cloudConnection -d '{ "value": "false"}'
 # enable connection
curl -X PUT http://localhost/api/cloudConnection -d '{ "value": "true"}'

Enable or disable connection to iTwin IoT cloud. This will prevent the THREAD from connecting to iTwin IoT cloud. For use by customers who have their own cloud. This will make the exterior LED red which can be adjusted using the LED endpoint above.
This setting is persistent and will remain across reboots but not factory resets

GET Device Connection

Show the status of the device connection enabled (true) or disabled (false)

curl 'http://localhost/api/deviceConnection?port=2'

Show the status of the device connection enabled (true) or disabled (false)

PUT Device Connection

Enable or disable connection to a device plugged into the given physical port

 # disable connection
curl -X PUT 'http://localhost/api/deviceConnection?port=3' -H 'Content-Type: application/json' -d '{ "value": false}'
 # enable connection
curl -X PUT 'http://localhost/api/deviceConnection?port=3' -H 'Content-Type: application/json' -d '{ "value": true}'

Enable or disable connection to a device plugged into the given physical port. This will open the connection for the device on the given port using the defined properties on the device.

iTwin IoT Edge WebSocket API

The iTwin IoT Edge WebSocket API uses WebSockets to allow for streaming bi-directional data. This is useful for “listening” for data as it comes in from a device as well as sending data directly to the device.

Sending and Receiving Data

Example WebSocket client

from websocket import create_connection

# open the WebSocket connection to device on port 1
ws = create_connection("ws://localhost/ws/port/1")
# send over a message to the device directly
ws.send("hello")

# listen for data from the device in an infinite loop and print out
while True:
  result = ws.recv()
  print(result)

# close WebSocket connection when done
ws.close()

Any data that comes in from the device will be sent to the websocket client. Any data that the websocket client sends in over the connection will be sent to the device directly.

Here are the WebSocket URIs any standard WebSocket client can connect to in order to send and receive data.

URI Description
ws://localhost/ws/port/1 device connected to THREAD port 1
ws://localhost/ws/port/2 device connected to THREAD port 2
ws://localhost/ws/port/3 device connected to THREAD port 3

Sensor Data Service

Introduction

The sensor data service offers REST APIs to third-party sensor manufacturers/data sources to integrate their sensors with iTwin IoT platform.

This document focuses on importing generic sensors into iTwin IoT and concentrates on the persona, where the sensor manufacturers will be able to push their data into sensor data service.

Entities Hierarchy

The first step to visualizing data on the platform is to add a connection to a project, then attach subsequent devices and sensors. Your network is broken up into three distinct levels of entities: Connections, Devices, and Sensors.

A connection is simply a connection to your data source. It aggregates the data from all associated devices and sensors and sends the information to the platform. They can be either hardware (cell/radio) or software (API and FTP) connections.

The device can then read the array of associated sensors and store their data, or it can house the sensor datasets being transmitted from the file transfer connection.

A sensor will physically collect the data from the field, and they also represent the data aggregation and storage node in the app.

Entities Hierarchy

Prerequisites

To create a Bentley account if you don’t already have one, click here and complete the registration process.

Use the following steps to integrate your sensors with the iTwin IoT platform.

  1. Create an application in iTwin Platform
  2. Obtaining Authorization Token
  3. Register an asset in iTwin IoT

Create an Application in iTwin Platform

You can register an application on the iTwin Platform to use the sensor data service. Selecting the Administration and Digital Twin Management API associations during registration will add the scopes to the allowed scopes field. Verify that the scopes listed below have been added, drop any other scopes that are unnecessary, choose “Service” as the application type, and save. You will see the client ID and client secret.

Required scopes: sensor-data:modify sensor-data:read users:read itwins:read itwins:modify

Entities Hierarchy

Entities Hierarchy

Obtaining Authorization Token

You can use the generated client id, client secret and scopes to obtain access token.

POST https://ims.bentley.com/connect/token

curl --location 'https://ims.bentley.com/connect/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=<Your client id> ' \
--data-urlencode 'client_secret=<Your client secret> ' \
--data-urlencode 'grant_type=client_credentials' \
--data-urlencode 'scope=sensor-data:modify sensor-data:read users:read itwins:read itwins:modify'
fetch('https://ims.bentley.com/connect/token', {
  method: 'POST',
  body: new URLSearchParams({
    'client_id': '<Your client id> ',
    'client_secret': '<Your client secret> ',
    'grant_type': 'client_credentials',
    'scope': 'sensor-data:modify sensor-data:read users:read itwins:read itwins:modify'
  })
});
import requests

data = {
    'client_id': '<Your client id> ',
    'client_secret': '<Your client secret> ',
    'grant_type': 'client_credentials',
    'scope': 'sensor-data:modify sensor-data:read users:read itwins:read itwins:modify',
}

response = requests.post('https://ims.bentley.com/connect/token', data=data)

Register an Asset in iTwin IoT

In order to create an asset you must have at least one of the following roles assigned in User Management: Account Administrator, Co-Administrator, or CONNECT Services Administrator. Then you can register an asset in iTwin IoT and make sure to enable Allow external team members.

Add the user that got created, while registering an application in iTwin Platform

(example@apps.imsoidc.bentley.com) into the create asset with IoT Creator role.

Entities Hierarchy

Entities Hierarchy

Create Entites

There are two methods by which you can create connection, device(s), and sensor(s):

  1. Simple approach: Allows you to create connection, device(s), and sensor(s) all at once
  2. Advanced approach: Allows you to create connection before adding device(s) & sensor(s)

Create Connection, Device(s), and Sensor(s)

You can use this API to create new connection, devices and sensors all together.

If none of the provided device template/sensor template meet the requirements, you can use IMPORT_DEVICE_SDE for device and GENERIC_SENSOR_SDE for sensor.

POST https://iiot.bentley.com/api/integrations/integrate?projectIds=6ee68656-ce20-452c-a4e6-b6b3a3b55537

curl --location 'https://iiot.bentley.com/api/integrations/integrate?projectIds=6ee68656-ce20-452c-a4e6-b6b3a3b55537' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer <Your access token>' \
--data '{
    "integration": {
        "changeState": "new",
        "devices": [
            {
                "changeState": "new",
                "props": {
                    "INTEGRATION_ID": "IMPORT_DEVICE_SDE",
                    "NAME": "Device-1"
                },
                "sensors": [
                    {
                        "changeState": "new",
                        "props": {
                            "INTEGRATION_ID": "GENERIC_SENSOR_SDE",
                            "NAME": "Sensor-1",
                            "UNKNOWN_UNITS": {
                                "0": "millimeter",
                                "1": "millimeter"
                            },
                            "UNKNOWN_METRICS": {
                                "0": "Alignment_Left_SD",
                                "1": "Alignment_Right_SD"
                            }
                        }
                    },
                    {
                        "changeState": "new",
                        "props": {
                            "INTEGRATION_ID": "GENERIC_SENSOR_SDE",
                            "NAME": "Sensor-2",
                            "UNKNOWN_UNITS": {
                                "0": "millimeter",
                                "1": "millimeter"
                            },
                            "UNKNOWN_METRICS": {
                                "0": "Alignment_Left_SD",
                                "1": "Alignment_Right_SD"
                            }
                        }
                    }
                ]
            }
        ]
    }
} 
 '
fetch('https://iiot.bentley.com/api/integrations/integrate?projectIds=6ee68656-ce20-452c-a4e6-b6b3a3b55537', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer <Your access token>'
  },
  body: JSON.stringify({
    "integration": {
        "changeState": "new",
        "devices": [
            {
                "changeState": "new",
                "props": {
                    "INTEGRATION_ID": "IMPORT_DEVICE_SDE",
                    "NAME": "Device-1"
                },
                "sensors": [
                    {
                        "changeState": "new",
                        "props": {
                            "INTEGRATION_ID": "GENERIC_SENSOR_SDE",
                            "NAME": "Sensor-1",
                            "UNKNOWN_UNITS": {
                                "0": "millimeter",
                                "1": "millimeter"
                            },
                            "UNKNOWN_METRICS": {
                                "0": "Alignment_Left_SD",
                                "1": "Alignment_Right_SD"
                            }
                        }
                    },
                    {
                        "changeState": "new",
                        "props": {
                            "INTEGRATION_ID": "GENERIC_SENSOR_SDE",
                            "NAME": "Sensor-2",
                            "UNKNOWN_UNITS": {
                                "0": "millimeter",
                                "1": "millimeter"
                            },
                            "UNKNOWN_METRICS": {
                                "0": "Alignment_Left_SD",
                                "1": "Alignment_Right_SD"
                            }
                        }
                    }
                ]
            }
        ]
    }
})
});
import requests

headers = {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer <Your access token>',
}

params = {
    'projectIds': '6ee68656-ce20-452c-a4e6-b6b3a3b55537',
}

json_data = {
    "integration": {
        "changeState": "new",
        "devices": [
            {
                "changeState": "new",
                "props": {
                    "INTEGRATION_ID": "IMPORT_DEVICE_SDE",
                    "NAME": "Device-1"
                },
                "sensors": [
                    {
                        "changeState": "new",
                        "props": {
                            "INTEGRATION_ID": "GENERIC_SENSOR_SDE",
                            "NAME": "Sensor-1",
                            "UNKNOWN_UNITS": {
                                "0": "millimeter",
                                "1": "millimeter"
                            },
                            "UNKNOWN_METRICS": {
                                "0": "Alignment_Left_SD",
                                "1": "Alignment_Right_SD"
                            }
                        }
                    },
                    {
                        "changeState": "new",
                        "props": {
                            "INTEGRATION_ID": "GENERIC_SENSOR_SDE",
                            "NAME": "Sensor-2",
                            "UNKNOWN_UNITS": {
                                "0": "millimeter",
                                "1": "millimeter"
                            },
                            "UNKNOWN_METRICS": {
                                "0": "Alignment_Left_SD",
                                "1": "Alignment_Right_SD"
                            }
                        }
                    }
                ]
            }
        ]
    }
}
response = requests.post('https://iiot.bentley.com/api/integrations/integrate', params=params, headers=headers, json=json_data)

Example response (200 OK)

{
    "integration": {
        "node": {
            "props": {
                "SERIAL_NO": "B6627B",
                "CREATION_DATE": "2024-03-05T10:27:22.553+00:00",
                "INTEGRATION_ID": "NODE_API_SDE",
                "PROJECT_ID": "65CB51CE8E199448BB4D4BDA",
                "NAME": "Data Source API Import B6627B"
            },
            "id": "/api/B6627B/node",
            "type": "DYNAMIC"
        },
        "devices": [
            {
                "device": {
                    "props": {
                        "SIZE": 2,
                        "CREATION_DATE": "2024-03-05T10:27:22.585+00:00",
                        "NODE_ID": "/api/B6627B/node",
                        "INTEGRATION_ID": "IMPORT_DEVICE_SDE",
                        "PROJECT_ID": "65CB51CE8E199448BB4D4BDA",
                        "NAME": "Device-1"
                    },
                    "id": "/api/B6627B/node/dynamic/BED858/device",
                    "type": "DYNAMIC"
                },
                "sensors": [
                    {
                        "props": {
                            "UNKNOWN_UNITS": {
                                "0": "millimeter",
                                "1": "millimeter"
                            },
                            "CREATION_DATE": "2024-03-05T10:27:22.615+00:00",
                            "UNKNOWN_METRICS": {
                                "0": "Alignment_Left_SD",
                                "1": "Alignment_Right_SD"
                            },
                            "NODE_ID": "/api/B6627B/node",
                            "INTEGRATION_ID": "GENERIC_SENSOR_SDE",
                            "PROJECT_ID": "65CB51CE8E199448BB4D4BDA",
                            "NAME": "Sensor-1"
                        },
                        "id": "/api/B6627B/node/dynamic/BED858/device/B2D116/sensor",
                        "observationType": "DYNAMIC",
                        "type": "DYNAMIC",
                        "device": "/api/B6627B/node/dynamic/BED858/device"
                    },
                    {
                        "props": {
                            "UNKNOWN_UNITS": {
                                "0": "millimeter",
                                "1": "millimeter"
                            },
                            "CREATION_DATE": "2024-03-05T10:27:22.626+00:00",
                            "UNKNOWN_METRICS": {
                                "0": "Alignment_Left_SD",
                                "1": "Alignment_Right_SD"
                            },
                            "NODE_ID": "/api/B6627B/node",
                            "INTEGRATION_ID": "GENERIC_SENSOR_SDE",
                            "PROJECT_ID": "65CB51CE8E199448BB4D4BDA",
                            "NAME": "Sensor-2"
                        },
                        "id": "/api/B6627B/node/dynamic/BED858/device/5AD71D/sensor",
                        "observationType": "DYNAMIC",
                        "type": "DYNAMIC",
                        "device": "/api/B6627B/node/dynamic/BED858/device"
                    }
                ]
            }
        ]
    }
}

Example response (400 Bad Request)

{
    "error": {
        "code": "InvalidProcessIntegrationRequest",
        "message": "Minimum request parameters not provided, new Devices and Sensors must have a valid `props.INTEGRATION_ID` set."
    }
}

Request parameters

Name Required? Description
projectIds Yes iTwin IoT asset id

Request definitions

Name   Type   Required?   Description  
integration[changeState] string   No To create a new connection (Although this property is not necessary, failing to provide will result in a 422 error)
devices[].changeState/ sensors[].changeState  string   No   To create devices and sensors set changeState to “new”; to update set to “update” to remove set to “remove”. 
INTEGRATION_ID  string  Yes  Device id or sensor id obtained from the devices types /sensor types endpoint.  
NAME  string  No  Device or sensor name 
LOCATION  object  No  X, Y & Z coordinates 
UNKNOWN_UNITS  object  No  Unknow units 
UNKNOWN_METRICS  object  No  Arbitrary metrics 
KNOWN_UNITS  object  No  Known units 
KNOWN_METRICS  object  No  Known metrics 
SERIAL_NO  string  No  Sensor serial no 
NOTES  string  No  Additional information of devices and sensors 
refId  string  No  Unique entity id can be used to update or delete devices or sensors. 

Generic sensors

If you have sensors that you would like to import into iTwin IoT, but the sensors don’t match any of the existing iTwin IoT Sensor templates. In this scenario GENERIC_SENSOR_SDE can be set to sensors props.INTEGRATION_ID property.

Generic sensors MUST have their metrics and units defined on the sensor during the time of creation. Most scenarios which opt to use a generic sensor use free form custom string for both metrics and unit.

Example of unknown metric and unknown unit

{ 
    "changeState": "new", 
    "refId": "{{$guid}}", 
    "props": { 
        "NAME": "Unknown metrics and units", 
        "INTEGRATION_ID": "GENERIC_SENSOR_SDE", 
        "UNKNOWN_UNITS": { 
            "0": "abcUnit" 
        }, 
        "UNKNOWN_METRICS": { 
            "0": "abc" 
        } 
    } 
} 

Example known metric and known unit:
Other scenarios might use both known metrics and known units however the ordering of metrics list slightly differs from a defined sensor template this might look like the sample below

{ 
    "changeState": "new", 
    "refId": "{{$guid}}", 
    "props": { 
        "NAME": "Known metrics and units", 
        "INTEGRATION_ID": "GENERIC_SENSOR_SDE", 
        "KNOWN_UNITS": { 
            "0": "Hz",
            "1": "mm"
        }, 
        "KNOWN_METRICS": { 
            "0": "f",
            "1" : "cum_distance"
        } 
    } 
} 

Example unknown metric and known unit:
Final scenarios might use known units with unknown metrics.

{ 
    "changeState": "new", 
    "refId": "{{$guid}}", 
    "props": { 
        "NAME": "Known metrics and units", 
        "INTEGRATION_ID": "GENERIC_SENSOR_SDE", 
        "KNOWN_UNITS": { 
            "0": "Hz",
            "1": "mm"
        }, 
        "UNKNOWN_METRICS": { 
            "0": "abc",  
            "1" : "def"  
        } 
    } 
} 

Upload Sensor Observations

To upload readings into specified sensor you require sensor id, timestamp at observation recorded and an object values containing required supported metrics with a key-value. You can find the required metrics along with units using Metrics and Units.

POST https://iiot.bentley.com/api/data/upload

curl --location 'https://iiot.bentley.com/api/data/upload' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer <Your access token> ' \
--data '{
    "observations": [
        {
            "sensorId": "/api/B08851/node/dynamic/AB10E2/device/D76619/sensor",
            "timestamp": "2024-02-20T04:35:23.653Z",
            "values": {
                "Alignment_Left_SD": 0.23,
                "Alignment_Right_SD": 0.84
            }
        },
    ]
}' 
fetch('https://iiot.bentley.com/api/data/upload', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer <Your access token> '
  },
  body: '{"observations":[{"sensorId":"/api/B08851/node/dynamic/AB10E2/device/D76619/sensor","timestamp":"2024-02-20T04:35:23.653Z","values":{"Alignment_Left_SD":0.23,"Alignment_Right_SD":0.84}},]}'
});
import requests
headers = {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer <Your access token> ',
}
data = '{"observations":[{"sensorId":"/api/B08851/node/dynamic/AB10E2/device/D76619/sensor","timestamp":"2024-02-20T04:35:23.653Z","values":{"Alignment_Left_SD":0.23,"Alignment_Right_SD":0.84}},]}'
response = requests.post('https://iiot.bentley.com/api/data/upload', headers=headers, data=data)

Example Response (202 Accepted):
This response indicates the observation was successfully uploaded.

{
    "status": "ACCEPTED"
}

Example Response (400 Bad Request):
This response indicates that request lacks valid payload (request body).

{ 
    "errors": [ 
        { 
            "message": "must not be empty", 
            "path": "observations" 
        } 
    ]
} 

Request definitions

Name   Type   Required?   Description  
sensorId  string   Yes   Sensor id 
timestamp  string   Yes  The date and time of the recorded observation in ISO date format 
values  object  Yes  Object containing all required metrics with corresponding value 

Entities Hierarchy

Advanced Connection Creation

To create a new connection via iTwin IoT open previously created asset and click on Connectivity tab and then on +Connection.

Entities Hierarchy

Choose Data Source option, enter Serial Number which is a unique id of underlying datasource system you want to associate a connection (This may go by different names across integrators; for instance, Urban io refers to it as Location Id) and click on Next button.

Entities Hierarchy

Enter connection URL and Authorization Token to form initial platform to platform connection. Input latitude, longitude, elevation and notes if needed and click on Apply button.

Entities Hierarchy

Advanced Device(s), and Sensor(s) Creation

Create new device(s), and sensor(s) under a connection.

The connection id that you generated in iTwin IoT is required. It is also necessary to have the device and sensor INTEGRATION_ID, which is obtained from the device template and sensor template APIs.

POST https://iiot.bentley.com/api/integrations/integrate

curl --location 'https://iiot.bentley.com/api/integrations/integrate?projectIds=<Your asset id>' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer <Your access token>' \
--data '
{
    "integration": {
        "nodeId": "/api/B08851/node",
        "devices": [
            {
                "changeState": "new",
                "refId": "{{$guid}}",
                "props": {
                    "INTEGRATION_ID": "IMPORT_DEVICE_SDE",
                    "NAME": "Test Device",
                    "LOCATION": {
                        "type": "Point",
                        "coordinates": [
                            174.7805678,
                            -41.27834066,
                            20
                        ]
                    },
                    "NOTES":"Demo device"
                },
                "sensors": [
                    {
                        "changeState": "new",
                        "refId": "{{$guid}}",
                        "props": {
                            "NAME": "Test Sensor",
                            "SERIAL_NO":"1234",
                            "INTEGRATION_ID": "GENERIC_SENSOR_SDE",
                            "LOCATION": {
                                "type": "Point",
                                "coordinates": [
                                    174.7805678,
                                    -41.25834066,
                                    65
                                ]
                            },
                            "UNKNOWN_UNITS": {
                                "0": "millimeter",
                                "1": "millimeter"
                            },
                            "UNKNOWN_METRICS": {
                                "0": "Alignment_Left_SD",
                                "1": "Alignment_Right_SD"
                            },
                            "NOTES":"Generic sensor"
                        }
                    },
                ]
            },
        ]
    }
}
'
fetch('https://iiot.bentley.com/api/integrations/integrate?projectIds=6ee68656-ce20-452c-a4e6-b6b3a3b55537', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer '
  },
  body: '{"integration":{"nodeId":"/api/B08851/node","devices":[{"changeState":"new","refId":"/api/13DC2F/node-test-device","props":{"INTEGRATION_ID":"IMPORT_DEVICE_SDE","NAME":"TestDevice","LOCATION":{"type":"Point","coordinates":[174.7805678,-41.27834066,20]},"NOTES":"Demodevice"},"sensors":[{"changeState":"new","refId":"/api/13DC2F/node-test-sensor","props":{"NAME":"TestSensor","SERIAL_NO":"1234","INTEGRATION_ID":"GENERIC_SENSOR_SDE","LOCATION":{"type":"Point","coordinates":[174.7805678,-41.25834066,65]},"UNKNOWN_UNITS":{"0":"millimeter","1":"millimeter"},"UNKNOWN_METRICS":{"0":"Alignment_Left_SD","1":"Alignment_Right_SD"},"NOTES":"Genericsensor"}}]}]}}'
});
import requests
headers = {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer ',
}
params = {
    'projectIds': '6ee68656-ce20-452c-a4e6-b6b3a3b55537',
}
data = '{"integration":{"nodeId":"/api/B08851/node","devices":[{"changeState":"new","refId":"/api/13DC2F/node-test-device","props":{"INTEGRATION_ID":"IMPORT_DEVICE_SDE","NAME":"TestDevice","LOCATION":{"type":"Point","coordinates":[174.7805678,-41.27834066,20]},"NOTES":"Demodevice"},"sensors":[{"changeState":"new","refId":"/api/13DC2F/node-test-sensor","props":{"NAME":"TestSensor","SERIAL_NO":"1234","INTEGRATION_ID":"GENERIC_SENSOR_SDE","LOCATION":{"type":"Point","coordinates":[174.7805678,-41.25834066,65]},"UNKNOWN_UNITS":{"0":"millimeter","1":"millimeter"},"UNKNOWN_METRICS":{"0":"Alignment_Left_SD","1":"Alignment_Right_SD"},"NOTES":"Genericsensor"}}]}]}}'

response = requests.post('https://iiot.bentley.com/api/integrations/integrate', params=params, headers=headers, data=data)

Request parameters

Name Required? Description
projectIds Yes iTwin IoT asset id

Request definitions

Name   Type   Required?   Description  
nodeId   string   Yes   connection id 
changeState  string   No   To create an entity set changeState to “new”; to update set to “update” to remove set to “remove”. 
INTEGRATION_ID  string  Yes  Device id or sensor id obtained from the devices types /sensor types endpoint.  
NAME  string  No  Device or sensor name 
LOCATION  object  No  X, Y & Z coordinates 
UNKNOWN_UNITS  object  No  Unknow units 
UNKNOWN_METRICS  object  No  Arbitrary metrics 
KNOWN_UNITS  object  No  Known units 
KNOWN_METRICS  object  No  Known metrics 
SERIAL_NO  string  No  Sensor serial no 
NOTES  string  No  Additional information of devices and sensors 
refId  string  No  Unique entity id can be used to update or delete devices or sensors. 

Example Response (200 Created):

{ 
    "integration": { 
        "node": { 
            "props": { 
                "LOCATION": { 
                    "type": "Point", 
                    "coordinates": [ 
                        -60.0, 
                        90.0, 
                        123.0 
                    ] 
                }, 
                "AUTH_TOKEN": "test auth", 
                "SERIAL_NO": "12345", 
                "CREATION_DATE": "2024-02-20T12:50:57.069+00:00", 
                "INTEGRATION_ID": "NODE_API_SDE", 
                "URL": "test url", 
                "NAME": "test node", 
                "NOTES": "test notes", 
                "EXTERNAL_ID": "12345", 
                "PROJECT_ID": "65CB51CE8E199448BB4D4BDA" 
            }, 
            "id": "/api/B08851/node", 
            "type": "DYNAMIC" 
        }, 
        "devices": [ 
            { 
                "device": { 
                    "props": { 
                        "LOCATION": { 
                            "type": "Point", 
                            "coordinates": [ 
                                174.7805678, 
                                -41.27834066, 
                                20.0 
                            ] 
                        }, 
                        "SERIAL_NO": "/api/13DC2F/node-test-device", 
                        "SIZE": 1, 
                        "CREATION_DATE": "2024-02-20T12:51:16.418+00:00", 
                        "NODE_ID": "/api/B08851/node", 
                        "INTEGRATION_ID": "IMPORT_DEVICE_SDE", 
                        "NAME": "Test Device", 
                        "NOTES": "Demo device", 
                        "PROJECT_ID": "65CB51CE8E199448BB4D4BDA" 
                    }, 
                    "id": "/api/B08851/node/dynamic/6B7514/device", 
                    "type": "DYNAMIC" 
                }, 
                "sensors": [ 
                    { 
                        "props": { 
                            "LOCATION": { 
                                "type": "Point", 
                                "coordinates": [ 
                                    174.7805678, 
                                    -41.25834066, 
                                    65.0 
                                ] 
                            }, 
                            "UNKNOWN_UNITS": { 
                                "0": "millimeter", 
                                "1": "millimeter" 
                            }, 
                            "NOTES": "Generic sensor", 
                            "SERIAL_NO": "/api/13DC2F/node-test-sensor", 
                            "CREATION_DATE": "2024-02-20T12:51:16.450+00:00", 
                            "UNKNOWN_METRICS": { 
                                "0": "Alignment_Left_SD", 
                                "1": "Alignment_Right_SD" 
                            }, 
                            "NODE_ID": "/api/B08851/node", 
                            "INTEGRATION_ID": "GENERIC_SENSOR_SDE", 
                            "PROJECT_ID": "65CB51CE8E199448BB4D4BDA", 
                            "NAME": "Test Sensor" 
                        }, 
                        "id": "/api/B08851/node/dynamic/6B7514/device/B059A6/sensor", 
                        "observationType": "DYNAMIC", 
                        "type": "DYNAMIC", 
                        "device": "/api/B08851/node/dynamic/6B7514/device" 
                    } 
                ] 
            } 
        ] 
    } 
} 

Example Response (401 Unauthorized):
This response indicates that request lacks valid authentication credentials. Access token might not been provided, issued by the wrong issuer, does not have required scopes or request headers were malformed.

{ 
    "errors": [ 
        { 
            "message": "Token has expired on Tue Feb 20 04:12:01 PST 2024", 
            "path": "Authorization" 
        } 
    ] 
} 

Example Response (402 Unprocessable Entity ):
Invalid request review the request body. The following error messages displayed if required properties (nodeId or INTEGRATION_ID) missing.

{ 
    "error": {
        "code": "InvalidProcessIntegrationRequest", 
        "message": "Cannot process integration ensure the provided nodeId is valid." 
    } 
} 
{
    "error": { 
        "code": "InvalidProcessIntegrationRequest", 
        "message": "Minimum request parameters not provided, new Devices and Sensors must have a valid `props.INTEGRATION_ID` set." 
    } 
} 

Choose Device Template

You can see the list of supported Device templates and choose the suitable device template with the help of this REST API.

GET https://iiot.bentley.com/api/devices/types?excludeLegacy=true

curl --location 'https://iiot.bentley.com/api/devices/types?excludeLegacy=true' \
--header 'Authorization: Bearer <Your access token>'
fetch('https://iiot.bentley.com/api/devices/types?excludeLegacy=true', {
  headers: {
    'Authorization': 'Bearer <Your access token>'
  }
});
import requests
headers = {
    'Authorization': 'Bearer <Your access token>',
}
params = {
    'excludeLegacy': 'true',
}

response = requests.get('https://iiot.bentley.com/api/devices/types', params=params, headers=headers)

In the response you can see the id field of the device template. Which will be needed to create a new device.

Example Response:

{ 
    "deviceTypes": [ 
        {
            "id": "IMPORT_DEVICE_SDE",
            "name": "Import Device",
            "icon": "smicon-device-import",
            "iconUnicode": "",
            "manufacturer": "sensemetrics",
            "minThreadVersion": "1.0",
            "editOnly": false,
            "isDynamic": true
        }
     ] 
} 

Choose Sensor Template

You can see the list of supported Sensor templates and choose the suitable sensor template with the help of this REST API.

GET https://iiot.bentley.com/api/sensors/types?excludeLegacy=true

curl --location 'https://iiot.bentley.com/api/sensors/types?excludeLegacy=true' \
--header 'Authorization: Bearer <Your access token>'

In the response you can see the id field of the sensor template. Which will be needed to create a new sensor.

fetch('https://iiot.bentley.com/api/sensors/types?excludeLegacy=true', {
  headers: {
    'Authorization': 'Bearer <Your access token>'
  }
});
import requests
headers = {
    'Authorization': 'Bearer <Your access token>',
}
params = {
    'excludeLegacy': 'true',
}
response = requests.get('https://iiot.bentley.com/api/sensors/types', params=params, headers=headers)

Example Response:

{ 
    " sensorTypes": [ 
        {
            "id": "GENERIC_SENSOR_SDE", 
            "name": "GENERIC_SENSOR_SDE", 
            "icon": "smicon-circle", 
            "iconUnicode": "", 
            "metricsAndUnits": [ 
                {
                    "metric": "GENERIC", 
                    "id": "GENERIC", 
                    "displayName": "generic", 
                    "defaultUnit": "GENERIC", 
                    "units": [ 
                        { 
                            "unit": "GENERIC", 
                            "id": "GENERIC" 
                        } 
                    ] 
                } 
            ], 
            "isDynamic": true 
        }, 
     ] 
} 

Metrics and Units

To view the catalog of all standard/known metrics and units use this REST request. For example, the temperature sensor above TEMPERATURE_SDE Stores readings for a single metric Temperature which has a metric Id of T the supported units for this metric are Celsius © kelvin (K) and Fahrenheit (F).

GET https://iiot.bentley.com/api/metrics/types?excludeLegacy=true

curl --location 'https://iiot.bentley.com/api/metrics/types?excludeLegacy=true' \
--header 'Authorization: Bearer <Your access token>'
fetch('https://iiot.bentley.com/api/metrics/types?excludeLegacy=true', {
  headers: {
    'Authorization': 'Bearer <Your access token>'
  }
});
import requests
headers = {
    'Authorization': 'Bearer <Your access token>',
}
params = {
    'excludeLegacy': 'true',
}
response = requests.get('https://iiot.bentley.com/api/metrics/types', params=params, headers=headers)

Example Response:

{
    "standard": true, 
    "metrics": [ 
     {
            "metric": "T", 
            "id": "T", 
            "displayName": "Temperature", 
            "defaultUnit": "C", 
            "units": [ 
                { 
                    "unit": "C", 
                    "id": "C", 
                    "displayName": "celsius", 
                    "notation": "°C" 
                }, 
                { 
                    "unit": "K", 
                    "id": "K", 
                    "displayName": "kelvin", 
                    "notation": "°K" 
                }, 
                { 
                    "unit": "F", 
                    "id": "F", 
                    "displayName": "fahrenheit",
                    "notation": "°F" 
                } 
            ] 
        }, 
    ] 
} 

Obtain Required Metrics and Supported Units For a Sensor

POST https://iiot.bentley.com/api/sensors/metrics

curl --location 'https://iiot.bentley.com/api/sensors/metrics?projectIds=6ee68656-ce20-452c-a4e6-b6b3a3b55537&filters=raw' \
--header 'Authorization: Bearer <Your access token> ' \
--header 'Content-Type: application/json' \
--data '{
    "ids": [
        "/api/B08851/node/dynamic/AB10E2/device/D76619/sensor"
    ]
}' 
fetch('https://iiot.bentley.com/api/sensors/metrics?projectIds=6ee68656-ce20-452c-a4e6-b6b3a3b55537', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer <Your access token> ',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    'ids': [
      '/api/B08851/node/dynamic/AB10E2/device/D76619/sensor'
    ]
  })
});
import requests
headers = {
    'Authorization': 'Bearer <Your access token> ',
    'Content-Type': 'application/json',
}
params = {
    'projectIds': '6ee68656-ce20-452c-a4e6-b6b3a3b55537'
}
json_data = {
    'ids': [
        '/api/B08851/node/dynamic/AB10E2/device/D76619/sensor',
    ],
}
response = requests.post('https://iiot.bentley.com/api/sensors/metrics', params=params, headers=headers, json=json_data)

Request parameters

Name Required? Description
projectIds Yes iTwin IoT asset id

According to the response, the Observations values object for sensor /api/5C2AAF/node/dynamic/9D1284/device/95A76E/sensor should have two metricIds Alignment_Left_SD and Alignment_Right_SD.

Example Response (200 Ok)

{
    "metricsBySensor": {
        "/api/13DC2F/node/dynamic/AB10E2/device/D76619/sensor": [
            {
                "defaultUnit": "millimeter",
                "hidden": false,
                "displayName": "Alignment_Left_SD",
                "notation": "Alignment_Left_SD", 
                "name": "Alignment_Left_SD",
                "id": "Alignment_Left_SD",
                "units": [
                    {
                        "notation": "millimeter",
                        "name": "millimeter",
                        "id": "millimeter"
                    }
                ]
            },
            {
                "defaultUnit": "millimeter",
                "hidden": false,
                "displayName": "Alignment_Right_SD",
                "notation": "Alignment_Right_SD",
                "name": "Alignment_Right_SD",
                "id": "Alignment_Right_SD",
                "units": [
                    {
                        "notation": "millimeter",
                        "name": "millimeter", 
                        "id": "millimeter" 
                    }
                ]
            } 
        ] 
    } 
} 

Get Integrations

This API can be used to retrieve information like all devices and sensors of given connection id.

POST https://iiot.bentley.com/api/integrations//api/13DC2F/node

curl --location 'https://iiot.bentley.com/api/integrations//api/13DC2F/node' \
--header 'Authorization: Bearer <your access_token>' \
--header 'Content-Type: application/json' \
--data ''
fetch('https://iiot.bentley.com/api/integrations//api/13DC2F/node', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer <your access_token>',
    'Content-Type': 'application/json'
  },
  body: ''
});
import requests
headers = {
    'Authorization': 'Bearer <your access_token>',
    'Content-Type': 'application/json',
}
response = requests.post('https://iiot.bentley.com/api/integrations//api/13DC2F/node', headers=headers)

Example Response (200 Ok)

{
    "integration": {
        "node": {
            "props": {
                "LOCATION": {
                    "type": "Point",
                    "coordinates": [
                        null,
                        null,
                        null
                    ]
                },
                "LAST_MODIFIED_BY": "ajinkya.gholape@bentley.com",
                "SERIAL_NO": "13DC2F",
                "OBSERVATION_COUNT": 7,
                "CREATION_DATE": "2024-02-19T12:50:48.705+00:00",
                "LAST_MODIFIED_DATE": "2024-02-19T12:52:41.085+00:00",
                "INTEGRATION_ID": "NODE_API_SDE",
                "NAME": "UI-13DC2F",
                "NOTES": "Created through UI",
                "EXTERNAL_ID": "1234",
                "PROJECT_ID": "65CB51CE8E199448BB4D4BDA"
            },
            "id": "/api/13DC2F/node",
            "type": "DYNAMIC"
        },
        "devices": [
            {
                "device": {
                    "props": {
                        "LOCATION": {
                            "type": "Point",
                            "coordinates": [
                                174.7805678,
                                -41.27834066,
                                20.0
                            ]
                        },
                        "SERIAL_NO": "/api/13DC2F/node-test-device",
                        "SIZE": 1,
                        "CREATION_DATE": "2024-02-20T12:47:40.179+00:00",
                        "NODE_ID": "/api/13DC2F/node",
                        "INTEGRATION_ID": "IMPORT_DEVICE_SDE",
                        "NAME": "Test Device",
                        "NOTES": "Demo device",
                        "PROJECT_ID": "65CB51CE8E199448BB4D4BDA"
                    },
                    "id": "/api/13DC2F/node/dynamic/62D7C7/device",
                    "type": "DYNAMIC"
                },
                "sensors": [
                    {
                        "props": {
                            "LOCATION": {
                                "type": "Point",
                                "coordinates": [
                                    -75.68640693450183,
                                    40.06649152407075,
                                    119.52999339815064
                                ]
                            },
                            "NOTES": "Generic sensor",
                            "UNKNOWN_UNITS": {
                                "0": "millimeter",
                                "1": "millimeter"
                            },
                            "SERIAL_NO": "/api/13DC2F/node-test-sensor",
                            "CREATION_DATE": "2024-02-20T12:47:40.206+00:00",
                            "UNKNOWN_METRICS": {
                                "0": "Alignment_Left_SD",
                                "1": "Alignment_Right_SD"
                            },
                            "NODE_ID": "/api/13DC2F/node",
                            "INTEGRATION_ID": "GENERIC_SENSOR_SDE",
                            "PROJECT_ID": "65CB51CE8E199448BB4D4BDA",
                            "NAME": "Test Sensor"
                        },
                        "id": "/api/13DC2F/node/dynamic/62D7C7/device/F55FE5/sensor",
                        "observationType": "DYNAMIC",
                        "type": "DYNAMIC",
                        "device": "/api/13DC2F/node/dynamic/62D7C7/device"
                    }
                ]
            },
            {
                "device": {
                    "props": {
                        "LOCATION": {
                            "type": "Point",
                            "coordinates": [
                                174.7805678,
                                -41.27834066,
                                20.0
                            ]
                        },
                        "SERIAL_NO": "/api/13DC2F/node-device-51",
                        "SIZE": 1,
                        "CREATION_DATE": "2024-02-20T12:45:13.203+00:00",
                        "NODE_ID": "/api/13DC2F/node",
                        "INTEGRATION_ID": "IMPORT_DEVICE_SDE",
                        "NAME": "Device-51",
                        "NOTES": "Demo device",
                        "PROJECT_ID": "65CB51CE8E199448BB4D4BDA"
                    },
                    "id": "/api/13DC2F/node/dynamic/DB01AD/device",
                    "type": "DYNAMIC"
                },
                "sensors": [
                    {
                        "props": {
                            "LOCATION": {
                                "type": "Point",
                                "coordinates": [
                                    -75.69059769203692,
                                    40.064844285778456,
                                    111.5061861896762
                                ]
                            },
                            "NOTES": "Generic sensor",
                            "UNKNOWN_UNITS": {
                                "0": "millimeter",
                                "1": "millimeter"
                            },
                            "SERIAL_NO": "/api/13DC2F/node-sensor-51",
                            "CREATION_DATE": "2024-02-20T12:45:13.229+00:00",
                            "UNKNOWN_METRICS": {
                                "0": "Alignment_Left_SD",
                                "1": "Alignment_Right_SD"
                            },
                            "NODE_ID": "/api/13DC2F/node",
                            "INTEGRATION_ID": "GENERIC_SENSOR_SDE",
                            "PROJECT_ID": "65CB51CE8E199448BB4D4BDA",
                            "NAME": "Sensor-51"
                        },
                        "id": "/api/13DC2F/node/dynamic/DB01AD/device/1FFCBB/sensor",
                        "observationType": "DYNAMIC",
                        "type": "DYNAMIC",
                        "device": "/api/13DC2F/node/dynamic/DB01AD/device"
                    }
                ]
            }
        ]
    }
}