Key Performance Indicators API Developer Guide

Key Performance Indicators API Developer Guide

Introduction to Clockworks Analytics

Clockworks is a cloud-based fault detection and diagnostics (FDD) platform, with a global analysis engine built on 30 person-years of research and development. The comprehensive library of root-cause diagnostics is curated and updated continuously to cover all HVAC equipment and systems.

Clockworks has the ability to run in-depth diagnostics to quantify the avoidable energy costs of each identified performance issue and prioritize by impacts to energy, comfort, and maintenance on a 0-10 scale. Diagnostics can be sorted by portfolio or individual building, as well as equipment class and type of analyses. This allows a user to drill into a specific building, piece of equipment, or type of analysis.

Introduction to the Key Performance Indicators API

The KPI endpoint provides full access to all data collected by and produced by Clockworks. For example, on a single piece of equipment like a chiller the endpoint will expose everything from static information about the equipment details (equipment name, rated capacity, etc.) down to the 5-minute interval data collected throughout each day.

image-20250417-113010.png

Traditional API calls versus the KPI endpoint

Let’s look at an example where you want to know how much energy waste occurred within your building in the last 7 days. For this you must sum all Avoidable Costs identified by the Diagnostics across all equipment within the building across the 7-day date range.

The Traditional Approach

Without the ability to aggregate data, all Diagnostic records for all equipment across the 7-day range must be collected. A POST call is used to specify the building, date range, and analysis interval and the response could include thousands of records based on the number of equipment within the building. As a result, you must parse through each record to find the cost savings field and further manipulate the data to find the sum across all records.

POST

https://rest.buildingsapi.net/core-diag/diagnostics

Post
Body

{
"CID": [79],
"BID": [1235],
"StartDate": "2025-04-10",
"EndDate": "2025-04-16",
"AnalysisInterval": "Daily",
}

Example Response

The call above would return thousands of individual diagnostic records for all equipment within the building across the 7-day period. Each response would have to be parsed to sum the “CostSavings” field for each “StartDate”

{ "ID": "584436452", "AEID": 22498, "AID": 81, "EID": 24478, "EquipmentClassId": 0, "EquipmentTypeId": 0, "StartDate": "2025-04-11T00:00:00", "AnalysisName": "Zone Unit", "EquipmentName": "Bldg1_VAV2", "BID": 1235, "EnergyPriority": 2.0, "ComfortPriority": 10.0, "MaintenancePriority": 4.0, "TotalPriority": 16.0, "CostSavings": 0.0, "Notes": "<div><p><strong>PROBLEM: ROOM AIR TEMPERATURE HIGHER THAN SETPOINT</strong><ul><li>The room air temperature was higher than its setpoint.</li></ul></p><p><strong>Possible Causes:</strong><ul><li>AHU supply air temperature higher than setpoint or max.</li><li>Zone unit supply air damper malfunction.</li><li>High load in zone(s) due to excess occupancy or solar gain.</li><li>Malfunctioning heating coil.</li><li>Uncalibrated or malfunctioning temperature sensor.</li><li>Controls or programming error.</li></ul></p></div><div><p><strong>PROBLEM: SLOW ROOM AIR TEMPERATURE RESPONSE TO CONDITIONING</strong><ul><li>The room air temperature was above its setpoint for 1.5 hours or more after the damper was opened.<li>The maximum time that elapsed before the room air temperature went within deadband, the damper was closed, or the diagnostic period ended was 17.3 hours.</li></ul></p><p><strong>Possible Causes:</strong><ul><li>Damper malfunction.</li><li>Not enough conditioning provided to zone.</li><li>High load in zone(s) due to excess occupancy or solar gain.</li><li>Uncalibrated or malfunctioning sensor.</li><li>Controls or programming error.</li></ul></p></div><div><p><strong>PROBLEM: SETPOINT IS ZERO</strong><ul><li>The zone supply air flow setpoint was zero for 6.6 hours over the analysis period while the zone was operational.</li><li>The times when any setpoint is zero were not used to determine zone compliance with that setpoint.</li></ul></p><p><strong>Possible Causes:</strong><ul><li>Incorrect or non-operational setpoint.</li><li>Controls or programming error.</li></ul></p></div><div><p><strong>Faults and opportunities investigated by this diagnostic:</strong></p><p>Damper cycling check. Max room air temp check. Min room air temp check. Room air temp setback check. Room air temp setpoint tracking. Sensor checks. Setpoint error check. Short cycling check. Slow room air temp response check. Stuck damper check. Supply air flow setpoint tracking. Zone on while unoccupied check. </p></div>", "NotesSummary": "Room air temp higher than setpoint. Setpoint zero. Slow room air temp response to conditioning.", "DiagnosticHyperlink": "https://portal.clockworksanalytics.com/Diagnostics?unitid=79&aid=81&eid=24478&etid=15&ecid=9&bid=1235&rng=DAILY&sd=04/11/2025", "DateCreated": "0001-01-01T00:00:00", "DateModified": "0001-01-01T00:00:00", "UpdateFields": null },

 

The KPI Approach

Unlike traditional API endpoints, the KPI endpoint allows users to customize a query against each dataset and aggregate the data by various parameters like buildings, equipment classes, etc.

Instead of collecting and parsing through thousands of records, the KPI endpoint can support a custom query to find the sum of cost savings for each day:

POST

https://rest.buildingsapi.net/core-kpis/AggregatedData

Post
Body

{
"KQL": [“DiagnosticsDataset | sum(AvoidableCost) by DiagnosticDate],
"Datasets": [“Diagnostics”],
"StartDate": "2025-04-10",
"EndDate": "2025-04-16",
"AnalysisInterval": "Daily",
BID”: [1235]
}

Example Response

The call above aggregates the AvoidableCost field by the DiagnosticDate for the specified range. This reduces thousands of diagnostic records down to a simple sum of AvoidableCost across the 7-day period.

[ { "DiagnosticDate": "2025-04-13T00:00:00Z", "CostSum": 118.0 }, { "DiagnosticDate": "2025-04-15T00:00:00Z", "CostSum": 110.0 }, { "DiagnosticDate": "2025-04-12T00:00:00Z", "CostSum": 3.0 }, { "DiagnosticDate": "2025-04-14T00:00:00Z", "CostSum": 127.0 }, { "DiagnosticDate": "2025-04-11T00:00:00Z", "CostSum": 0.0 }, { "DiagnosticDate": "2025-04-16T00:00:00Z", "CostSum": 0.0 }, { "DiagnosticDate": "2025-04-10T00:00:00Z", "CostSum": 0.0 } ]

Setting up your API Calls

The KPI endpoint exposes ALL datasets within Clockworks with a single URL to call upon. Calls against the KPI endpoint utilize the same POST format shown below for all calls:

Post Call Format

POST

https://rest.buildingsapi.net/core-kpis/AggregatedData

Body

{
"KQL": "DiagnosticsDataset | summarize CostSum = sum(ConvertedAvoidableCost) by EquipmentID, EquipmentName",
"Datasets": ["Diagnostics"],
"DiagnosticStartDate": "2025-04-02",
"DiagnosticEndDate": "2025-04-08",
"DiagnosticAnalysisInterval": "Daily",
"BID": [1235]
}

Post Call Body Parameters

KQL

The KQL parameter is a user-defined Kusto Query Language (KQL) query. KQL is Microsoft’s query language and provides a lot of flexibility for querying and aggregating data to suite the exact use case.

All KQL queries must begin by calling the dataset name you are querying against along with the word “Dataset.” In the example above, the dataset name is “Diagnostics” so the query begins with “DiagnosticsDataset”

Datasets

Datasets for the query must be listed, e.g. [“Dataset1”,”Dataset2”]. See the Available Datasets section below for a full list of Clockworks datasets.

Required Parameters

Some, but not all, datasets have required parameters to run successfully. In the example shown, the Diagnostics call must have a date range and analysis interval. Simpler calls, like getting static information from Buildings have no required parameters.

Optional Parameters

Additional paramters are available for optional use to quickly filter API calls across common elements such as Organizations, Buildings, and Equipment.

Available Datasets

To see available fields within each dataset you can retrieve a single record from each following this POST call format below. Simply replace DatasetName with one of the datasets listed below.

{
"KQL": "DatasetNameDataset | take 1",
"Datasets": ["DatasetName"],
"[RequiredParameters]": "Parameter"
}

Dataset

Description

Dataset

Description

AggregatedRawData

Pre-aggregated statistics on raw data binned into fixed time intervals of hourly, daily, weekly, and monthly.

BuildingGroups

User-defined groups of buildings which can represent buildings within a common geography (e.g. Southeast Region), common management (e.g. Bob’s Buildings) or any other grouping.

Buildings

The Buildings dataset contains static information about the building itself including total area, name, and location.

BuildingVariables

Static variables which provide metadata about the Buildings, such as utility rates and operating schedules.

DataSources

Details on the Clockworks Connect software gateway that is polling raw data from the Building Management System (BMS).

DataSourceHealth

Connected/disconnected status of an entire data source.

Deployments

New deployments of Clockworks. Deployments are related to the Buildings and Equipment being configured.

DiagnosticResults

Diagnostic Results are produced by the daily analysis performed across all connected equipment. A Diagnostic Result can be a fault such as “Stuck pre-heating coil valve” with additional information around the energy cost waste and maintenance priorities.

DiagnosticResultsVData

Calculated points produced by Clockworks with either an hourly or daily timestamp. Calculated points range from fan runtime hours, energy consumed by equipment, fault duration, and more.

Diagnostics

Unlike an individual Diagnostic Result (see above), the Diagnostics are a combined output of all diagnostic results produced by a single analysis on a piece of equipment. The Diagnostic record may contain multiple faults along with their combined impacts to energy waste and maintenance priorities.

Equipment

The Equipment dataset contains static information about the equipment itself including name, class, type, and any user-defined labels.

EquipmentRelationships

Relationships between connected equipment, such as an Air Handling Unit (AHU) that serves many Variable Air Volume (VAV) boxes.

EquipmentVariables

Static variables which provide metadata about the Equipment, such as rated fan power, rated flow, and sequence details to inform analytic checks.

Organizations

The Organizations dataset contains static information about the organization itself including name and ID.

Points

The Points dataset contains static information about the raw data points including name, class, type, and references to the BMS for cross-referencing points.

RawData

5-minute (typically) interval data collected from the Building Management System (BMS).

ResultGroups

A collection of Diagnostic Results that when viewed together improve the investigation of related issues across equipment.

Tasks

User-generated tasks which typically relate to issues identified by the Diagnostics which need to be addressed.

Users

The Users dataset contains static information about the users within an organization including name and email.

Vdata

Calculated points produced by Clockworks with either an hourly or daily timestamp. Calculated points range from fan runtime hours, energy consumed by equipment, fault duration, and more.

Payload Keys

Payload Key

Data Type

Required for Datasets

Notes

Payload Key

Data Type

Required for Datasets

Notes

KQL

String

YES - all calls

 

Datasets

Array of string

YES - all calls

 

AggregatationInterval

string

AggregatedRawDataDataset

 

FiveMinute, Hourly, Weekly, Daily, and Monthly.

BGID

Array Of Integers

 

Building group ID

BID

Array Of Integers

 

Building ID

BuildingClassID

Array Of Integers

 

 

BuildingTypeID

Array Of Integers

 

 

ConfigurationStatusID

Array Of Integers

 

Status of equipment within a new deployment. Defaults to 5 (Complete)

DiagnosticAnalysisInterval

String

DiagnosticResults, DiagnosticResultsVData, Diagnostics,  and VData

 

DiagnosticEndDate

Datetime

DiagnosticResults, DiagnosticResultsVData, Diagnostics, and VData

 

DiagnosticStartDate

Datetime

DiagnosticResults, DiagnosticResultsVData, Diagnostics, and VData

 

EID

Array Of Integers

 

Equipment ID

EquipmentClassID

Array Of Integers

 

 

EquipmentTypeID

Array Of Integers

 

 

IsBuildingVisible

Boolean

 

Defaults to true if not supplied.

IsEquipmentVisible

Boolean

 

Defaults to true if not supplied.

IsVisible

Boolean

 

Defaults to true if not supplied.

PID

Array Of Integers

 

Point ID

PointClassID

Array Of Integers

 

 

PointTypeID

Array Of Integers

 

 

RawDataEndDate

Nullable Datetime

 

 

RawDataStartDate

Datetime

AggregatedRawDataDataset and RawData

 

ResultClassID

Array Of Integers

 

 

ResultSubTypeID

Array Of Integers

 

 

ResultTypeID

Array Of Integers

 

 

TaskAssignedUID

Array Of Integers

 

 

TaskCompletionEndDate

Datetime

 

 

TaskCompletionStartDate

Datetime

 

 

TaskModifiedEndDate

Datetime

 

 

TaskModifiedStartDate

Datetime

 

 

TaskOpenedEndDate

Datetime

 

 

TaskOpenedStartDate

Datetime

 

 

TaskReporterUID

Array Of Integers

 

 

TaskStatus

Array Of Strings

 

 

TaskStatusID

Array Of Integers

 

 

UnitID

Array Of Integers

 

 

VPID

Array Of Integers

 

Vdata Point ID

Sample Calls 

POST https://{URL}/core-kpis/AggregatedData Request Body - { "kql": "AggregatedRawDataDataset", "Datasets":["AggregatedRawDataDataset"], "AggregatationInterval":"Monthly", "RawDataStartDate": "2023-10-31", "RawDataEndDate": "2024-10-31" }
POST https://{URL}/core-kpis/AggregatedData Request Body - { "kql": "BuildingGroups", "Datasets":["BuildingGroups"] }
POST https://{URL}/core-kpis/AggregatedData Request Body - { "kql": "BuildingsDataset", "Datasets":["Buildings"] }
POST https://{URL}/core-kpis/AggregatedData Request Body - { "kql": "BuildingVariablesDataset", "Datasets":["BuildingVariables"] }
POST https://{URL}/core-kpis/AggregatedData Request Body - { "kql": "DiagnosticResultsDataset", "Datasets":["DiagnosticResults"], "DiagnosticStartDate": "2023-10-31", "DiagnosticEndDate": "2024-10-31", "DiagnosticAnalysisInterval": "Monthly" }
POST https://{URL}/core-kpis/AggregatedData Request Body - { "kql": "DiagnosticResultsVDataDataset", "Datasets":["DiagnosticResultsVData"], "DiagnosticStartDate": "2024-10-01", "DiagnosticEndDate": "2024-10-31", "DiagnosticAnalysisInterval": "Daily" }
POST https://{URL}/core-kpis/AggregatedData Request Body - { "kql": "DiagnosticsDataset", "Datasets":["Diagnostics"], "DiagnosticStartDate": "2024-10-24", "DiagnosticEndDate": "2024-10-31", "DiagnosticAnalysisInterval": "Daily" }
POST https://{URL}/core-kpis/AggregatedData Request Body - { "kql": "EquipmentDataset", "Datasets":["Equipment"] }
POST https://{URL}/core-kpis/AggregatedData Request Body - { "kql": "EquipmentRelationshipsDataset", "Datasets":["EquipmentRelationships"] }
POST https://{URL}/core-kpis/AggregatedData Request Body - { "kql": "EquipmentVariablesDataset", "Datasets":["EquipmentVariables"] }
POST https://{URL}/core-kpis/AggregatedData Request Body - { "kql": "PointsDataset", "Datasets":["Points"] }
POST https://{URL}/core-kpis/AggregatedData Request Body - { "kql": "RawDataDataset", "Datasets":["RawData"], "RawDataStartDate": "2025-04-29T00:00:00.000Z" }
POST https://{URL}/core-kpis/AggregatedData Request Body - { "kql": "Tasks", "Datasets":["Tasks"] }
POST https://{URL}/core-kpis/AggregatedData Request Body - { "kql": "VDataDataset", "Datasets":["VData"], "DiagnosticStartDate": "2023-10-31", "DiagnosticEndDate": "2024-10-31", "DiagnosticAnalysisInterval": "Monthly" }
POST https://{URL}/core-kpis/AggregatedData Request Body - { "KQL": "VDataDataset\n\t| project PointTypeID, DateLoggedLocal, ConvertedRawValue, ConvertedRawValueUnitLabel, OrganizationID, OrganizationName, BuildingID, BuildingName, EquipmentID, EquipmentName\n\t| where PointTypeID in (\"976\", \"4920\", \"509\")\n\t| summarize GasUse = sum(ConvertedRawValue) by OrganizationID, OrganizationName, BuildingID, BuildingName, EquipmentID, EquipmentName, Day=bin(DateLoggedLocal, 1d), GasUseUnits = ConvertedRawValueUnitLabel\n\t| join kind=leftouter(\n\tEquipmentVariablesDataset\n\t| project EquipmentID, EquipmentName, ConvertedEquipmentVariableValue, ConvertedEquipmentVariableValueUnitLabel, EquipmentVariableID\n\t| where EquipmentVariableID == 1\n\t| extend EquipGasCost = ConvertedEquipmentVariableValue, EquipGasCostUnits = ConvertedEquipmentVariableValueUnitLabel\n\t) on $left.EquipmentID == $right.EquipmentID\n\t| join kind=leftouter(\n\tBuildingVariablesDataset\n\t| project BuildingID, BuildingName, ConvertedBuildingVariableValue, ConvertedBuildingVariableValueUnitLabel, BuildingVariableID\n\t| where BuildingVariableID == 4\n\t| extend BldgGasCost = ConvertedBuildingVariableValue, BldgGasCostUnits = ConvertedBuildingVariableValueUnitLabel\n\t) on $left.BuildingID == $right.BuildingID\n\t| project OrganizationID, BuildingID, BuildingName, EquipmentID, EquipmentName, GasUse, GasUseUnits, EquipGasCost, EquipGasCostUnits, BldgGasCost, BldgGasCostUnits\n\t| extend CostValue = case(isnull(EquipGasCost), BldgGasCost, \n\tEquipGasCost)\n\t| extend Units = case(isnull(EquipGasCost), BldgGasCostUnits, \n\tEquipGasCostUnits)\n\t| extend GasCost = GasUse * CostValue\n\t| summarize totalGas = sum(GasUse), totalGasCost = round(sum(GasCost), 0) by BuildingID, BuildingName, GasUseUnits\n\t| order by totalGasCost desc", "Datasets":["VData","BuildingVariables","EquipmentVariables"], "UnitID":[79], "BID" : [6065,1235,1238,180,173,1237,5758,179,1236,181], "BGID": [17,21,20,16], "DiagnosticStartDate":"2024-10-28", "DiagnosticEndDate":"2024-10-29", "DiagnosticAnalysisInterval":"Daily" }

 

 

 

Use Case Example: External Reporting with PowerBI

1. Get Data → More → Blank Query

image-20250304-190010.png

2. Setup API Key Parameter

Edit Query → Manage → New Parameter

image-20250304-232936.png

3. Power Query Configuration

let //EDIT ME - POST BODY WITH SINGLE QUOTES PostBody = "{ 'kql': 'EquipmentLiveDataset | take 10', 'Datasets': ['EquipmentLive'], 'UnitID': [79] }", // Wrapping PotBody in double quotes body = Text.Replace(PostBody, "'", """"), // Perform the POST request and Extract to Table url = "https://rest.buildingsapi.net/core-kpis/AggregatedData", headers = [ #"Content-Type" = "application/json", #"Ocp-Apim-Subscription-Key" = APIKey ], data = Json.Document(Web.Contents(url, [Headers = headers, Content = Text.ToBinary(body)])), resultTable = Table.FromRecords(data) in resultTable