API Versions
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 theidis part of the metadata object. In earlier versions this is a JSON object with theidas the key, so time series sharing anidarrive 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
/measurementsis unchanged from2025-10-31, each data point carries anid, atimestampand avalue.Statistics: changed response format for
/statistics, which now follows more closely the response format of the/measurementsendpoint. The response containstimestampandid(like in/measurements), and anaggregationsobject containing all requested aggregations. The aggregation type is not part of theidanymore, and the default aggregation has been changed fromavgtomean(avgis not defined when there is only 1 datapoint within a window, while themeanis equal to that datapoint). In earlier versions each result key is{domainId}/{AggregationName}, so time series sharing adomainIdproduce several objects with the same key and the sametimestamp. 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
idare merged into a single result per time window andid, covering all of their data points.Query with
output_domain=idto keep every time series separate. Theiddomain guarantees uniqueness, so each result carries its own uniqueidand 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
meaninstead ofavg. A request that does not set theaggregationsparameter now returns a plain mean of the values in the window, where it previously returned a time-weighted average. Requestaggregations=avgexplicitly to keep the old calculation.The aggregation names in the response are the lowercase names you requested, for example
meanandcount. In2025-10-31the names and the delimiter were determined by the queried domain, and appeared capitalized in the result keys (.../Mean,.../Count). Code that matches on/Avgor/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}")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?