Skip to main content

Real Time Events

Besides HTTP commands to control devices, a WebSocket connection can be established, via OAuth account credentials, so a real time events about device status changes can be received, some simple device control commands can also be issued. The API allows simple way for a integrator to monitor devices statuses associated with single Shelly cloud account.

note

This is work in progress. Some URLs might change in the final variant of this documentation

Intended use and implementation example

This API is intended to be used by third party integrator that provide user account level integration with their services or by DIY enthusiast to experiment with some complex scripting of automations over their devices. This API is not intended to be used by cloud-to-cloud integrators that need to monitor multiple user accounts for device status events. If your use case leads to having multiple Real Time Events Web Sockets opened from single service point you should strongly consider the cloud-to-clod centric Integrator API

A working NodeJS/TypeScript example for consuming this API can be found at https://github.com/ALLTERCO/ushelly

Expected architecture on the integrator side

Shelly CloudSecure Local DBWebSocketfor live status updateHTTP Control RequestsOAuth Callback helperAccess token DBOAuth login initiation

OAuth as per RFC6749

This API uses OAuth for authorization. Take a look at this 6 minutes video for OAuth workflow refreshment as the documentation assumes the reader is familiar with OAuth. For oauth to work a client id must be provided to authorization APIs. If you are a DIY enthusiast please use shelly-diy as client id. If you're a third party integrator please contact us at support@allterco.com to obtain a integrator specific client id and callback URL matching regexp. Tokens obtained with shelly-diy client id MIGHT be subject of rate restrictions.

OAuth details (work in progress)

Triggering the login process

The most secure for account owner login process follows this scenario:

sequenceDiagram participant Account Owner participant Account Owner Agent's JS participant Integrator Site participant Shelly Cloud Account Owner->>Integrator Site: Load Intergrator's UI Integrator Site-->>Account Owner Agent's JS: Intergrator's JS Note right of Account Owner: Generic site browsing Account Owner Agent's JS->>Account Owner: Redirect to Shelly Cloud Account Owner->>Shelly Cloud: Load Shelly OAuth Login UI Shelly Cloud-->>Account Owner Agent's JS: Shelly Clouds's JS Note right of Account Owner: Provides raw credentials Account Owner Agent's JS-->>Shelly Cloud: Creates Authorization Code Account Owner Agent's JS->>Account Owner: Redirect to Integrator's callback Account Owner->>Integrator Site: Load Integrator callback URL Integrator Site-->>Account Owner: Redirected to other URI path post callback data handling Account Owner-->>Integrator Site: Load post OAuth login UI

Although a scenario where the handling of raw credentials from account owner and creating the Authorization Code is done entirely with Integrator's JS/Server side code is technically possible we strongly advise against it as account owners might have hard time trusting their raw credentials to UI from domain other than shelly.cloud

Our current OAuth authorization page (authorization endpoint) is at https://my.shelly.cloud/oauth_login.html When you redirect the account owner to this page you need to specify in GET parameter client_id the client id assigned to your system. For DIY enthusiast that just want to try something for their account this parameter should be shelly-diy. Other important GET parameter that the authorization page considers is state that is appended to the callback url. N.B. no URL encode is applied to it's value to allow easy GET parameters appending on the callback url, but if your value there contains & unintentionally you should double urlencode your data or use some other kind of encoding. We also support the redirect_uri parameter that is verified by the server to match the per client id preconfigured regular expression.

Obtaining the authorization code

When the authorization is complete the user browser will be redirected to client id specific callback URL with some GET parameters added:

  • state from the authorization endpoint request
  • code the authorization code used later to obtain access tokens for actual API calls and WebSocket connection authentications.

Understanding the authorization code and access token

All OAuth tokens from Shelly cloud are JWT tokens. They are signed with a secret key but you can still decode them via your favorite JWT library or if you prefer to not depend on too much libraries you can just split the token by ., take the middle part and base64decode it. Anyways you should get a JSON encoded object. The most important field there is user_api_url that gives the designated server for the authorizing user. Subsequent HTTP calls/web socket connections MUST address the host mentioned in user_api_url from the access token or authorization code.

Obtaining access token

To do any HTTP API calls or to be authenticated for WebSocket connection you need a access token a time limited representation of the account owner consent for account access. Obtaining the access code is a matter of calling HTTP API at https://<shelly_server>/oauth/auth you need to provide these parameters:

  • client_id - the client id assigned to your system or shelly-diy for experimental purposes
  • grant_type - this should be set to code
  • code - this should be set to URLEncoded authorization code obtained from the OAuth authorization process described above

On success you should get a JSON response - a object with most important field access_token holding the access token you should use for authorization on all API calls. As mentioned above the access token is a JWT token with built in expiration date, If the JWT access token is expired the server will refuse to serve any requests and you will need to refresh your access token.

Common API considerations

Understanding the Device IDs

There is a contradiction in the way devices are identified in different parts of Shelly cloud. All user facing interfaces use the hexadecimal representation of the device ID while the device facing parts of the cloud prefer the decimal representation as string. There are also other devices (BLE, ZWave) that use device IDs starting with letter X that are string-only and can't be converted to or from hex. APIs mix and match those representations. Both representations have pros and cons but any of them should uniquely identify a device. Keep in mind that hexadecimal representation MUST be prepadded with 0 to 6 or 12 characters length and if you want to check if two hexadecimal ids are equal you MUST transfer both to upper or lower case. In the example implementation project there is a function shelly_devid_hex in shelly_types.ts for converting from decimal to hexadecimal representation. From hexadecimal to decimal representation a simple call to JS String(parseInt(hexid,16)) is enough. Keep in mind that all device IDs that appear in JSON MUST be strings.

Understanding the Device Generations

There are two distinct generations of Shelly devices gen1 and gen2 all devices in the same generation have similar data structures but there are some drastic differences between the two generations. It is reasonable to expect that the two generations will be handled in the third party code by different modules with almost no shared code so a quick way to identify the generation at top level logic is almost always present in the data structures. Gen1 devices are usually designated with "gen": "G1", gen2 devices with "gen": "G2" and BLE devices with "gen": "GBLE". Sometimes a "virtual" might get reported with different designations. There are other generations of devices not discussed here such as ZWave line of devices. You should early filter out device generations that your code can't handle.

HTTP API

Shelly cloud has many callable HTTP API endpoints but we prefer to retain the ability to change any of their parameters and result formats. Not documenting them is a deliberate choice in this direction. Here we will document the bare minimum to allow functional communication with third party integrators at account level.

Authorization

All HTTP API calls should include Authorization : Bearer <ACCESS_TOKEN> header to facilitate the OAuth authorization.

Interpreting API results

Most HTTP API calls return JSON formatted responses with common structure:

On success:

{
isok:true;
data: ....
}

On error:

{
isok:false;
errors: [
....
]
}

List of current/last known statuses of owned devices

/device/all_status?show_info=true&no_shared=true

This endpoint returns in data.devices_status a object with fields named after all devices ID holding current status or last known status of all devices owned by the account. Here is an shortened example of a response:

{
"isok": true,
"data": {
"devices_status": {
"dc4f2276846a": {
.......
"_dev_info": {
"id": "dc4f2276846a",
"gen": "G1",
"code": "SHSW-1",
"online": false
}
},
.......
"84cca87c0144": {
.......
"_dev_info": {
"id": "84cca87c0144",
"gen": "G2",
"code": "SPSW-001PE16EU",
"online": true
}
},
.......
"1643370677417": {
.......
"_dev_info": {
"gen": "V1",
"id": 1643370677417,
"code": "THERMOSTAT",
"online": false
}
}
}
....
}
}

Few things to note as supported by the example above:

  • All reported statuses have the _dev_info metadata attached. Where the generation (G1,G2, V1 ) of the device, it's product code (SHSW-1, SPSW-001PE16EU and THERMOSTAT) and device id are explicitly stated.
  • There are other generations of devices beside gen1 (G1) and gen2 (G2). You should explicitly check the type and not assume "if it is not gen1 it's gen2"
  • The key values in devices_status should be ignored as they are inconsistent, especially with the virtual thermostat devices, and will probably change at some point.
  • For gen1 and gen2 devices the id in _dev_info.id is the hexadecimal id of the device. For other generations this might not be the case.
  • online key in _dev_info indicates the online status as seen by the could. Whether the device is locally available is something different. Some battery devices on the other hand will be reported online while they are actually offline. Simply put, the cloud will refuse to handle commands for devices marked as offline. Translated to battery operated devices this means that when a battery device is marked offline the cloud will not try to store commands for delivery when the device connects back.
  • All other keys in data should be ignored

WebSocket API

To obtain real time data with device statuses a WebSocket connection to the proper shelly cloud server should be established. The url for the connection is wss://<shelly_cloud_server>:6113/shelly/wss/hk_sock?t=<ACCESS_TOKEN>

All messages coming from shelly cloud are JSON encoded. All messages coming from the server SHOULD have event key holding string identifying the event type. you code should be prepared for ignoring unknown message formats and unknown event types.

Event Shelly:StatusOnChange

This event is send every time a status change is reported from a device.

{
"event": 'Shelly:StatusOnChange',
"device": {
"id": string,
"code": string,
"gen": string,
},
"status": {
....
};
}
  • members of device identify the originating device
  • status holds the new status of the device.

Event Shelly:Online

This event is send every time the online status of a device changes.

{
"event": 'Shelly:Online',
"device": {
"id": string,
"code": string,
"gen": string,
},
"online": number
}
  • members of device identify the device whose online status changes
  • online holds the new status of the device. if it is 1 the device is considered online

Event Shelly:CommandResponse

This event is received when a command send to device gets resolved.

{
"event": 'Shelly:CommandResponse',
"deviceId":string,
"trid":number,
"data":unknown
...
}
  • trid is the transaction id that led to this response
  • deviceId is the originating device
  • data is the response send from the device(or the error code send from the server)

Request Shelly:CommandRequest

The websocket client can request control operation to devices via shelly cloud. All request are send via common "event" structure:

{
"event":"Shelly:CommandRequest",
"trid":number,
"deviceId":string,
"data":{
"cmd":string
.....
}
}
  • trid is a transaction id number linking request message to response message. The response arrives via Shelly:CommandResponse event. This is transparent to the cloud, best practice is to use atomically incremented counter that wraps around to 0 at some sane value.
  • deviceId is the target device to handle the command.
  • data holds the command payload
  • data.cmd identifies the command with other keys in data being specific to the command itself

Implemented command requests:

relay

The command changes a relay state.

{
"cmd":"relay",
"params":{
"turn":string,
"id":number
}
}
  • params.turn is on, off or toggle
  • params.id identifies the relay channel to change, this starts from 0. It must be present even for single channel relays with value 0

light

The command changes a light controller state.

{
cmd: 'light',
params: {
id:number
turn?: string,
mode?: string,
timeout?: number,
red?: number,
green?: number,
blue?: number,
white?: number,
gain?: number,
brightness?: number,
effect?: number,
temp?: number,
}
}
  • All params but id, the channel to setup, are optional. The channel number starts from 0 and must be present even for single channel devices with value 0
  • Not all models support all parameters. Take a look at each device local control description to deduce what is possible for what model
  • turn is on, off or toggle
  • gain, brightness are numbers in rage 0 to 100, 100 indicating "full power"
  • effect is number in rage 0 to 6, model specific
  • all colors are in rage 0 to 255 to represent the usual RGB colorspace
  • temp is light temperature in Kelvins

roller

The command starts a roller/cover direction or stops it

{
cmd: 'roller',
params: {
go: string,
duration:number,
id:number
}
}
  • go is up, down or stop
  • id identifies the roller/cover channel to change, this starts from 0. It must be present even for single channel roller/cover with value 0
  • duration is optional, in seconds and works only for go=up and go=down

roller_to_pos

The command sends a roller/cover to specific position.

{
cmd: 'roller_to_pos',
params: {
pos: number,
id:number
}
}
  • The roller/cover must be calibrated for this command to be accepted by the device
  • pos is the position expressed in percent in range 0 to 100
  • id identifies the roller/cover channel to change, this starts from 0. It must be present even for single channel roller/cover with value 0