I’m looking into this a bit. So far, I’ve found out that it’s actually provided by OJ Electronics. They have their own branded thermostat, an OJ Microline UWG4 too. AFAICT, there is no local access - you have to go through their cloud API. For Ditra devices, this is located at https://ditra-heat-e-wifi.schluter.com/. For OJ, it’s at https://mythermostat.info/. Interestingly, both domains are hosted by the same server (point to the same IP address), and the Ditra iOS app also uses https://mythermostat.info/. They do have separate user databases though, and you specify which one when making the authentication call:
POST /api/authenticate/user
Takes a JSON object POST body with fields for Email, Password, and Application. Application is 0 for OJ’s web version, 7 for Ditra’s web version, or 8 for Ditra’s iOS App (my same password works across iOS and web, but not OJ’s web. I didn’t bother installing the OJ app and capturing its traffic. presumably it sends 1. Presumably there are other brands that are the same hardware and cloud service, too).
Response is a JSON object, with fields SessionId, NewAccount, ErrorCode, RoleType, Email, and Language. SessionId is probably all we’re interested in. It’s some sort of session identifier. I’m still trying to determine the longevity of this ID, but it’s definitely not cookie based session. This token is all you need to make further requests.
GET /api/useraccount?sessionid=
Response is a JSON object with fields (strings unless otherwise indicated) City, Country, DistributerId (integer; mine is 0), Email, FirstName, Inactive (boolean), Language, LastName, MondayFirstDayOfWeek (boolean), Password (null, thankfully), RoleType (integer, matches the auth response), SafetyAnswer, SafetyQuestion, StateProvince (empty string for me), StreetName (empty string for me), TempUnitIsCelsius (boolean), Use12Hour (boolean), ZipCode (empty string for me)
GET /api/thermostats?sessionid=
Response is a JSON Object with a single field, Groups. Groups is an array of JSON objects with AwayMode (boolean), GroupId (integer), GroupName, and Thermostats. Thermostats is an array of JSON objects. Each object is the same as an individual thermostat call; see below
GET /api/unassigned_thermostats?sessionid=
Response is a JSON object with a single field, Thermostats, which is an array. Presumably of the same Thermostat object as /thermostats and /thermostat return. Mine is empty.
GET /api/thermostat?sessionid=&serialnumber=<serialnumber; get it from the thermostats call>
Response is a JSON object. I’ll just paste mine in slightly redacted for simplicity.
{
"SerialNumber": "<redacted>",
"Room": "master tile",
"GroupName": "",
"GroupId": -1,
"Temperature": 2474, # looks to be Celsius multiplied by 100, even when user account is set to Fahrenheit
"SetPointTemp": 2444,
"RegulationMode": 1, # 1 is on schedule, 2 is "for a few hours", 3 is "permanently", 4 is "for a few days"
"VacationEnabled": false,
"VacationBeginDay": "31/05/2019 00:00:00",
"VacationEndDay": "08/06/2019 00:00:00",
"VacationTemperature": 878,
"ComfortTemperature": 2388,
"ComfortEndTime": "08/06/2019 02:15:00 +00:00",
"ManualTemperature": 2166,
"LastPrimaryModeIsAuto": true,
"Online": true,
"Heating": false,
"EarlyStartOfHeating": false,
"MaxTemp": 4000,
"MinTemp": 500,
"ErrorCode": 0,
"Confirmed": true,
"Email": "<redacted>",
"TZOffset": "-06:00",
"Assigned": true,
"KwhCharge": 0.11,
"LoadMeasuringActive": true,
"LoadManuallySetWatt": 100,
"LoadMeasuredWatt": 892,
"SWVersion": "1012P108",
"HasBeenAssigned": true,
"DistributerId": 10207,
"Schedules": []
"Support": "Customer Service\r\nUSA: 1-800-472-4588\r\nCanada: 1-800-667-8746\r\ninfo@schluter.com\r\n"
}
Schedules is an array of 7 JSON objects, with fields WeekDayGrpNo (one based), and Events, another array of 6 JSON objects that look like:
{
"ScheduleType": 0,
"Clock": "07:00:00",
"TempFloor": 2555,
"Active": true
}
GET /api/newthermostat?sessionid=
Presumably a long-poll to get notified if a thermostat is added. The only thing I ever see is:
{
"SequenceNr": 0,
"Action": 0,
"Thermostat": null
}
GET /api/notification?sessionid=&sequencenr=0
A long-poll to get notified of changes from another API user or the thermostat itself. A basic response looks the same as /newthermostat. Other responses return a full thermostat object with an Action of 2, and a SequenceNr that seems to be otherwise discarded.
POST /api/thermostat?sessionid=&serialnumber=
Request body is a JSON thermostat object that contains only the keys you want to change. Response is: { "Success": true }
. In particular, “Return to Schedule” button sends RegulationMode: 1, VacationEnabled: false. Manually adjusting “for a few hours” sends ComfortTemperature, ComfortEndTime, RegulationMode: 2, and VacationEnabled: false. Manually adjusting “for a few days” sends RegulationMode: 4, VacationBeginDay: “07/06/2019 00:00:00”, VacationEnabled: true, VacationEndDay: <same above, but a different day>, VacationTemperature. Manually adjusting “permanently” sends ManualTemperature, RegulationMode: 3, and VacationEnabled: false. I haven’t bothered messing with anything else like schedules, but presumably it matches the thermostat response, and you just send what you want to change.