KASA Device Purchase Links
Amazon:
KASA vs TP-Link Cloud
Assuming you allow for non-local use, TP-Link Cloud is basically what bridges the connection between your local network (TP-Link physical devices) and the "cloud", and seems to be what maintains the mesh of devices and their API communications.
Kasa is really just an App, and some branding for TP-Link smart devices, and mostly does just two things:
- Allows you to manage the TP-Link cloud and register new devices
- Provides a GUI for turning things off and on
Kasa itself is using the TP-Link Cloud API, which is discussed further below, since it can be used unofficially as a way to interact with physical TP-Link products / Cloud, without requiring being connected to the same router / LAN.
Official Integrations
At the time of researching, it was hard to find one spot for this info, but it appears that the official integrations are:
- IFTTT
- Google Home
- Samsung SmartThings
- Amazon Alexa
- Brilliant (Smart Home App)
Unofficial Integrations
- By hand approaches
- Libraries:
- Local / LAN
- Node / NPM - Patrick Seal: plasticrake/tplink-smarthome-api
- Python - python-kasa
- TP-Link Cloud
- Node / NPM - Alexandre Dumont: adumont/tplink-cloud-api
- This is an impressive use of the unofficial API. Dumont also has documented a lot of the API in blog posts at itnerd.space.
- Android / Kotlin - Sternbach: Sternbach-Software/KasaTPLinkAndroidSDK
- Python: goodtune/pykasa
- Node / NPM - Alexandre Dumont: adumont/tplink-cloud-api
- Local / LAN
- Other
- TP-Link Cloud
- Google Apps Script: I created a Google Apps Script wrapper around the API. You can read more about it here. This also let me more fully integrate it with Android.
- TP-Link Cloud
TP-Link Cloud API - Dev Cheatsheet
Endpoints and Methods
Main API Base URL:
https://wap.tplinkcloud.com
- OR (?)
https://use1-wap.tplinkcloud.com
As noted below on "general usage", the API is a little unique in that all methods share the same path (just
/
, the base URL), and the methods are passed in the body / payload, rather than in the path itself. For example, there is no endpoint like/turnDeviceOn
- instead you have to pass a carefully crafted JSON payload to turn devices on.
The placeholder
{{variable_name}}
indicates a place where you need to replace the{{...}}
with your unique value.
Use a
Content-Type
header, with value ofapplication/json
, for all of these requests.
Endpoints / Methods:
- Get Auth Token (these expire):
- Path:
/
- Method:
POST
- Payload:
{ "method": "login", "params": { "appType": "Kasa_Android", "cloudUserName": "{{KASA_User}}", "cloudPassword": "{{KASA_Pass}}", "terminalUUID": "{{$guid}}" } }
- Path:
- Get Device List
- Path:
/
- Method:
POST
- Payload:
{ "method": "getDeviceList", "params": { "token": "{{KASA_Token}}" } }
- Path:
- Turn a device on/off
- Path:
/
- Method:
POST
- Payload:
{ "method": "passthrough", "params": { "deviceId": "{{Device_ID}}", "requestData": { "system": { "set_relay_state": { "state": 0 } } }, "token": "{{KASA_Token}}" } }
- Note: Use
"state": 0
for off, and"state": 1
for on.
- Path:
- Get device status
- Path:
/
- Method:
POST
- Payload (sample):
{ "method": "passthrough", "params": { "deviceId": "{{device_id}}", "requestData": { "system": { "get_sysinfo": null }, "emeter": { "get_realtime": null } }, "token": "{{KASA_Token}}" } }
- Path:
- Power Strip: Turn individual plugs on and off
- Path:
/
- Method:
POST
- Payload (sample):
{ "method": "passthrough", "params": { "deviceId": "{{Device_ID}}", "requestData": { "context": { "child_ids": [ "{{Plug_ID}}" ] }, "system": { "set_relay_state": { // 0 = off, 1 = on "state": 0 } } }, "token": "{{KASA_Token}}" } }
- I have not tested this, but this is based on A, B, C, and D.
- To get
Plug_ID
, based on this comment and this code as well as this code, it should be theDevice_ID
of the entire strip, plus 2 digits (zero-left-padded, zero-indexed) corresponding to the numerical order of the plugs- For example, if the ID of your entire strip is
ABC
, then the very first plug would have aPlug_ID
ofABC00
, the second would haveABC01
, and so on.
- For example, if the ID of your entire strip is
- I'm not sure if
child_ids
will work with more than one plug ID at a time - you would think since it is an array that passing more would work and turn all IDs on or off based on the system payload, but without a device I can't test this
- Path:
These are subject to change. Currently, the best maintained place to find up-to-date info on the API is probably
adumont/tplink-cloud-api
, or the related blog posts on itnerd.space.
Notes on usage
- General usage:
- The API is a little unique in that it uses the pattern of differentiating between actions by values in the payload, rather than different endpoints / URL paths
- The action is passed in with the key of
method
- For example, rather than using something like
GET /deviceList
, it usesPOST /
with payload{"method": "getDeviceList", "params": { ... }}
- The action is passed in with the key of
- Another oddity is that the nested
requestData
object (e.g. forpassthrough
method) can be passed either as a single stringified value, or as a regular nested JSON object. Changing the type actually affects the shape of the response data (e.g. sending stringified JSON results in a stringified response, and vice-versa).- The
adumont/tplink-cloud-api
code makes it look like, at one point or another, the API might have only accepted stringifiedrequestData
payloads, but as of 7/2020, it seems fine with regular JSON objects.
- The
- The API is a little unique in that it uses the pattern of differentiating between actions by values in the payload, rather than different endpoints / URL paths
- Token sending options
- It looks like the auth token can be sent either in the URL itself or in the JSON body
- URL:
___/?token=TOKEN
- Body:
{params: {token: TOKEN}
- URL:
- For security reasons, you should pretty much always opt to send tokens as part of a payload, rather than in the URL, so they can't be sniffed (assuming valid HTTPS)
- It looks like the auth token can be sent either in the URL itself or in the JSON body
- terminalUUID / UUID
- This does not need to be a specific ID, nor does it need to be generated as a unique ID each time.
- If you are using POSTMAN to mock, you can use the
{{$guid}}
macro to generate a unique ID
- What is the purpose of
Kasa_Android
asappType
and theUser-Agent
header?- Most of the API docs available by the community rely on endpoints exposed by reverse-engineering the Kasa smartphone app. At any point, TP-Link could start cracking down on suspicious API requests, so "spoofing" the official Kasa app is a way to minimize the risk of your request getting blocked.
- Based on
tplink-cloud-api
, here are good values:appType
:Kasa_Android
User-Agent
:Dalvik/2.1.0 (Linux; U; Android 6.0.1; A0001 Build/M4B30X)
(really, this could be any modern valid Android UA that Kasa can run on)- In fact, this should actually probably be something with
Android 8.0.0
or higher in the string, as anything lower has already reached EOL (as of 2020).
- In fact, this should actually probably be something with
Android Client
- Checkout Sternbach-Software/KasaTPLinkAndroidSDK for an unofficial Android SDK
- I've pulled a recent AndroidManifest.xml - here (EDIT: This is now quite old and possibly outdated)
- I tried to spoof a related intent to the On/Off widget but could not get it to work (I think some of the Android action strings might be blocked from spoofing?)