For the complete documentation index, see llms.txt. This page is also available as Markdown.

API Versions

New releaseStable

2026-09-10

Updates

Use this version by specifying the header:

"x-api-version": "2026-09-10"

This version was previously available as the beta version 2026-01-01-beta. The two are identical, and 2026-01-01-beta remains callable, so if you are already using the beta you only need to change the version header. Omitting the x-api-version header targets the default version, which is unchanged by this release.

This version includes several updates for all endpoints. Two of them address the same underlying fact: an id is not guaranteed to be unique within a domain, because some domains name groups of time series rather than every time series individually, so a single query can match several time series that share one id.

  • Metadata: changed response format for /metadata, the response is now a JSON array with metadata objects, where the id is part of the metadata object. In earlier versions this is a JSON object with the id as the key, so time series sharing an id arrive as repeated keys in the same object. While the JSON standard does allow duplicate object keys, most parsers resolve them by keeping only the last occurrence, which drops the other time series without any error. With the array, every matched time series is its own element, so nothing is lost regardless of what parser or software library is used, and the number of elements tells you how many time series the query matched. Additionally, NDJSON has been introduced as a new response format for /metadata.

  • Measurements: the response format for /measurements is unchanged from 2025-10-31, each data point carries an id, a timestamp and a value.

  • Statistics: changed response format for /statistics, which now follows more closely the response format of the /measurements endpoint. The response contains timestamp and id (like in /measurements), and an aggregations object containing all requested aggregations. The aggregation type is not part of the id anymore, and the default aggregation has been changed from avg to mean (avg is not defined when there is only 1 datapoint within a window, while the mean is equal to that datapoint). In earlier versions each result key is {domainId}/{AggregationName}, so time series sharing a domainId produce several objects with the same key and the same timestamp. Nothing in the response says which time series a value came from, and code that indexes the results by key and timestamp overwrites one value with another. This version gives two well defined outcomes instead:

    • By default, time series that resolve to the same id are merged into a single result per time window and id, covering all of their data points.

    • Query with output_domain=id to keep every time series separate. The id domain guarantees uniqueness, so each result carries its own unique id and every value can be traced back to one time series.

Migration guide

This guide walks you through the breaking changes in 2026-09-10 compared to 2025-10-31. Requests and query parameters remain identical to the previous version, apart from one changed default aggregation, so this guide focuses on how to update parsing logic to the changed response formats (with Python code examples).

Measurements endpoint

The /measurements response format is identical in API versions 2025-10-31 and 2026-09-10, in every response format including parquet, so your code does not need updating.

Metadata endpoint The /metadata response has undergone substantial changes. See below for a sample of how the new format looks like, and how to update parsing logic.

Version 2025-10-31 response:

{
  "IMO1000003/jsmea_nav/PositioningSystem/GPS///Longitude/": {
    "name": "Longitude",
    "description": "SHIP POSITION (LONGITUDE)",
    "source": "FurunoVDR",
    "scale": 1.0,
    "uom": "deg",
    "mapsTo": {
      "jsmea": "IMO1000003/jsmea_nav/PositioningSystem/GPS///Longitude/",
      "raalabs": "IMO1000003/GPS_LONG"
    },
    "rangeHigh": 180.0,
    "rangeLow": -180.0,
    "timeSeriesId": "11000001-0000-0000-0000-000000000000",
    "vesselImo": "1000003",
    "vesselName": "Flying Dutchman"
  }
}

Version 2026-09-10 response:

[
  {
    "id": "IMO1234567/jsmea_mac/MainEngine/Cylinder9/ScavAir//Temp/",
    "timeSeriesId": "a8009573-5013-4d18-8e63-1ae2f91d6b80",
    "source": "Aconis",
    "dataProvider": "Raa Labs",
    "unitOfMeasure": "°C",
    "scale": 1.0,
    "vessel": {
      "name": "Flying Dutchman",
      "imo": "1234567"
    },
    "domains": {
      "jsmea": {
        "id": "IMO1234567/jsmea_mac/MainEngine/Cylinder9/ScavAir//Temp/",
        "namingRule": "jsmea_mac",
        "category": "MainEngine",
        "subcategory": "Cylinder9",
        "content": "ScavAir",
        "item": "Temp"
      },
      "raalabs": {
        "id": "IMO1234567/ME CylinderScavAirTemp_9",
        "shortName": "ME CylinderScavAirTemp_9"
      },
      "maker": {
        "id": "IMO1234567/Aconis/0517",
        "name": "0517",
        "description": "M/E CYL.#9 SCAV. AIR BOX FIRE",
        "rangeHigh": 200.0,
        "rangeLow": 0.0
      }
    }
  }
]

How to update your code:

import requests

TOKEN = "YOUR_TOKEN"
ENVIRONMENT = "YOUR_ENVIRONMENT"

headers = {
    "Authorization": f"Bearer {TOKEN}",
    "x-api-version": "2026-09-10"  # was "2025-10-31"
}

url = f"https://portal.raalabs.io/{ENVIRONMENT}/metadata/raalabs/IMO1234567/*"
response = requests.get(url, headers=headers)

# Version 2025-10-31 — response is a dict with domainIds as keys
# note that standard json parsers drop duplicate keys (this is one of major issues fixed with the new api version)
metadata = response.json()  # dict
for tag_id, meta in metadata.items():
    name = meta["name"]
    description = meta["description"]
    unit = meta["uom"]
    scale = meta["scale"]
    source = meta["source"]
    time_series_id = meta["timeSeriesId"]
    vessel_imo = meta["vesselImo"]
    vessel_name = meta["vesselName"]
    range_high = meta["rangeHigh"]
    range_low = meta["rangeLow"]
    raalabs_id = meta["mapsTo"].get("raalabs")

# Version 2026-09-10 — response is a list of metadata objects

metadata = response.json()  # list
for meta in metadata:
    tag_id = meta["id"]                            # was the dict key
    unit = meta["unitOfMeasure"]                   # was "uom"
    scale = meta["scale"]                          # unchanged
    source = meta["source"]                        # unchanged
    time_series_id = meta["timeSeriesId"]          # unchanged
    data_provider = meta["dataProvider"]           # new in this version
    vessel_imo = meta["vessel"]["imo"]             # was "vesselImo"
    vessel_name = meta["vessel"]["name"]           # was "vesselName"
    raalabs_id = meta["domains"]["raalabs"]["id"]  # was mapsTo["raalabs"]
    maker = meta["domains"].get("maker", {})       # a domain is only present if the time series has a name in it
    name = maker.get("name")                       # was "name"
    description = maker.get("description")         # was "description"
    range_high = maker.get("rangeHigh")            # was "rangeHigh"
    range_low = maker.get("rangeLow")              # was "rangeLow"

A time series that has no name in a given domain has no entry for it in domains, so guard the reads that go through one.

If your code needs to look up metadata by identifier, build the dictionary yourself and key it by timeSeriesId. Keying it by id reintroduces the dropped duplicates that the array response fixes.

Statistics endpoint

Two changes affect your queries and their results before you get to the response shape:

  • The default aggregation is mean instead of avg. A request that does not set the aggregations parameter now returns a plain mean of the values in the window, where it previously returned a time-weighted average. Request aggregations=avg explicitly to keep the old calculation.

  • The aggregation names in the response are the lowercase names you requested, for example mean and count. In 2025-10-31 the names and the delimiter were determined by the queried domain, and appeared capitalized in the result keys (.../Mean, .../Count). Code that matches on /Avg or /Mean, or that splits a result key to recover the aggregation type, needs updating. See below:

Version 2025-10-31 response:

[
  {
    "IMO1000001/ME ShaftPower/Avg": 13.12,
    "timestamp": "2025-09-10T09:00:00.000000Z"
  },
  {
    "IMO1000001/ME ShaftPower/Max": 15.47,
    "timestamp": "2025-09-10T09:00:00.000000Z"
  },
  {
    "IMO1000001/ME ShaftPower/Min": 10.85,
    "timestamp": "2025-09-10T09:00:00.000000Z"
  },
  {
    "IMO1000001/ME ShaftPower/Count": 3600,
    "timestamp": "2025-09-10T09:00:00.000000Z"
  }
]

Version 2026-09-10 response:

[
  {
    "timestamp": "2025-09-10T09:00:00.000000Z",
    "id": "IMO1000001/ME ShaftPower",
    "aggregations": {
      "avg": 13.12,
      "max": 15.47,
      "min": 10.85,
      "count": 3600
    }
  }
]

How to update your code:

import requests

TOKEN = "YOUR_TOKEN"
ENVIRONMENT = "YOUR_ENVIRONMENT"

headers = {
    "Authorization": f"Bearer {TOKEN}",
    "x-api-version": "2026-09-10"  # was "2025-10-31"
}

url = f"https://portal.raalabs.io/{ENVIRONMENT}/statistics/raalabs/IMO1234567/ME%20ShaftPower"
params = {"aggregations": "avg,max,min,count", "window": "1h", "last": "1day"}
response = requests.get(url, headers=headers, params=params)
data = response.json()

# Version 2025-10-31 — each object has a dynamic key like "{domainId}/{AggType}"
for entry in data:
    timestamp = entry["timestamp"]
    for key, value in entry.items():
        if key != "timestamp":
            # key is e.g. "IMO1234567/ME ShaftPower/Avg"
            # Split to extract the domainId and aggregation type
            *id_parts, agg_type = key.split("/")
            domain_id = "/".join(id_parts)
            print(f"{domain_id} {agg_type}={value} at {timestamp}")

# Version 2026-09-10 — structured fields with aggregations object
# there is no need for string splitting to get aggregation types
for entry in data:
    timestamp = entry["timestamp"]
    domain_id = entry["id"]
    for agg_type, value in entry["aggregations"].items():
        print(f"{domain_id} {agg_type}={value} at {timestamp}")
Stable

2025-10-31 (default)

Initial dated version. Target this version by using the following version header in your requests.

"x-api-version": "2025-10-31"

This version is supported until 2027-03-10, six months after the release of 2026-09-10, and is deactivated after that date. The migration guide in the 2026-09-10 entry above covers the changes you need to make in order to change to this version.

Migration guide

The functionality of this version is identical to the default version, which is targeted when no version header is specified. There are no changes to client code required when updating to version 2025-10-31 from the unversioned API.

Unversioned API (default)

The unversioned API, this is currently the default version. This version is targeted when no x-api-version header is provided in the request.

Last updated

Was this helpful?