Leviton Decora Smart Wi-Fi (My Leviton)
GEM driver for Leviton Decora Smart Wi-Fi switches, dimmers, and plug-in modules via the MyLeviton (my.leviton.com) cloud REST API. Covers the first-generation Decora Smart Wi-Fi line — DW15S, DW15P, DW6HD, D215S, D26HD, D215P, and the outdoor DW15R plug — plus the same generation's fan controllers.
Feature coverage:
- Discovery of every switch on the account (
get_devices) - On / Off / Toggle
set_levelfor dimmers (0-100, with automatic off at 0)- Periodic polling → mirrors
poweronto the zonestateattribute andbrightnessonto the zonelevel - Optional pin to a specific residence when the account owns more than one home
- Raw discovery of residences (
get_residences) for multi-home accounts
Second-generation "Decora Smart WiFi-2 / HomeKit" devices use a different platform and are NOT covered here. If the device shows up under "Leviton Homekit" or "Decora Smart" (rather than "My Leviton") in the mobile app switcher, this driver won't reach it.
Prerequisites
- MyLeviton account. The account must be fully verified (email confirmation completed) and hold the target switches. If you can toggle the switches from the MyLeviton mobile app in "My Leviton" mode, the account is good.
- Two-factor authentication OFF. The driver does not solve TOTP prompts inline. Either disable 2FA on the MyLeviton account, or run a small helper that mints a session token by another means and bypass the login step.
- A dedicated automation account is a good idea. Reset the password on a dedicated MyLeviton account and share the residence to it, rather than embedding a homeowner's personal credentials in a controller.
Setup steps
- Enroll switches in MyLeviton. Pair each switch through the MyLeviton mobile app, verify the app can toggle it, and note whether the switch is a dimmer (supports brightness) or a plain on/off model.
- Add the device in GEM. In Devices, create a new device with driver
leviton_decora. Fill in:email— the MyLeviton account emailpassword— the MyLeviton account password (stored encrypted)- Leave
api_baseon the default. Only setresidence_idif the account has multiple residences and you want to pin the driver to one.
- Discover switches. Open the device's Script Console tab and run:
The response is an array of iotSwitches. Note the numericawait gem.command({device: <device_id>, action: 'get_devices'})
id,name,power, andcanSetLevelfor each —canSetLevel: truemeans it's a dimmer. - Create GEM zones. For each switch you want to control from GEM, create a zone under a lighting subsystem. Set
zone.addressto the numeric iotSwitchid. Set the zone type to a dimmer forcanSetLevel: truedevices; on/off otherwise. - Verify. From the Script Console, send
on,off, and (for dimmers)set_levelwith a value like 50. Confirm the switch responds and that the zonestate/levelattributes track the physical state within a poll cycle (default 30s).
Attributes
Device — required
| Name | Type | Description |
|---|---|---|
email | string | MyLeviton account email. Same address used to sign into the mobile app. |
password | string (secure) | MyLeviton account password. Stored encrypted; used only to mint session tokens against my.leviton.com. |
Device — optional
| Name | Type | Default | Description |
|---|---|---|---|
api_base | string | https://my.leviton.com/api | MyLeviton API endpoint. Only change if Leviton publishes a region-specific or partner endpoint. |
residence_id | string | — | When the account has multiple residences, pin the driver to one by setting the residence id (from get_residences). Leave blank to include all residences the account can see. |
status_interval | int (ms) | 30000 | How often to refresh switch state from the cloud. Minimum 10s — Leviton throttles below that. |
request_timeout | int (ms) | 15000 | HTTP request timeout per call. |
Zone — address
| Field | Description |
|---|---|
address | The MyLeviton iotSwitch numeric id (e.g. 1234567). Discover via get_devices. |
Commands
| Name | Args | Description |
|---|---|---|
on | address | Turn the switch on. For dimmers, the cloud restores the last-set brightness. |
off | address | Turn the switch off. |
set_level | address, level | Set brightness for a dimmer (0-100). Auto-off when level=0, auto-on when level>0. Rejected for non-dimmers based on the cached canSetLevel flag. |
toggle | address | Toggle the switch based on last-known cached state (falls back to a cloud fetch if the zone isn't cached). |
get_status | address | Return the current power / brightness / model for one switch. |
get_devices | — | List every iotSwitch across the residences the account can see. |
get_residences | — | List residences visible to the account. Use to populate residence_id. |
refresh_token | — | Force a fresh MyLeviton login now. Rarely needed — the driver re-authenticates automatically on 401 responses. |
Auth model
The MyLeviton API is opaque-token based:
POST /api/Person/loginwith{email, password, clientId}.- Response includes
id(the session token) anduserId. - Subsequent calls pass the raw token in an
Authorizationheader — noBearerprefix. This matches the MyLeviton mobile app. GET /api/Person/{userId}/residentialPermissionsyields one or moreresidentialAccountIds.GET /api/ResidentialAccounts/{accountId}/Residencesyields residence ids.GET /api/Residences/{residenceId}/iotSwitchesyields the switch list.PUT /api/IotSwitches/{id}with{power, brightness}sends control commands.
Session tokens are long-lived (weeks) but do eventually expire; the driver re-logs in transparently on 401.
Known limitations
- Cloud-only. Leviton does not expose a documented local API for the Decora Smart Wi-Fi (My Leviton) generation. All commands round-trip through the MyLeviton cloud. When the internet or the cloud is down, the switches keep working locally but GEM cannot control them.
- No push channel. MyLeviton has no third-party WebSocket or webhook. Manual state changes at the switch face show up on the next poll (default 30s).
- No scene / group control. MyLeviton scenes are visible on the account but not exposed as a first-class verb here. Use
raw_geton/Residences/{id}/scenesfor advanced access. - No color / colour temperature. The first-generation platform is white-only (or on/off). Newer color-tunable Decora products are on a different platform.
- 2FA-enabled accounts are not supported inline. Bootstrap a session token separately if the account requires TOTP on login.
- Undocumented rate limits. Poll intervals below 10s frequently produce 429 responses.
Troubleshooting
| Symptom | Check |
|---|---|
401 on every request immediately after connect | Verify the email/password against the MyLeviton mobile app. If 2FA is enabled, disable it or bootstrap a token by another means. |
get_devices returns an empty list | The account may have zero switches enrolled on the primary residence. If the account has multiple homes, set residence_id, or first run get_residences to enumerate. |
set_level returns device does not support brightness | The switch model is not a dimmer (e.g. DW15S, DW15P). Use on/off instead. |
429 Too Many Requests | Raise status_interval. MyLeviton silently throttles chatty accounts. |
| Command completes but the switch does not react | The switch may be offline (Wi-Fi drop, breaker off). The cloud accepts the command and returns 200 even if the device can't execute it. Confirm the switch is online in the mobile app. |
Brightness returns to a previous value after on | Expected. The cloud restores the last non-zero brightness when transitioning OFF → ON. Send set_level explicitly if you want a different value. |
Related
tplink_kasa.js— TP-Link Kasa smart switches / plugs (local + cloud).wled.js— LED strip controllers (local HTTP).philips_hue.js— Hue bridge (local REST + CLIP v2).insteon_hub.js— Insteon Hub legacy HTTP.lutron_caseta.js— Lutron Caseta on the Smart Bridge (LEAP).