Ecobee Thermostat (Cloud)
GEM controls Ecobee smart thermostats through Ecobee's cloud REST API at api.ecobee.com. One GEM device represents one Ecobee homeowner account; every thermostat registered to that account becomes a Climate zone underneath it. The link is established once, through Ecobee's PIN authorization, after which GEM keeps itself signed in automatically — there is nothing to re-enter on a server restart unless the pairing is revoked.
This is a cloud integration: there is no LAN/local path. The GEM server needs outbound internet access to reach Ecobee.
What's controlled
- Per-thermostat heat and cool setpoints, with an automatic gap (deadband) kept between them.
- System mode —
auto/cool/heat/off. - Fan mode —
auto/on. - Live feedback published back onto each zone: current temperature, relative humidity, the active heat/cool setpoints, the running system and fan mode, and the thermostat's online/offline state.
Prerequisites
- An Ecobee homeowner account with at least one thermostat already commissioned in the Ecobee app (if it shows up in the app, GEM can reach it).
- A developer API key. Register a developer application at
https://www.ecobee.com/developersand set its Authorization Method to ecobee PIN. The application's API key is what you paste into GEM. - Access to the homeowner account during setup, to type the pairing PIN into Ecobee's web portal.
- Outbound HTTPS (port 443) from the GEM server to
api.ecobee.com.
Setup
1. Add the device
- Open Devices and add a new device of type Ecobee Thermostat.
- In the API Key field, paste the developer API key from your Ecobee developer application. This is stored encrypted.
- Save the device.
2. Pair with the homeowner account (one time)
The first link uses Ecobee's PIN flow. You start it from GEM, then approve it in Ecobee's web portal.
- Run the
authorizecommand against the device — from the device's command list, the Commands screen, or the Script Console. - GEM generates a pairing PIN and shows it to you. The same value also lands in the device's read-only Authorization PIN attribute and is emailed to the site's mail recipient.
- Within about three minutes, sign in to the Ecobee web portal, open My Apps → Add Application, enter the PIN, and confirm/add the application.
- GEM polls Ecobee every few seconds in the background. As soon as you approve the PIN, it
fills in the read-only Access Token and Refresh Token attributes and begins
syncing. The PIN is single-use — if you miss the three-minute window, run
authorizeagain for a fresh one.
After the first successful pairing, GEM only needs the Refresh Token to reconnect. You do
not run authorize again on reboots or reloads. Re-authorize only if you removed GEM's
application from the Ecobee account, or re-paired the same account elsewhere (which revokes
the old refresh token).
3. Create one zone per thermostat
Zones are not created automatically — you add one Climate zone for each thermostat.
- Run
get_thermostatsagainst the device to list everything on the account. The response is keyed by both the thermostat's identifier and its name. - Open Zones and create a zone under the Climate subsystem for each thermostat.
- Set the zone Address to that thermostat's identifier or its name exactly as
it appears in the
get_thermostatsoutput. Either works; the name must match character-for-character.
Once a zone's address matches, GEM starts populating its temperature, humidity, setpoints, mode and fan attributes on the next poll (within ~3 minutes), and the standard climate controls light up in the UI.
Attribute reference
Device attributes
| Attribute | Required | Type | Default | Notes |
|---|---|---|---|---|
api_key | yes | string (secure) | — | Developer API key from the Ecobee developer portal. |
pin | auto | string (read-only) | — | Pairing PIN produced by authorize; you type this into Ecobee's web portal. |
token | auto | string (secure, read-only) | — | OAuth access token. Filled in and rotated automatically. |
refresh_token | auto | string (secure, read-only) | — | OAuth refresh token. This is what lets GEM reconnect without re-pairing — do not clear it. |
hold_type | no | string | indefinite | How long a setpoint hold lasts. One of indefinite, nextTransition, dateTime, holdHours. |
Zone attributes
| Attribute | Required | Type | Notes |
|---|---|---|---|
address | yes | string | Thermostat identifier or name on the Ecobee account. Run get_thermostats to list valid values. |
deadband | no | int (°F) | Minimum gap between the heat and cool setpoints. The opposite setpoint is pushed out of the way only when a new setpoint would come within this many degrees of it. If unset, the Climate subsystem's deadband default is used. |
locked_deadband | no | int (°F) | Keeps the two setpoints exactly this far apart — setting one always moves the other to match. Takes precedence over deadband. This is the same rule the Climate (Locked Deadband) zone control uses. |
The driver keeps the following feedback attributes in sync on each zone automatically — you do
not set them by hand: temperature, humidity, heat_setpoint, cool_setpoint,
system_mode, fan_mode, and the connected (online) flag. It also publishes
system_modes (auto/cool/heat/off) and fan_modes (auto/on) so the admin UI
renders the correct pickers.
All temperatures and setpoints are whole degrees Fahrenheit — Ecobee's API is Fahrenheit-based and the driver does not convert to Celsius.
Commands
These target a Climate zone; GEM supplies the thermostat address from the zone, so in a macro you only provide the values below.
| Command | Args | Notes |
|---|---|---|
authorize | — | Starts the one-time PIN pairing and returns the PIN to enter at Ecobee's web portal. |
setpoint | address, mode (heat/cool), setpoint | Applies a hold at the target temperature (whole °F). GEM sends both heat and cool setpoints — the opposite one follows the deadband rules below — and the hold lasts according to hold_type. |
system_mode | address, mode (auto/cool/heat/off) | Sets the thermostat's HVAC mode. |
fan_mode | address, mode (auto/on) | Sets the fan mode. Sent as a hold carrying the current setpoints — Ecobee has no standalone fan command. |
get_thermostats | — | Lists every thermostat on the account; use it to find a zone's Address. |
get_system_mode | address | Returns {system_mode} from the last poll (at most three minutes old) and corrects the zone attribute if it has drifted. |
address is the thermostat identifier or name. You do not normally supply it: when the
command targets a zone, GEM fills it in from that zone's Address.
How control works
Setpoints and the deadband. Setpoints are applied as a single Ecobee hold carrying both a heat and a cool temperature, so GEM always has to decide what the other side of the pair should be. Two rules, and the zone chooses:
deadband(or the Climate subsystem default) is a minimum gap. The opposite setpoint is left exactly where it is unless the new setpoint would come within the deadband of it — then it is pushed out of the way. With a 4° deadband and heat at 65, setting cool to 76 leaves heat at 65; setting cool to 67 pushes heat down to 63.locked_deadbandwelds the two together at that distance. Setting either one always moves the other to match — with a 4° locked deadband, cool 76 puts heat at 72 regardless of where it was. Use this where the homeowner should only ever move a single dial.
A refused change is not recorded. Ecobee reports a rejection as an HTTP 200 carrying a non-zero status code, so a setpoint, mode or fan change that the cloud declines returns an error and leaves the zone attributes showing what the thermostat is actually doing.
Hold duration. Every setpoint change is sent as a hold whose length is the device's
hold_type:
indefinite(default) — holds until you clear it on the thermostat or send a new value.nextTransition— holds until the thermostat's next scheduled program change, then resumes the schedule.dateTime/holdHours— Ecobee's timed-hold modes.
If a setpoint "takes" and then reverts a while later, the thermostat's schedule resumed —
switch hold_type to indefinite to make manual changes stick.
Polling and tokens. GEM refreshes thermostat state about every 3 minutes, so feedback (temperature, humidity, mode, setpoints) reflects the thermostat within that window. The access token is renewed roughly every 50 minutes using the stored refresh token; this is automatic and invisible to the integrator.
Known limitations
- Cloud only. No local/LAN control — the integration depends on internet reachability to
api.ecobee.com. - Holds, not schedules. GEM applies temporary holds; it does not edit the thermostat's comfort schedules, comfort profiles (Home/Away/Sleep), or vacation events.
- Fan is
auto/on. Ecobee's API has no fan speed; fan mode is a property of a hold, sofan_modeplaces one carrying the thermostat's current heat/cool setpoints. - No remote-sensor or equipment-runtime detail. Per-room occupancy sensors and stage/equipment runtime data are not surfaced as attributes.
- Fahrenheit only. Setpoints and readings are whole °F; there is no Celsius mode.
get_system_modereads the cache, not the thermostat — it answers from the last three-minute poll rather than issuing a cloud round-trip per call.
Troubleshooting
| Symptom | Check |
|---|---|
authorize returns no PIN | The API Key must belong to a developer application whose authorization method is ecobee PIN. Re-check the key and the app's setting in the Ecobee developer portal. |
| PIN entered but nothing links | The PIN expires about three minutes after authorize. Run authorize again and enter the fresh PIN promptly. |
unmatched ecobee thermostat address in the device log | A zone's Address doesn't equal any thermostat identifier or name on the account. Run get_thermostats and copy an exact value. |
| Zone shows disconnected | The thermostat is offline to Ecobee's cloud (no Wi-Fi or power). GEM mirrors the thermostat's online state; restore the thermostat's connection. |
| Device shows disconnected | The account link is the device's connection: it goes offline when there is no API key, when the pairing has never completed or has been revoked, and when the thermostat list cannot be read (no internet, Ecobee outage, expired token). Check the device log for the reason, then re-run authorize if the pairing is gone. |
| A setpoint or mode change returns an error | Ecobee refused it. The message carries Ecobee's own status code — 14 is an expired/invalid token (re-authorize), 3 is a rejected request. The zone is deliberately left showing what the thermostat is really doing. |
| Setpoint accepted but reverts later | A thermostat schedule resumed. Set hold_type to indefinite, or place the hold on the thermostat itself. |
| Feedback stopped updating after about an hour | Token refresh failed — usually because the refresh token was revoked (the account was re-paired elsewhere, or GEM's app was removed). Reload the device; if it still can't refresh, re-run authorize. |
See also
- Zones — create one Climate zone per thermostat; the zone Address is the thermostat identifier or name.
- Subsystems — set the Climate subsystem's deadband default used when a zone has none.
- Devices — where the Ecobee account device is added and
authorizeis run. - Commands — where
setpoint,system_mode, andfan_modeare dispatched and wired into macros.