The Nuheat OpenAPI delivers access for 3rd Party developers to integrate with the Nuheat smart thermostats.
To gain access to the API, first you must contact our support department to get access to a ClientID.
Request access to the API
To make your life easier when getting started developing against the API, we have implemented Swagger as our Endpoint and Model documentation tool.
This allows you to explore and test the API right in your browser, with our included test client.
The Nuheat OpenAPI uses OAuth2 and OpenID-Connect as the Authorization methods to the API.
OAuth 2 is an authorization framework that enables a service to grant 3rd party applications access to obtain limited access to a users account via a HTTP service.
This protocol allows third-party applications to request limited access to an HTTP service, either on behalf of a resource owner or by allowing the third-party application to obtain access on its own behalf.
Access is requested by the client, which can be a website, desktop application or a mobile application.
Grants:
describes a number of grants (“methods”) for a client application to acquire an access token.
Scopes
provide a way to limit the amount of access that is granted to an access token. It is a lists of identifiers used to specify what access privileges are being requested.
Claims
are name/value pairs that contain information about a user. Profile claims are included that ensures that the request for the user’s info was made using a token that was obtained with the profile scope.
Read more about OAuth 2.0 and OpenID Connect.
Tokens are issued from the OAuth2 server to provide access to the system. Three types exist:
The implicit grant type is optimized for browser-based applications. Either for user authentication-only (both server-side and JavaScript applications), or authentication and access token requests (JavaScript applications).
In the implicit flow, all tokens are transmitted via the browser, and advanced features like refresh tokens are thus not allowed.
openid
openapi
Authorization code flow was originally specified by OAuth 2, and provides a way to retrieve tokens on a back-channel as opposed to the browser front-channel. It also support client authentication.
While this grant type is supported on its own, it is generally recommended you combine that with identity tokens which turns it into the so called hybrid flow. Hybrid flow gives you important extra features like signed protocol responses.
offline_access
- [Optionally required] Add this if you need to get a refresh tokenopenid
openapi
Hybrid flow is a combination of the implicit and authorization code flow - it uses combinations of multiple grant types, most typically code id_token
.
In hybrid flow the identity token is transmitted via the browser channel and contains the signed protocol response along with signatures for other artifacts like the authorization code. This mitigates a number of attacks that apply to the browser channel. After successful validation of the response, the back-channel is used to retrieve the access and refresh token.
This is the recommended flow for native applications that want to retrieve access tokens (and possibly refresh tokens as well) and is used for server-side web applications and native desktop/mobile applications.
offline_access
- [Optionally required] Add this if you need to get a refresh tokenopenid
openapi
Authorization Code | Implicit | Hybrid | |
---|---|---|---|
All tokens returned from authorization endpoint | |||
All tokens returned from token endpoint | |||
Tokens sent via user agent | |||
Client can be authenticated (e.g. using client secret) | |||
Can use refresh tokens | |||
Communication in one round trip | |||
Most communication server-to-server |
Flow | Response Types |
---|---|
Authorization Code | code |
Implicit | id_token |
Implicit | id_token token |
Hybrid | code id_token |
Hybrid | code token |
Hybrid | code id_token token |
Tables adapted from OpenID Connect 1.0 Core Specification.
As a 3rd party developer, you have two (2) webservices to talk to.
Here is an example scenario showing the communication between the components
Here is an example scenario showing the communication between the components
The Nuheat OpenAPI only supports json data. That means that it sends and receives the mimetype application/json
.
200
- Success
204
- No Content
400
- Bad Request
401
- Unauthorized
403
- Forbidden
404
- Not found
500
- Internal server error
The error codes are described more detailed in the error handling section.
All endpoints can send one of the following errors:
400
- Bad Request
401
- Unauthorized
403
- Forbidden
404
- Not found
429
- Too many requests
500
- Internal server error
The following status codes [400
, 500
] will return a json response.
{ "messages": [ "Description of the error that occured.", "Also this occured" ] }
This section covers everything around the models and how they are related to eachother. Additional information on the models can be found on the swagger page, at the bottom of the page.
All the models in the OpenApi are related to each other. With thermostat pointing towards a group id,
then there are more info on that specific group, when you do a GET
request on that specific id.
The distributor OpenAPI supports multiple versions, which means that you as a developer, do not have to worry about
the release of new features or breaking changes.
New features added in the same API version will always be backwards
compatible and any breaking changes will be in a new major version of the api.
First version,
Contains GET
list, GET
single and PUT
for following models:
Burst and rate limits have been implemented to avoid overuse by 3rd party applications. This has been implemented to stop a single client from being able to reduce the quality of service for the rest of the clients, whether it is by accident or on purpose.
These limits are defined as a per user & per client basis. Which means a single user combined with a specific client id, has these specific limits. That means if a client ids user hits the limit, he is blocked out for the rest of that period, but it does not affect the other users of that client id.
Per second | 50 |
---|---|
Per 30 minutes | 750 |
Per 12 hours | 20,000 |
Per 7 days | 250,000 |
When the rate limits are hit, the Nuheat OpenAPI will return HttpCode 429 - Too many requests
.
If you hit the limit the response will be:
Status Code: 429 Retry-After: 58 Content: API calls quota exceeded! maximum admitted 2 per 1m.
If the request doesn't get rate limited then the longest period defined in the rules is used to compose the X-Rate-Limit headers, these headers are injected in the response:
X-Rate-Limit-Limit: the rate limit period (eg. 1m, 12h, 1d) X-Rate-Limit-Remaining: number of request remaining X-Rate-Limit-Reset: UTC date time (ISO 8601) when the limits resets
A client can parse the X-Rate-Limit-Reset like this:
DateTime resetDate = DateTime.ParseExact(resetHeader, "o", DateTimeFormatInfo.InvariantInfo);
A thermostat has two operating modes; Auto and Manual. In Auto mode, the thermostat uses its schedule to adjust the temperature during the day.
When the thermostat is set to Operating Mode Manual, the thermostats Schedule and ScheduleMode functionality is disabled, and only the SetPointTemp can be changed.
The Thermostat Updates from the API when in Manual mode function as usual, the ScheduleMode and HoldSetPointDateTime is ignored in this mode.
In this section we will cover how to use the different modes, and what input you should provide to get the expected result.
Auto operation has 3 modes you can set the thermostat to:
ScheduleMode:Auto
the thermostat will use the schedule to define the temperature it should be set to.
ScheduleMode:Auto
you do not have to set the SetPointTemp
or HoldSetPointDateTime
.
Since it does not use any of thise values it will ignore any input from them.
{ "serialNumber": "00000000", "setPointTemp": 0, "scheduleMode": 1, "holdSetPointDateTime": "" }
ScheduleMode:Hold
, you temporarily set the temperature so it doesn't follow the current schedule.
SetPointTemp
or it will try to use the last used value it had in this mode.
If the HoldSetPointDateTime
is not supplied, it will default to the next schedule event. Once the HoldSetPointDateTime
expires it will
return to ScheduleMode:Auto
.
HoldSetPointDateTime
with a specific date and time, as long as it is within the following 23 hours.
{ "serialNumber": "00000000", "setPointTemp": 3000, "scheduleMode": 2, "holdSetPointDateTime": "2018-10-03T19:00:00+00:00" }
ScheduleMode:Auto
on its own.
SetPointTemp
to set the thermostats target temperature.
{ "serialNumber": "00000000", "setPointTemp": 3000, "scheduleMode": 3 }
The required date and time format is ISO-8601 and its based on UTC. All timestamps must be in 24-hour format.
yyyy-MM-ddTHH:mm:ssZDate
yyyy-MM-ddTime
HH:mm
Date and time: 2018-09-14T07:58:51Z 2018-09-14T09:58:51+02:00 Date: 2017-12-18 2018-09-14 Time: 11:05 23:30
When creating ScheduleDayEventModel
there are some rules that needs to be followed, the API wont allow you to do otherwise:
The energylog endpoints supplies information about the estimated energy usage for a given period.
It tells the amount of minutes the thermostats heating relay has been on, in the given period of that energyUsage
-entry.
If the end-user has supplied information in his account about:
KWatt/hour
and therefor approximate the $ charge
thermostat has spent in the given period.
{ "energyUsageType": "Day", "energyUsageFrom": "2018-09-25", "energyUsageTo": "2018-09-25", "mondayIsFirstDay": true, "energyUsage": [ { "entry": "23", "minutes": 60, "energyKWattHour": 0, "chargeKWattHour": 0 }, { "entry": "22", "minutes": 13, "energyKWattHour": 0, "chargeKWattHour": 0 }, { "entry": "21", "minutes": 0, "energyKWattHour": 0, "chargeKWattHour": 0 }, .... { "entry": "2", "minutes": 0, "energyKWattHour": 0, "chargeKWattHour": 0 }, { "entry": "1", "minutes": 0, "energyKWattHour": 0, "chargeKWattHour": 0 }, { "entry": "0", "minutes": 0, "energyKWattHour": 0, "chargeKWattHour": 0 } ] }
There are three (3) energylog endpoints, and they are defined as:
Lookup energylog for a single day. It will return the energy spent per individual hour of that day.
Lookup energylog for a given week. It will return the energy spent per individual day of that week.
Lookup energylog for a given year. It will return the energy spent per individual month of that year.
Change notifications functionality was implemented utilizing SignalR technology and should be used for minimising an amount of requests to OpenAPI. Currently we support following notification types:
typeid | type | id |
---|---|---|
1 | UserAccount | UserAccount id |
2 | Thermostat | Thermostat Serialnumber |
3 | Schedule | Thermostat Serialnumber |
4 | Group | Group id |
Client side for handling notifications is technology agnostic, and some basic examples for creating SignalR clients can be found here.
Also need to remember that for authorization it is using OAuth2 and OpenID-Connect in the same way as OpenAPI.
[{"type":3,"id":"223414","timeStamp":"2018-10-04T11:51:27Z"}, {"type":2,"id":"223414","timeStamp":"2018-10-04T11:52:27Z"}]
The following plain JS/jQuery code sample shows how to create a simple SignalR client, establish connection and subscribe/unsubscripted for certain notification types. And for authorization it uses oidc-client-js library.
var oidcConfig = { authority: authorityUrl, // OAuth2 authority url. client_id: "js", // client id that will be used for authorization. redirect_uri: redirectUrl, //Redirect url, should be matched with one defined for the specified client. response_type: "id_token token", //Authorization response type scope: "openid profile openapi", //Authorization scope automaticSilentRenew: true, filterProtocolClaims: true, nonce: "N" + Math.random() + "" + Date.now(), loadUserInfo: true }; var manager = new Oidc.UserManager(oidcConfig) var token; manager.signinRedirectCallback().then(function (user) { console.log("signed in", user); token = user.access_token; }).catch(function (err) { console.log(err); }); //Builds SignalR connection. SignalR connection hub is called - "notificationsHost". //So full connection url should always be "baseUrl/notificationsHost". var connection = new signalR.HubConnectionBuilder().withUrl(baseUrl/notificationsHost+ "?token=" + token).build(); //Start new connection. connection.start().then(() => { console.log('Connection started!'); }) .catch(err => console.error(err.toString())); }); //Close connection. //If notification tracking is not needed anymore then connection should be closed, and it's safe to close connection without unsubscribing from what is currently tracking. connection.stop() .then(() => { console.log('Connection stopped!'); }) .catch(err => console.error(err.toString())); }); //Subscribe for notifications after connections starts. var notificationTypes = ["1","2","3","4"] //Subscribe for all kind of notifications. connection .invoke("Subscribe", notificationTypes).then(() => { console.log('Subscribed for notifications'); }) .catch(err => console.error(err.toString())); }); //Unsubscribe from notifications. Please remember that amount of "unsubscribe" calls should be equal to amount of "subscribe" calls connection .invoke("Unsubscribe", notificationTypes).then(() => { console.log('Unsubscribed from notifications'); }) .catch(err => console.error(err.toString())); }); //Handle notifications. The only callback that can be invoked on the client side is called - "Notify". connection.on("Notify", (value) => { traceNotification(value); }); function traceNotification(notificationList) { notificationList.forEach(function (notification) { var notificationType = ""; switch (notification.type) { case 0: notificationType = "UserAccount"; break; case 1: notificationType = "UserAccount"; break; case 2: notificationType = "Thermostat"; break; case 3: notificationType = "Schedule"; break; case 4: notificationType = "Group"; break; } var traceMessage = notificationType + ' notification for item ' + notification.id + " at " + notification.timeStamp; console.log(traceMessage); }); }