Historical Data and Export
Read timeseries older than the project's retention period, query any time range with a single request, and export data to Excel.
Retention period
Every project has a retention period, two years unless agreed otherwise with Apio. Values within the retention period are kept in the platform’s primary storage and are the fastest to read. Older values are moved to historical storage, where they remain available for queries.
The boundary is computed at the start of the day: with a two-year retention, on 2 October 2026 everything before 2 October 2024 at 00:00 is historical data.
Choosing the query endpoint
All three endpoints accept the same queries and pipelines described in Query Timeseries:
| Endpoint | Reads | Use it when |
|---|---|---|
/telemetry/query | Values within the retention period | You need recent data |
/historical/query | Historical values only | You need data older than the retention period |
/unified/query | Both | The time range may cross the retention boundary, or you don’t want to care about it |
/unified/query splits the time range at the retention boundary, reads each part from the right storage and returns a single, continuous result. The response has the same format as /telemetry/query.
/historical/query responds with 400 Bad Request when timeFrom is within the retention period. When only timeTo is within the retention period, the result stops at the boundary.
Historical and unified queries only accept the series form of the query. Use devices/{deviceId}/{property} instead of deviceId and name.
Apache Arrow results
For large results, the unified endpoint can return an Apache Arrow table instead of JSON arrays. This variant accepts a single query object, not an array:
{
"query": {
"series": ["devices/meter-1/power"],
"timeFrom": "2026-01-01T00:00:00.000Z",
"timeTo": "2026-01-02T00:00:00.000Z"
},
"pipeline": [
{ "resample": { "interval": 900000, "operation": "avg", "from": "2026-01-01T00:00:00.000Z", "to": "2026-01-02T00:00:00.000Z" } }
]
}The response is JSON, and data holds the bytes of the Arrow table in IPC format as an array of numbers. The table has a time column, in milliseconds since the Unix epoch, and one column per series:
import { tableFromIPC } from 'apache-arrow';
const res = await fetch(`https://api.apio.network/projects/${projectId}/unified/arrow/query`, {
method: 'POST',
headers: { Authorization: `apikey ${apiKey}`, 'Content-Type': 'application/json' },
body: JSON.stringify(query),
});
const { data } = await res.json();
const table = tableFromIPC(Uint8Array.from(data));
for (const row of table) {
console.log(new Date(row.time), row['devices/meter-1/power']);
}A GET variant is also available, with the query passed in the q parameter.
Export to Excel
Values can be downloaded as an Excel file:
| Parameter | Required | Description |
|---|---|---|
deviceId | yes | One or more device ids |
name | yes | One or more property names |
timeFrom, timeTo | no | Time range. Defaults to the last hour |
aggregationType | no | raw (default), quarter, hourly or daily |
timezone | no | Time zone used for the timestamps. Defaults to Europe/Rome |
locale | no | Locale used to format the timestamps. Defaults to it-IT |
To pass more than one device or property, repeat the parameter:
curl -G "https://api.apio.network/projects/$PROJECT_ID/telemetry/export" \
-H "Authorization: apikey $APIO_API_KEY" \
--data-urlencode "deviceId=meter-1" \
--data-urlencode "name=activeEnergy" \
--data-urlencode "name=activePower" \
--data-urlencode "timeFrom=2026-09-01T00:00:00.000Z" \
--data-urlencode "timeTo=2026-10-01T00:00:00.000Z" \
--data-urlencode "aggregationType=hourly" \
-o export.xlsxThe file contains one sheet per device, with a timestamp column and one column per property. Column headers use the property’s display name and unit of measurement from the device type.
With an aggregationType other than raw, the export contains the aggregated series of that period. Every exported property must declare an aggregation for that period in its device type.
Exports read values within the retention period and cover at most about two years per request; longer ranges are rejected with 400 Bad Request.