# Relynk consumption

Energy, water and waste consumption and cost per building or per meter, with data quality, for energy dashboards and reports.

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 /consumption.buildings/v1

Energy, water and waste consumption of buildings from EOS, exactly as the EOS dashboard shows it: per building and carrier (electricity, heating, cooling, gas, water, waste), the sum of the building's building meters (physical and virtual; sub-meters never), per Oslo-time bucket over [from, to), with cost, estimated share, coverage and confidence per bucket and in total. Use this for any building or portfolio consumption, cost or comparison; never add up raw meter points for that. Values are already consumption per hour (not meter readings), in kWh (energy), L (water) or kg (waste); never add different carriers together. null means no data for the bucket, not zero. Omit buildingIds for every building with EOS data. day, week and month buckets need from and to at Oslo midnight; hour buckets up to 62 days, others up to 1100 days, and buildings × carriers × buckets up to 50000.

Body: `ConsumptionBuildingsInput`. Response: `ConsumptionBuildingsOutput`.

```js
/**
 * @typedef {Object} ConsumptionBuildingsInput
 * @property {Array<string>} [buildingIds] Building ids (from buildings.list), at most 200; omit for all (min items 1, max items 200)
 * @property {Array<"electricity" | "gas" | "thermal" | "thermal_heat" | "thermal_cool" | "water" | "waste">} [carriers] Only these carriers; omit for all (min items 1, max items 7)
 * @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 {"hour" | "day" | "week" | "month"} bucket Bucket size; weeks start on Monday
 * @property {boolean} [includeSubCategories=false] Also split each carrier by what it was consumed for (the waste fraction for waste)
 */

/**
 * @typedef {Object} ConsumptionBuildingsOutput
 * @property {"Europe/Oslo"} timeZone
 * @property {"hour" | "day" | "week" | "month"} bucket
 * @property {string} from
 * @property {string} to
 * @property {Array<string>} t Bucket starts, Oslo time with offset; index i matches every array
 * @property {Array<ConsumptionBuildingsOutputBuildingsItem>} buildings
 */

/**
 * @typedef {Object} ConsumptionBuildingsOutputBuildingsItem
 * @property {string} buildingId
 * @property {string} buildingName
 * @property {Array<ConsumptionBuildingsOutputBuildingsItemCarriersItem>} carriers Carriers with data; empty when the building has no EOS data in the range
 */

/**
 * @typedef {Object} ConsumptionBuildingsOutputBuildingsItemCarriersItem
 * @property {"electricity" | "gas" | "thermal" | "thermal_heat" | "thermal_cool" | "water" | "waste"} carrier
 * @property {"kWh" | "L" | "kg"} unit
 * @property {number} meterCount Building meters with data in the range
 * @property {Array<number | null>} consumption Consumption per bucket
 * @property {Array<number | null>} estimated Part of it EOS estimated to fill gaps
 * @property {Array<number | null>} cost Cost per bucket; null where no hour is priced
 * @property {Array<number | null>} coverage Share of the expected hours with measured data, 0 to 1
 * @property {Array<"HIGH" | "MEDIUM" | "LOW" | "MISSING" | null>} confidence HIGH, MEDIUM, LOW or MISSING per bucket (coverage minus a penalty for estimated values); null when nothing could be scored yet
 * @property {Array<number | null>} sortedShare Waste only: share of the weight counted as sorted (sorting grade); empty for other carriers
 * @property {Array<ConsumptionBuildingsOutputBuildingsItemCarriersItemSubCategoriesItem>} subCategories Only with includeSubCategories
 * @property {ConsumptionBuildingsOutputBuildingsItemCarriersItemTotal} total
 * @property {Array<ConsumptionBuildingsOutputBuildingsItemCarriersItemOverlappingBuildingMetersItem>} overlappingBuildingMeters Virtual building meters calculated from another building meter counted here, or from its sub-meters: that consumption is in the total twice. Mention it whenever not empty
 */

/**
 * @typedef {Object} ConsumptionBuildingsOutputBuildingsItemCarriersItemSubCategoriesItem
 * @property {string | null} label What it was consumed for, or the waste fraction; null when not set
 * @property {Array<number | null>} consumption Consumption per bucket
 * @property {Array<number | null>} cost Cost per bucket
 * @property {number | null} total
 * @property {number | null} totalCost
 */

/**
 * @typedef {Object} ConsumptionBuildingsOutputBuildingsItemCarriersItemTotal
 * @property {number | null} consumption
 * @property {number | null} estimated
 * @property {number | null} estimatedShare
 * @property {number | null} cost
 * @property {number | null} costCoverage Share of the meter-hours with a price. Below 1 the cost is incomplete: say so
 * @property {number | null} coverage
 * @property {"HIGH" | "MEDIUM" | "LOW" | "MISSING" | null} confidence The lowest bucket confidence, as on the dashboard
 * @property {number | null} sortedShare
 * @property {number} bucketsWithoutData
 */

/**
 * @typedef {Object} ConsumptionBuildingsOutputBuildingsItemCarriersItemOverlappingBuildingMetersItem
 * @property {string} virtualMeterId
 * @property {Array<string>} sourceMeterIds
 */
```

## POST /consumption.meters/v1

Consumption of up to 10000 EOS meters (energy, water, waste; ids from meters.list) per Oslo-time bucket over [from, to), from the hourly values EOS processed (resets, gaps and virtual meter formulas handled): consumption, the estimated part, cost, missing hours and confidence per bucket, and per meter a total with the peak hour, coverage, cost coverage and hours per confidence tier. Values are consumption per hour, not meter readings: sum them, never take differences. Unit per meter: kWh, L or kg. null means no data, not zero (waste meters: no pickup is 0). For a building's total use consumption.buildings instead of adding meters: sub-meters are part of their parent. from and to must be whole hours; hour buckets up to 62 days, others up to 400, meters × days up to 20000.

Body: `ConsumptionMetersInput`. Response: `ConsumptionMetersOutput`.

```js
/**
 * @typedef {Object} ConsumptionMetersInput
 * @property {Array<string>} meterIds Meter ids (from meters.list), at most 10000 (min items 1, max items 10000)
 * @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 {"hour" | "day" | "week" | "month"} bucket Bucket size; weeks start on Monday
 */

/**
 * @typedef {Object} ConsumptionMetersOutput
 * @property {"Europe/Oslo"} timeZone
 * @property {"hour" | "day" | "week" | "month"} bucket
 * @property {string} from
 * @property {string} to
 * @property {Array<string>} t Bucket starts, Oslo time with offset; index i matches every array
 * @property {Array<ConsumptionMetersOutputMetersItem>} meters
 */

/**
 * @typedef {Object} ConsumptionMetersOutputMetersItem
 * @property {string} meterId
 * @property {string} name
 * @property {"electricity" | "gas" | "thermal" | "thermal_heat" | "thermal_cool" | "water" | "waste"} carrier
 * @property {"kWh" | "L" | "kg"} unit
 * @property {boolean} isBuildingMeter
 * @property {boolean} isVirtual
 * @property {string | null} buildingId
 * @property {Array<number | null>} consumption Consumption per bucket; null when the meter has no values in it
 * @property {Array<number | null>} estimated Part of it EOS estimated to fill gaps
 * @property {Array<number | null>} cost Cost per bucket; null where no hour is priced
 * @property {Array<number>} missingHours Expected hours without a measured or estimated value
 * @property {Array<"HIGH" | "MEDIUM" | "LOW" | "MISSING" | null>} confidence HIGH, MEDIUM, LOW or MISSING per bucket; null when no hour of it could be scored yet (future, or still arriving)
 * @property {ConsumptionMetersOutputMetersItemTotal} total
 */

/**
 * @typedef {Object} ConsumptionMetersOutputMetersItemTotal
 * @property {number | null} consumption
 * @property {number | null} measured
 * @property {number | null} estimated
 * @property {number | null} estimatedShare
 * @property {number | null} cost
 * @property {number | null} costCoverage Share of the hours with a price. Below 1 the cost is incomplete: say so
 * @property {ConsumptionMetersOutputMetersItemTotalPeak1 | null} peak The highest hourly consumption and its hour (Oslo time)
 * @property {number} expectedHours
 * @property {number} missingHours
 * @property {number} pendingHours Recent hours that have often not arrived yet; not scored
 * @property {number} highHours
 * @property {number} mediumHours
 * @property {number} lowHours
 * @property {number | null} coverage Measured share of the expected hours, 0 to 1
 * @property {"HIGH" | "MEDIUM" | "LOW" | "MISSING" | null} confidence The lowest bucket confidence
 */

/**
 * @typedef {Object} ConsumptionMetersOutputMetersItemTotalPeak1
 * @property {number} value
 * @property {string} at
 */
```