How to obtain live data

Connect to a live data stream safely and turn its messages into named variable values.

This article explains in detail how to obtain live data in your own application and how to interpret the messages received. In each section, you will find explanations as well as ready-made code tutorials.

Obtaining live data consists of two main steps:

  • Connecting to a live data stream: a small server requests a JWT token and the variable list, and a client (e.g., a browser) page opens the WebSocket.
  • Interpreting a live data stream: the client's page matches the values in each message to their variables and shows them.

What you will need

  • An Application ID, an access token and a company ID. If you do not know yet how to get them, check IXON API Authentication and How to get a company ID.
  • A data source with variables configured on your device, so it has something to send.

Connecting to a live data stream

Opening the WebSocket and showing the values happens on the client side, but the requests that need your access token don't. They run on your own server.

How the connection is set up

  1. GET /live-data/session: the client application asks your server for a live data session.
    Note: this is an endpoint on your own server, and the name is just an example.
  2. Your server calls the IXON API with its access token:
    1. Discovery: gets the current URLs of the endpoints, including the WebSocket URL.
    2. JWT token: requests a short-lived token for the agents the client may stream, with AuthTokenDataList.
    3. Variable list: gets the names, types and factors of the variables with AgentDataVariableList, needed to interpret the messages.
  3. WebSocket URL, JWT token, variable list: your server sends these to the client. The access token never leaves the server.
  4. WebSocket + Authorization:
  5. Live data: IXON pushes a message with the latest values about every half second, until the connection closes or the token expires.
❗️

Never put your access token in a browser page!

Anyone who opens the page can read it and use it to call the IXON API as you. Request the JWT token on your server and only send that to the client (browser): it expires after at most an hour and only works for the agents you requested it for.

Deciding which agents a user can stream

The IXON API only gives access to the agents allowed by the roles of the account that makes the request.

The server code tutorial below uses this: it streams exactly the agents that AgentList returns for its access token, so no agent IDs are hard-coded.

How you get that access token depends on your use case, for example a Service Account or a user access token. See IXON API Authentication for the options.

⚠️

Set up the right permissions for your access token

The server code tutorial streams every agent its access token can see. For example, a new Service Account gets the Platform Administrator role, which gives access to all agents in the company. Narrow its roles down to the groups or devices it actually needs before you use it for live data.

The JWT token

The WebSocket doesn't accept your regular access token. Your server requests a JWT token for the agents you want to stream with the AuthTokenDataList endpoint.

  • Set expiresIn between 60 (1 minute) and 3600 (1 hour) seconds.
  • The token is the secretId in the response. Use expiresOn to know when you need a new one: before it expires, request a fresh token and open a new connection.
curl --request POST \
     --url 'USER.URL:443/api/auth-tokens/data' \
     --header 'Api-Version: 2' \
     --header "Api-Application: <application_id>" \
     --header "Api-Company: <company_id>" \
     --header 'Content-Type: application/json' \
     --header "Authorization: Bearer <access_token>" \
     --data '{
         "expiresIn": 3600,
         "agents": [
             {"publicId": "<agent_id_1>"},
             {"publicId": "<agent_id_2>"}
         ]
     }'
{
    "status": "success",
    "type": "AuthTokenCreateResponse",
    "data": {
        "publicId": "<token_id>",
        "secretId": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3OTE4MDAwMDB9.ZXhhbXBsZS1zaWduYXR1cmUtbm90LWEtcmVhbC10b2tlbg",
        "expiresOn": "2026-10-06T10:00:00Z"
    }
}

The WebSocket URL

You find the URL in the AgentDataRealTimeWebSocket entry of the API discovery (USER.URL:443/api/). It looks like this:

wss://<host>/agents/{publicIdList}/data-realtime

Replace {publicIdList} with the agent IDs you want to stream, each followed by a comma. The trailing comma is required, also for a single agent. Without it the server returns a 404.

For example, the final URL for two agents looks like this:

wss://wse.nl-ams.core.dkc.ayayot.com/agents/aBcD1234EfGh,iJkL5678MnOp,/data-realtime
❗️

Don't hardcode the WebSocket URL

The URL can change in the future. Read it from the discovery endpoint each time your application starts instead of copying it into your code.

Authenticating the connection

As soon as the connection opens, send the JWT token as the first message, preceded by Authorization: . If the server doesn't receive the token right after the connection opens, it closes the connection.

Here is an example of what the token can look like:

Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3OTE4MDAwMDB9.ZXhhbXBsZS1zaWduYXR1cmUtbm90LWEtcmVhbC10b2tlbg
⚠️

Include every agent in the JWT token

Every agent in the WebSocket URL must also be included in the JWT token. If one of them isn't, the server replies with {"error": "Authorization failed"} and closes the connection, also for the agents that are in the token.

Code tutorial: how to create the server

This tutorial sets up the server side logic: the discovery, the variable list, the JWT token and the session endpoint the client calls.

Interpreting a live data stream

To keep the stream as fast as possible, live data messages contain only the bare values: no variable names, and values grouped by data type.

Every variable on a data source has a variableId. It identifies the variable's value along the whole real-time pipeline, from the IXagent to your application, and it's the key you'll use to decode each message.

The message format

The example below comes from a data source with six variables. The rest of this section uses the same data source to explain how to interpret it.

{
  "message": {
    "points": [
      {
        "tags": [1, 2, 3, 4, 5, 6],
        "bool": [true],
        "int16": [8738, 5],
        "int32": [9074],
        "str": ["", "Recipe A"],
        "time": 1791280800000
      }
    ],
    "sentTime": 1791280800090
  },
  "agent": { "publicId": "<agent_id>" },
  "device": { "publicId": "<source_id>" }
}
FieldMeaning
pointsOne or more sets of values. Each point is a snapshot at one moment.
tagsThe variableId of every variable included in this point. Despite the name, these are not the tagIds used in historical data.
bool, int16, int32, str, ...The values, grouped by data type.
timeWhen the values were read, in milliseconds since the Unix epoch.
sentTimeWhen the message was sent, in milliseconds since the Unix epoch.
agent.publicIdThe agent the values come from.
device.publicIdThe data source the values come from.

The variable list

To know which value belongs to which variable, request the AgentDataVariableList endpoint with at least the fields variableId, name, type, signed, width, factor, source and internalUse. In the setup above, your server does this and passes the list on to the browser.

curl --request GET \
     --url 'USER.URL:443/api/agents/<agent_id>/data-variables?fields=variableId,name,type,signed,width,factor,source,internalUse&page-size=4000' \
     --header 'Api-Version: 2' \
     --header "Api-Application: <application_id>" \
     --header "Api-Company: <company_id>" \
     --header 'Content-Type: application/json' \
     --header "Authorization: Bearer <bearer_token>"
{
    "type": "AgentDataVariable",
    "data": [
        {
            "publicId": "<public_id_1>",
            "variableId": 1,
            "name": "CloudLogger Error Message",
            "type": "str",
            "signed": null,
            "width": null,
            "factor": null,
            "source": { "publicId": "<source_id>" },
            "internalUse": true
        },
        {...},
        {
            "publicId": "<public_id_3>",
            "variableId": 3,
            "name": "Temperature",
            "type": "int",
            "signed": true,
            "width": "16",
            "factor": "0.01000000",
            "source": { "publicId": "<source_id>" },
            "internalUse": false
        },
        {...}
    ],
    "moreAfter": null,
    "status": "success"
}
❗️

Always retrieve the full variable list

Without page-size, the endpoint returns only the first 20 variables. Interpreting with an incomplete list doesn't fail visibly: every value after the first missing variable gets matched to the wrong name. Check that moreAfter is null. If it isn't, request the next page with page-after=<moreAfter> until it is.

Some things to know about the list:

  • variableIds start at 1 for every data source.
  • Variables with internalUse: true, such as CloudLogger Error Message (variableId 1), are used internally. Leave them out of what you display, but keep them in your list: their values still take up a position in the message, and skipping them would misalign the interpretation.
  • Values arrive raw, exactly as the device reads them from the machine. If a variable has a factor, multiply the value by it to get the number the IXON Cloud shows. For example, a raw 8738 with factor 0.01 is 87.38.
  • The variable list only changes when the device's configuration changes, so you can fetch it once and cache it.

Data type keys

Each variable's type, signed and width together tell you which array in the message holds its value.

The array name is the type plus the width, with a u in front for unsigned integers. Types without a width, like bool and str, use their type name.

Note that width and factor are returned as a string ("16" and "0.0100000" respectively), and that integers can also be 8-bit (int8 and uint8).

typesignedwidthArray in the message
inttrue16int16
intfalse16uint16
inttrue32int32
intfalse32uint32
floatnull32float32
boolnullnullbool
strnullnullstr

Matching values to variables

Within each data type array, values are ordered by ascending variableId. The first value in int16 belongs to the int16 variable with the lowest variableId, the second to the next one, and so on.

So, for each point:

  1. Go through the variableIds in tags from lowest to highest.
  2. Look up the variable's data type key.
  3. Take the next unused value from that data type's array.

If you hit a variableId that isn't in your list, the list is incomplete or out of date. Stop interpreting that point rather than guessing, because every value after it would be assigned to the wrong variable.

Example

The internal variable still takes the first str value. If you skip it in the list, Current Recipe (the next str) would get the empty string instead.

Going through tags of the message format example from lowest to highest gives:

variableIdNameData type keyValue in the messageResult
1CloudLogger Error Messagestr"" (1st str value)Hidden: internalUse is true
2Machine Runningbooltrue (1st bool value)true
3Temperatureint168738 (1st int16 value)87.38 after the factor 0.01
4Production Counterint329074 (1st int32 value)9074
5Production Speedint165 (2nd int16 value)5
6Current Recipestr"Recipe A" (2nd str value)"Recipe A"

When using two or more data sources

Because variableIds start at 1 for every data source, two data sources on the same device can both have, for example, a variable 6. To tell them apart:

  • Include the field source in your AgentDataVariableList request. Each variable then comes with the publicId of the data source it belongs to.
  • Use device.publicId in each live data message to know which data source the message comes from, and only interpret it against that data source's variables.

Code tutorial: the browser page

This code tutorial is the client side: it opens the WebSocket, interprets every message as described above and shows the values in a live table. It uses the server from the previous code tutorial.

Troubleshooting

ErrorPossible Cause
404 when opening the WebSocketMissing trailing comma in {publicIdList}.
Connection opens, then closes immediatelyThe JWT token wasn't sent on open, has expired, or doesn't include this agent.
Connected, but no messages arriveThe device is offline or has no variables configured on its data source.
Values appear under the wrong namesThe variable list is incomplete. Check the pagination.
Values differ from the portal by a factor of 10, 100, ...The variable's factor isn't applied.

Did this page help you?