Python library for controlling TP-Link Kasa and Tapo smart home devices via the TP-Link Cloud API
Project description
tplink-cloud-api
Control TP-Link Kasa and Tapo smart home devices from anywhere over the internet using TP-Link's cloud API — no local network access required.
Why cloud control?
Most TP-Link/Kasa Python libraries (like python-kasa) communicate with devices over your local network. That works great when your code runs at home, but not when it runs somewhere else.
This library uses the TP-Link Cloud API instead, so your code can control devices from anywhere with an internet connection. That makes it the right choice for:
- Server-side automations — cloud functions, cron jobs, CI pipelines
- Web service integrations — IFTTT, Thinger.io, webhooks
- Mobile scripting — Tasker (Android), Shortcuts (iOS)
- Remote access — control devices while you're away from home
If you just need local control on the same network as your devices, python-kasa is a great option with broader device support.
How it works
The library authenticates with your TP-Link / Kasa account credentials using the V2 TP-Link Cloud API with HMAC-SHA1 request signing. It automatically connects to both the Kasa and Tapo clouds in parallel, returning a unified device list. It supports MFA (two-factor authentication) and automatic refresh token management.
Originally a Python port of Adumont's Node.js module.
Device Compatibility
The following devices are officially supported by the library at this time:
Kasa Devices
Smart Plugs
- HS100 (Smart Plug - Blocks two outlets as a single outlet)
- HS103 (Smart Plug Lite - 12 Amp)
- HS105 (Smart Plug Mini - 15 Amp)
- HS110 (Smart Plug with Energy Monitoring)
- KP115 (Smart Plug with Energy Monitoring - 15 Amp; replacement for HS110)
- KP125 (Smart Plug Mini with Energy Monitoring)
- EP40 (Outdoor Smart Plug)
Smart Switches
- HS200 (Smart Light Switch)
Smart Power Strips
- HS300 (Smart Plug Power Strip with 6 Smart Outlets)
- KP303 (Smart Plug Power Strip with 3 Outlets)
Smart Outdoor Plugs (Multi-Outlet)
- KP200 (Smart Outdoor Plug with 2 Outlets)
- KP400 (Smart Outdoor Plug with 2 Outlets)
Smart Light Strips
- KL420L5 (Smart LED Light Strip)
- KL430 (Smart Light Strip, Multicolor)
Tapo Devices
Tapo devices are supported through the Tapo cloud API. The library automatically authenticates against both clouds and returns a unified device list. All Tapo devices support basic on/off control through the generic TPLinkDevice class:
- P100 (Mini Smart Wi-Fi Plug)
- P110 (Mini Smart Wi-Fi Plug with Energy Monitoring)
- L530 (Smart Wi-Fi Light Bulb, Multicolor)
- Any other Tapo device registered to your TP-Link account
Each device has a cloud_type attribute ("kasa" or "tapo") so you can identify which cloud it belongs to.
Devices not explicitly listed above will still work with basic on/off functionality through the generic TPLinkDevice class.
Requirements
- Python 3.10+
Installation
The package is available via PyPi and can be installed with the following command:
pip3 install tplink-cloud-api
To install it from the repo, clone the repo and cd into the directory:
git clone https://github.com/piekstra/tplink-cloud-api.git
cd tplink-cloud-api
You can install this library with pip:
pip3 install .
Usage
Authenticate
Instantiating a TPLinkDeviceManager automatically logs in with your TP-Link / Kasa credentials using the V2 API, caches the login token, and fetches your devices.
from tplinkcloud import TPLinkDeviceManager
username = 'kasa@email.com'
password = 'secure'
device_manager = TPLinkDeviceManager(username, password)
Note that the device manager can also be constructed using
awaitif desired and running in anasynccontext
MFA (Two-Factor Authentication)
If your TP-Link account has two-factor authentication enabled, you can provide an mfa_callback function that will be called when MFA verification is needed:
def handle_mfa(mfa_type, email):
"""Called when MFA is required. Returns the verification code."""
return input(f'Enter the MFA code sent to {email}: ')
device_manager = TPLinkDeviceManager(
username='kasa@email.com',
password='secure',
mfa_callback=handle_mfa,
)
Token Management
The library automatically handles refresh tokens. You can also manually manage tokens for session persistence:
# Get tokens for later use
token = device_manager.get_token()
refresh_token = device_manager.get_refresh_token()
# Resume a session without re-authenticating
device_manager = TPLinkDeviceManager(prefetch=False)
device_manager.set_auth_token(token)
device_manager.set_refresh_token(refresh_token)
Error Handling
The library provides specific exception classes for common error scenarios:
from tplinkcloud import (
TPLinkDeviceManager,
TPLinkAuthError,
TPLinkMFARequiredError,
TPLinkTokenExpiredError,
TPLinkCloudError,
)
try:
device_manager = TPLinkDeviceManager(username, password)
except TPLinkAuthError:
print('Wrong username or password')
except TPLinkMFARequiredError as e:
print(f'MFA required (type: {e.mfa_type}), provide an mfa_callback')
except TPLinkCloudError as e:
print(f'API error: {e} (code: {e.error_code})')
Async Context
In order to run the async methods, you will need an async context. For a simple Python script, you can simply use the following:
import asyncio
asyncio.run(example_async_method())
For more advanced usage, you can gather tasks so they all run at once such as in the following example which can fetch a large number of devices' system info very quickly:
from tplinkcloud import TPLinkDeviceManager
import asyncio
import json
username = 'kasa@email.com'
password = 'secure'
device_manager = TPLinkDeviceManager(username, password)
async def fetch_all_devices_sys_info():
devices = await device_manager.get_devices()
fetch_tasks = []
for device in devices:
async def get_info(device):
print(f'Found {device.model_type.name} device: {device.get_alias()}')
print("SYS INFO")
print(json.dumps(device.device_info, indent=2, default=lambda x: vars(x)
if hasattr(x, "__dict__") else x.name if hasattr(x, "name") else None))
print(json.dumps(await device.get_sys_info(), indent=2, default=lambda x: vars(x)
if hasattr(x, "__dict__") else x.name if hasattr(x, "name") else None))
fetch_tasks.append(get_info(device))
await asyncio.gather(*fetch_tasks)
asyncio.run(fetch_all_devices_sys_info())
Retrieve devices
To view your devices, you can run the following:
devices = await device_manager.get_devices()
if devices:
print(f'Found {len(devices)} devices')
for device in devices:
print(f'[{device.cloud_type}] {device.model_type.name} device called {device.get_alias()}')
By default, get_devices() returns devices from both the Kasa and Tapo clouds. To disable Tapo device discovery:
device_manager = TPLinkDeviceManager(username, password, include_tapo=False)
Control your devices
Smart Power Strips (HS300, KP303)
Toggle a plug:
device_name = "My Smart Plug"
device = await device_manager.find_device(device_name)
if device:
print(f'Found {device.model_type.name} device: {device.get_alias()}')
await device.toggle()
else:
print(f'Could not find {device_name}')
Replace My Smart Plug with the alias you gave to your plug in the Kasa or Tapo app (be sure to give a different alias to each device). Instead of toggle(), you can also use power_on() or power_off().
To retrieve power consumption data for one of the individual plugs on an HS300 power strip (KP303 does not support power usage data):
import json
device = await device_manager.find_device("My Smart Plug")
power_usage = await device.get_power_usage_realtime()
print(json.dumps(power_usage, indent=2, default=lambda x: x.__dict__))
If you want to get multiple devices with a name including a certain substring, you can use the following:
device_names_like = "plug"
devices = await device_manager.find_devices(device_names_like)
if devices:
print(f'Found {len(devices)} matching devices')
for device in devices:
print(f'{device.model_type.name} device called {device.get_alias()}')
Smart Plugs (HS100, HS103, HS105, HS110, KP115, KP125, EP40)
These have the same functionality as the Smart Power Strips, though the HS100, HS103, and HS105 do not have the power usage features.
Smart Outdoor Plugs (KP200, KP400)
Multi-outlet outdoor plugs. Each outlet is exposed as a child device that can be controlled independently:
# Control the parent device (affects all outlets)
device = await device_manager.find_device("Backyard Plug")
await device.power_on()
# Child devices are returned by get_devices() alongside parents
devices = await device_manager.get_devices()
for device in devices:
if 'KP400CHILD' in device.model_type.name:
print(f'Outlet: {device.get_alias()}')
await device.toggle()
Smart Light Strips (KL420L5, KL430)
Light strips support color and brightness control:
device = await device_manager.find_device("Living Room Strip")
# Basic on/off
await device.power_on()
# Set brightness (0-100)
await device.set_brightness(75)
# Set color (hue: 0-360, saturation: 0-100, brightness: 0-100)
await device.set_color(hue=240, saturation=100, brightness=80)
# Set color temperature (2500-9000 Kelvin)
await device.set_color_temp(4000)
Smart Switches (HS200)
Smart switches have the same on/off functionality as smart plugs:
device = await device_manager.find_device("Kitchen Light Switch")
await device.toggle()
Add and modify schedule rules for your devices
Edit an existing schedule rule
device_name = "My Smart Plug"
device = await device_manager.find_device(device_name)
if device:
print(f'Found {device.model_type.name} device: {device.get_alias()}')
print(f'Modifying schedule rule')
schedule = await device.get_schedule_rules()
original_rule = schedule.rules[0]
rule_edit = TPLinkDeviceScheduleRuleBuilder(
original_rule
).with_enable_status(
False
)
await device.edit_schedule_rule(rule_edit.to_json())
else:
print(f'Could not find {device_name}')
Add a new schedule rule
device_name = "My Smart Plug"
device = await device_manager.find_device(device_name)
if device:
print(f'Found {device.model_type.name} device: {device.get_alias()}')
print(f'Adding schedule rule')
new_rule = TPLinkDeviceScheduleRuleBuilder(
).with_action(
turn_on=True
).with_name(
'My Schedule Rule'
).with_enable_status(
True
).with_sunset_start().with_repeat_on_days(
[0, 0, 0, 0, 0, 1, 1]
).build()
await device.add_schedule_rule(new_rule.to_json())
else:
print(f'Could not find {device_name}')
Delete a schedule rule
device_name = "My Smart Plug"
device = await device_manager.find_device(device_name)
if device:
print(f'Found {device.model_type.name} device: {device.get_alias()}')
print(f'Deleting schedule rule')
schedule = await device.get_schedule_rules()
rule = schedule.rules[0]
await device.delete_schedule_rule(rule.id)
else:
print(f'Could not find {device_name}')
Testing
This project leverages wiremock to test the code to some extent. Note this will not protect the project from changes that TP-Link makes to their API, but instead verifies that the existing code functions consistently as written.
Local Testing
Note that the tests setup leverages the local_env_vars.py file. The values for those environment variables need to be set based on the following:
TPLINK_KASA_USERNAME:kasa_docker- This must have parity with the V2 loginbodyspecified intests/wiremock/mappings/v2_login_request.jsonTPLINK_KASA_PASSWORD:kasa_password- This must have parity with the V2 loginbodyspecified intests/wiremock/mappings/v2_login_request.jsonTPLINK_KASA_TERM_ID:2a8ced52-f200-4b79-a1fe-2f6b58193c4c- This must be a UUID V4 string and must have parity with the V2 loginbodyspecified intests/wiremock/mappings/v2_login_request.jsonTPLINK_KASA_API_URL:http://127.0.0.1:8080- This URL is simplyhttp://127.0.0.1but the url port must have parity with thedocker-compose.yamlwiremock service's exposed httpport.
To run tests, you will first need to start the wiremock service by running:
docker compose up -d
Then, you can run the actual tests with the following command:
pytest --verbose
GitHub Testing
This project leverages GitHub Actions and has a workflow that will run these tests. The environment configuration for the tests must have parity with the local_env_vars.py file from the local testing.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file tplink_cloud_api-5.2.0.tar.gz.
File metadata
- Download URL: tplink_cloud_api-5.2.0.tar.gz
- Upload date:
- Size: 52.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a35ef9c79b5608e893a5e66dec3215e8b3bc7c99b2cc271d09c3032966fdcde3
|
|
| MD5 |
f41526a0fc099f7deeb743ae0a8c2d8f
|
|
| BLAKE2b-256 |
26f272e3b43f9f36ece386ec2fd968d599fd9a544da14a4c298f1646c2961a1c
|
Provenance
The following attestation bundles were made for tplink_cloud_api-5.2.0.tar.gz:
Publisher:
python-publish.yml on piekstra/tplink-cloud-api
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tplink_cloud_api-5.2.0.tar.gz -
Subject digest:
a35ef9c79b5608e893a5e66dec3215e8b3bc7c99b2cc271d09c3032966fdcde3 - Sigstore transparency entry: 928000225
- Sigstore integration time:
-
Permalink:
piekstra/tplink-cloud-api@7b8773eb784e6eba3dd5cdf93fa99b610921ab9d -
Branch / Tag:
refs/tags/v5.2.0 - Owner: https://github.com/piekstra
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-publish.yml@7b8773eb784e6eba3dd5cdf93fa99b610921ab9d -
Trigger Event:
release
-
Statement type:
File details
Details for the file tplink_cloud_api-5.2.0-py3-none-any.whl.
File metadata
- Download URL: tplink_cloud_api-5.2.0-py3-none-any.whl
- Upload date:
- Size: 52.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f2821835bf1550490fd1852d6aafe4fedf27a82337b85f8eab935d067bc35db7
|
|
| MD5 |
02442f591bb7a9a320d26c4505515026
|
|
| BLAKE2b-256 |
9cb642781ba77d537cb727034647a81ed41d49357b0e5cb8ecb3b6477281351a
|
Provenance
The following attestation bundles were made for tplink_cloud_api-5.2.0-py3-none-any.whl:
Publisher:
python-publish.yml on piekstra/tplink-cloud-api
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tplink_cloud_api-5.2.0-py3-none-any.whl -
Subject digest:
f2821835bf1550490fd1852d6aafe4fedf27a82337b85f8eab935d067bc35db7 - Sigstore transparency entry: 928000263
- Sigstore integration time:
-
Permalink:
piekstra/tplink-cloud-api@7b8773eb784e6eba3dd5cdf93fa99b610921ab9d -
Branch / Tag:
refs/tags/v5.2.0 - Owner: https://github.com/piekstra
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-publish.yml@7b8773eb784e6eba3dd5cdf93fa99b610921ab9d -
Trigger Event:
release
-
Statement type: