# Relynk points and time series

Finding sensors, setpoints and meter points and reading their latest values, time series, summaries and raw readings for charts and tables.

Each function is a POST to the path shown, relative to the connector's base URL, with its body as JSON; the response is JSON. The types below are JSDoc.

Notes:
- Add `?tenantId=<id>` to every call when `GET /me` lists more than one tenant for the user.
- Errors are `{ "error": { "code", "message" } }`; a 400 message says how to change the request.
- Lists page with `nextCursor` (or `offset` and `hasMore`). Times are Europe/Oslo, ranges half-open `[from, to)`, ids are UUIDs. `null` means no data.
- A path never changes its contract; a new version gets a new path.

## POST /points.list/v1

Points (sensors, meters, setpoints, commands) with their unit and latest reading, for a building, for given equipment or rooms (resourceIds: the points they have), or by id. Give at least one of buildingId, resourceIds or ids. To find a kind of point on a kind of resource (room temperatures, the supply air temperature of air handling units), first list those resources with resources.list (typeKeys from resourceTypes.list), then pass their ids as resourceIds together with the point typeKeys: a point type with only a buildingId also matches points of every other resource in the building, and typeKeys include subtypes (brick_Temperature_Sensor also matches supply, return and outside air temperature sensors). Each point names its exact typeKey and its resourceIds. Pages by id: pass nextCursor back until it is null. Feed the ids to points.timeseries, points.summary or points.raw.

Body: `PointsListInput`. Response: `PointsListOutput`.

```js
/**
 * @typedef {Object} PointsListInput
 * @property {string} [buildingId] A building id (from buildings.list). Matches the building itself and everything that is part of it (uuid)
 * @property {Array<string>} [resourceIds] Only points of these resources (equipment, rooms, meters) (min items 1, max items 1000)
 * @property {Array<string>} [ids] Only these points (min items 1, max items 1000)
 * @property {Array<string>} [typeKeys] Only these types and their subtypes, e.g. ["rec_Room"] (keys from resourceTypes.list) (min items 1, max items 100)
 * @property {Array<string>} [unitKeys] Only these units, e.g. ["unit_Kilo_W_hr"] (min items 1, max items 50)
 * @property {string} [search] Only names containing this text, ignoring case (min length 1, max length 200)
 * @property {number} [limit=500] (min 1, max 2000)
 * @property {string} [cursor] nextCursor from the previous page; omit for the first page (uuid)
 */

/**
 * @typedef {Object} PointsListOutput
 * @property {Array<PointsListOutputPointsItem>} points (max items 2000)
 * @property {string | null} nextCursor Pass as cursor for the next page; null on the last
 */

/**
 * @typedef {Object} PointsListOutputPointsItem
 * @property {string} id
 * @property {string} name
 * @property {string} typeKey
 * @property {string} typeName
 * @property {string} typePath e.g. "Point.Sensor.Temperature_Sensor"
 * @property {string | null} unitKey e.g. "unit_Deg_C", "unit_Kilo_W_hr"
 * @property {string | null} unit Unit symbol, e.g. "°C", "kW·h"
 * @property {Array<string>} resourceIds The resources (equipment, rooms, meters) that have this point
 * @property {string | null} buildingId
 * @property {PointsListOutputPointsItemLatest1 | null} latest The most recent reading; null when there is none
 */

/**
 * @typedef {Object} PointsListOutputPointsItemLatest1
 * @property {string} time Oslo time with offset
 * @property {number | null} value
 * @property {string | null} text
 */
```

## POST /points.timeseries/v2

Readings of up to 1000 points aggregated into Oslo-time buckets over [from, to): one shared time axis t and one array per requested metric per point, null where a bucket has no readings. Days, weeks (Monday) and months start at Oslo midnight, so DST days have 23 or 25 hours. Limits: 15m buckets up to 62 days, others up to 400 days, and points × buckets up to 50 000; split longer ranges into several calls.

Body: `PointsTimeseriesInput`. Response: `PointsTimeseriesOutput`.

```js
/**
 * @typedef {Object} PointsTimeseriesInput
 * @property {Array<string>} pointIds Point ids (from points.list), at most 1000 (min items 1, max items 1000)
 * @property {string} from Start, inclusive: "YYYY-MM", "YYYY-MM-DD" (Oslo midnight) or ISO 8601 with offset, e.g. "2026-09-01T06:00:00+02:00"
 * @property {string} to End, exclusive: "YYYY-MM", "YYYY-MM-DD" (Oslo midnight) or ISO 8601 with offset, e.g. "2026-09-01T06:00:00+02:00"
 * @property {"15m" | "hour" | "day" | "week" | "month"} bucket Bucket size
 * @property {Array<"avg" | "min" | "max" | "sum" | "count" | "first" | "last">} [metrics=["avg"]] Per bucket: avg, min, max, sum, count, and first and last reading (min items 1, max items 7)
 */

/**
 * @typedef {Object} PointsTimeseriesOutput
 * @property {"Europe/Oslo"} timeZone
 * @property {"15m" | "hour" | "day" | "week" | "month"} bucket
 * @property {string} from Resolved start, Oslo time with offset
 * @property {string} to Resolved end (exclusive), Oslo time with offset
 * @property {Array<string>} t Bucket starts, Oslo time with offset; index i matches every series array
 * @property {Array<PointsTimeseriesOutputSeriesItem>} series
 */

/**
 * @typedef {Object} PointsTimeseriesOutputSeriesItem
 * @property {string} pointId
 * @property {string | null} unitKey
 * @property {Array<number | null>} [avg] Mean reading
 * @property {Array<number | null>} [min] Lowest reading
 * @property {Array<number | null>} [max] Highest reading
 * @property {Array<number | null>} [sum] Sum of readings
 * @property {Array<number>} [count] Readings in the bucket
 * @property {Array<number | null>} [first] First reading
 * @property {Array<number | null>} [last] Last reading
 */
```

## POST /points.summary/v1

One summary per point over [from, to) (up to 380 days): count, min and max with their times, average, sum, and first and last reading with their times. Cheaper than points.timeseries when one number per point is enough, e.g. the warmest room last month.

Body: `PointsSummaryInput`. Response: `PointsSummaryOutput`.

```js
/**
 * @typedef {Object} PointsSummaryInput
 * @property {Array<string>} pointIds Point ids (from points.list), at most 500 (min items 1, max items 500)
 * @property {string} from Start, inclusive: "YYYY-MM", "YYYY-MM-DD" (Oslo midnight) or ISO 8601 with offset, e.g. "2026-09-01T06:00:00+02:00"
 * @property {string} to End, exclusive: "YYYY-MM", "YYYY-MM-DD" (Oslo midnight) or ISO 8601 with offset, e.g. "2026-09-01T06:00:00+02:00"
 */

/**
 * @typedef {Object} PointsSummaryOutput
 * @property {"Europe/Oslo"} timeZone
 * @property {string} from Resolved start, Oslo time with offset
 * @property {string} to Resolved end (exclusive), Oslo time with offset
 * @property {Array<PointsSummaryOutputPointsItem>} points
 */

/**
 * @typedef {Object} PointsSummaryOutputPointsItem
 * @property {string} pointId
 * @property {string | null} unitKey
 * @property {number} count Readings in the range; 0 means no data and every other field is null
 * @property {number | null} min
 * @property {string | null} minAt When the lowest reading was taken
 * @property {number | null} max
 * @property {string | null} maxAt When the highest reading was taken
 * @property {number | null} avg
 * @property {number | null} sum
 * @property {number | null} first
 * @property {string | null} firstAt
 * @property {number | null} last
 * @property {string | null} lastAt
 */
```

## POST /points.raw/v2

Individual readings of up to 200 points over [from, to) (up to 31 days), ordered by point then time. Prefer points.timeseries or points.summary for analysis; use this to inspect exact readings. Pages: pass nextCursor back until it is null. Each page has one series per point that has readings in it, so merge pages by pointId.

Body: `PointsRawInput`. Response: `PointsRawOutput`.

```js
/**
 * @typedef {Object} PointsRawInput
 * @property {Array<string>} pointIds Point ids (from points.list), at most 200 (min items 1, max items 200)
 * @property {string} from Start, inclusive: "YYYY-MM", "YYYY-MM-DD" (Oslo midnight) or ISO 8601 with offset, e.g. "2026-09-01T06:00:00+02:00"
 * @property {string} to End, exclusive: "YYYY-MM", "YYYY-MM-DD" (Oslo midnight) or ISO 8601 with offset, e.g. "2026-09-01T06:00:00+02:00"
 * @property {number} [limit=5000] Readings per page, across all points (min 1, max 100000)
 * @property {PointsRawInputCursor} [cursor] nextCursor from the previous page; omit for the first page
 */

/**
 * nextCursor from the previous page; omit for the first page
 * @typedef {Object} PointsRawInputCursor
 * @property {string} pointId (uuid)
 * @property {string} time (min length 1, max length 64)
 */

/**
 * @typedef {Object} PointsRawOutput
 * @property {"Europe/Oslo"} timeZone
 * @property {Array<PointsRawOutputSeriesItem>} series
 * @property {PointsRawOutputNextCursor1 | null} nextCursor Pass as cursor for the next page; null on the last
 */

/**
 * @typedef {Object} PointsRawOutputSeriesItem
 * @property {string} pointId
 * @property {Array<string>} t Reading times, Oslo time with offset
 * @property {Array<number | null>} value Numeric readings; null for text-only readings
 * @property {Array<string | null>} text Text readings; usually null
 */

/**
 * @typedef {Object} PointsRawOutputNextCursor1
 * @property {string} pointId (uuid)
 * @property {string} time (min length 1, max length 64)
 */
```