I wanted to control the hot water production on my Thermia Diplomat Inverter M heat pump from Homey Pro, but there was no official Homey app available. After some digging through the Thermia Online web app using browser DevTools, I managed to reverse-engineer the API and build a working HomeyScript integration. Sharing it here in case others find it useful.
Tested on:
-
Homey Pro 2026
-
Thermia Diplomat Inverter M [230]
-
Thermia Online account (online.thermia.se)
What it does
-
Turns hot water production ON or OFF via Homey Flows
-
Reads temperatures from the heat pump every 10 minutes (outdoor, supply line, brine in, hot water)
-
Stores temperatures as Homey Logic variables — visible in Insights with graphs over time
-
Auto-renews the authentication token so it never expires
-
Sends a push notification if authentication fails
My setup turns hot water OFF at 06:00 and ON again at 22:00 to avoid peak electricity hours, with a safety flow that turns it back on if the water temperature drops below 50°C during the day.
Prerequisites
-
HomeyScript app installed on your Homey
-
A Thermia Online account at online.thermia.se
-
A PC/Mac browser with DevTools (Chrome or Edge recommended)
-
Basic familiarity with Homey Flows
Step 1 — Get your refresh token
The Thermia API uses token-based authentication. You need to get an initial refresh token from your browser:
-
Go to online.thermia.se and log in
-
Open DevTools (F12) → Network tab
-
Refresh the page
-
Find the request named token in the list
-
Click it → Response tab
-
Copy the value of the
refresh_tokenfield — it is a long ~1400 character string with 5 dot-separated segments starting witheyJ
Do not copy the access_token or id_token — these are shorter 3-part strings and will not work.
Step 2 — Find your installation ID
-
In DevTools with the Network tab still open, look for requests with URLs containing
/installationstatus/or/Registers/Installations/ -
Your installation ID is the number in those URLs — for example
/Registers/Installations/XXXXXXX/Groups/ -
Note this number — you will need to replace
XXXXXXXwith it in the scripts below
Step 3 — Create Homey Logic variables
Create these variables in Homey → Logic → Variables:
| Name | Type |
|---|---|
thermia_refresh_token |
Text |
thermia_hotwater_command |
Text |
thermia_hotwater_status |
Text |
thermia_temp_hot_water |
Number |
thermia_temp_outdoor |
Number |
thermia_temp_supply |
Number |
thermia_temp_brine_in |
Number |
Step 4 — Store the refresh token
Create a new HomeyScript called thermia_set_token and run it once with your token pasted in:
javascript
const newRefreshToken = "PASTE_YOUR_REFRESH_TOKEN_HERE";
const allVars = Object.values(await Homey.logic.getVariables());
const refreshVar = allVars.find(v => v.name === "thermia_refresh_token");
await Homey.logic.updateVariable({
id: refreshVar.id,
variable: { value: newRefreshToken }
});
log("Stored length: " + newRefreshToken.length);
log("Segments: " + newRefreshToken.split(".").length);
log("✅ Done — expect length ~1400 and segments 5");
Step 5 — The scripts
Replace XXXXXXX with your own installation ID in both scripts before running them.
Script 1: thermia_read_temperatures
javascript
const token_var = await Homey.logic.getVariables();
const allVars = Object.values(token_var);
const refreshVar = allVars.find(v => v.name === "thermia_refresh_token");
const loginBody = new URLSearchParams();
loginBody.append("grant_type", "refresh_token");
loginBody.append("client_id", "09ea4903-9e95-45fe-ae1f-e3b7d32fa385");
loginBody.append("refresh_token", refreshVar.value.trim());
loginBody.append("redirect_uri", "https://www.online.thermia.se/login");
const loginRaw = await fetch("https://thermialogin.b2clogin.com/thermialogin.onmicrosoft.com/b2c_1a_signuporsigninonline/oauth2/v2.0/token", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: loginBody
});
const loginData = JSON.parse(await loginRaw.text());
if (!loginData.access_token) {
log("❌ Token refresh failed - paste a new refresh_token into Homey Logic");
log(JSON.stringify(loginData));
await Homey.notifications.createNotification({
excerpt: "⚠️ Thermia token expired — paste a new refresh_token into Homey Logic"
});
return;
}
if (loginData.refresh_token) {
await Homey.logic.updateVariable({
id: refreshVar.id,
variable: { value: loginData.refresh_token }
});
log("✅ Refresh token renewed (" + loginData.refresh_token.length + " chars)");
}
const BASE = "https://online-classic-serviceapi.azurewebsites.net/api/v1";
const INSTALL = "XXXXXXX"; // ← Replace with your installation ID
const headers = { "Authorization": "Bearer " + loginData.access_token, "Accept": "application/json" };
const res = await fetch(BASE + "/Registers/Installations/" + INSTALL + "/Groups/REG_GROUP_TEMPERATURES", { headers });
const data = JSON.parse(await res.text());
const mapping = {
"REG_HOT_WATER_TEMPERATURE": "thermia_temp_hot_water",
"REG_OUTDOOR_TEMPERATURE": "thermia_temp_outdoor",
"REG_SUPPLY_LINE": "thermia_temp_supply",
"REG_BRINE_IN": "thermia_temp_brine_in"
};
for (const reg of data) {
const varName = mapping[reg.registerName];
if (!varName) continue;
const homeyVar = allVars.find(v => v.name === varName);
if (!homeyVar) { log("⚠️ Variable not found: " + varName); continue; }
await Homey.logic.updateVariable({ id: homeyVar.id, variable: { value: reg.registerValue } });
log(varName + " = " + reg.registerValue + "°C");
}
Script 2: thermia_hotwater
javascript
// ── CONFIGURATION ───────────────────────────────────────────────
const CLIENT_ID = "09ea4903-9e95-45fe-ae1f-e3b7d32fa385";
const TOKEN_URL = "https://thermialogin.b2clogin.com/thermialogin.onmicrosoft.com/b2c_1a_signuporsigninonline/oauth2/v2.0/token";
const REDIRECT = "https://www.online.thermia.se/login";
const BASE = "https://online-classic-serviceapi.azurewebsites.net/api/v1";
const INSTALL = "XXXXXXX"; // ← Replace with your installation ID
const REG_SPEC_ID = 29442; // Hot water on/off register — may differ on other models
// ── STEP 1: READ HOMEY LOGIC VARIABLES ──────────────────────────
const allVars = Object.values(await Homey.logic.getVariables());
const refreshVar = allVars.find(v => v.name === "thermia_refresh_token");
const commandVar = allVars.find(v => v.name === "thermia_hotwater_command");
const statusVar = allVars.find(v => v.name === "thermia_hotwater_status");
if (!refreshVar) { log("❌ Missing variable: thermia_refresh_token"); return; }
if (!commandVar) { log("❌ Missing variable: thermia_hotwater_command"); return; }
const command = (commandVar.value || "").trim().toLowerCase();
log("Command: " + command);
// ── STEP 2: AUTHENTICATE WITH THERMIA ───────────────────────────
const loginBody = new URLSearchParams();
loginBody.append("grant_type", "refresh_token");
loginBody.append("client_id", CLIENT_ID);
loginBody.append("refresh_token", refreshVar.value.trim());
loginBody.append("redirect_uri", REDIRECT);
const loginRaw = await fetch(TOKEN_URL, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: loginBody
});
const loginData = JSON.parse(await loginRaw.text());
if (!loginData.access_token) {
log("❌ Token refresh failed - paste a new refresh_token into Homey Logic");
log(JSON.stringify(loginData));
await Homey.notifications.createNotification({
excerpt: "⚠️ Thermia token expired — paste a new refresh_token into Homey Logic"
});
return;
}
if (loginData.refresh_token) {
await Homey.logic.updateVariable({
id: refreshVar.id,
variable: { value: loginData.refresh_token }
});
log("✅ Refresh token renewed (" + loginData.refresh_token.length + " chars)");
}
const headers = {
"Authorization": "Bearer " + loginData.access_token,
"Accept": "application/json",
"Content-Type": "application/json"
};
// ── STEP 3: READ CURRENT HOT WATER STATE ────────────────────────
const getRes = await fetch(BASE + "/Registers/Installations/" + INSTALL + "/Groups/REG_GROUP_HOT_WATER", { headers });
const getData = JSON.parse(await getRes.text());
const currentValue = getData[0].registerValue;
log("Current: " + (currentValue === 1 ? "ON" : "OFF"));
// ── STEP 4: HANDLE "STATUS" COMMAND ─────────────────────────────
if (command === "status") {
const s = currentValue === 1 ? "on" : "off";
if (statusVar) await Homey.logic.updateVariable({ id: statusVar.id, variable: { value: s } });
log("Status: " + s);
return s;
}
// ── STEP 5: VALIDATE THE COMMAND ────────────────────────────────
const targetValue = command === "on" ? 1 : command === "off" ? 0 : -1;
if (targetValue === -1) {
log("❌ Unknown command: " + command + " (use on/off/status)");
return;
}
// ── STEP 6: SKIP IF ALREADY IN TARGET STATE ──────────────────────
if (currentValue === targetValue) {
log("Already " + command.toUpperCase() + " — no change needed");
return command;
}
// ── STEP 7: SEND THE COMMAND TO THE HEAT PUMP ───────────────────
const uuid = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, c => {
const r = Math.random() * 16 | 0;
return (c === "x" ? r : (r & 0x3 | 0x8)).toString(16);
});
const postRes = await fetch(BASE + "/Registers/Installations/" + INSTALL + "/Registers", {
method: "POST",
headers,
body: JSON.stringify({
registerSpecificationId: REG_SPEC_ID,
registerValue: targetValue,
clientUuid: uuid
})
});
log("Set " + command.toUpperCase() + ": HTTP " + postRes.status);
// ── STEP 8: SAVE THE NEW STATE TO HOMEY ─────────────────────────
if (statusVar) {
await Homey.logic.updateVariable({ id: statusVar.id, variable: { value: command } });
}
return command;
Step 6 — Set up Flows
Temperature polling (every 10 minutes):
-
WHEN: Every 10 minutes
-
THEN: Run script
thermia_read_temperatures
Hot water OFF at 06:00:
-
WHEN: Time is 06:00
-
THEN: Set
thermia_hotwater_commandtooff -
THEN: Run script
thermia_hotwater
Hot water ON at 22:00:
-
WHEN: Time is 22:00
-
THEN: Set
thermia_hotwater_commandtoon -
THEN: Run script
thermia_hotwater
Optional — Safety rescue if water gets too cold:
-
WHEN: Every 10 minutes
-
AND: Time is between 06:01 and 21:59
-
AND:
thermia_temp_hot_wateris less than 50 -
THEN: Set
thermia_hotwater_commandtoon -
THEN: Run script
thermia_hotwater
Notes
-
The
client_idin the scripts is the public Thermia Online app ID — it is the same for all users and not a secret -
The
REG_SPEC_ID(29442) is the register ID for hot water on/off on the Diplomat Inverter M — it may be different on other Thermia models. If it doesn’t work, check the DevTools network traffic when toggling hot water in the Thermia web app and look for theregisterSpecificationIdvalue in the POST request payload -
The refresh token auto-renews on every script run, so you should never need to paste a new one manually as long as the polling flow runs at least once a day
-
Temperature graphs appear automatically in Homey Insights once the polling flow has been running for a while