Your script has been switching a Tapo plug on and off for months. Then the plug quietly updates its firmware, and the next request comes back with 403 Forbidden. Nothing in your code changed.

The cause is a switch called "Third-Party Compatibility", tucked away in the Tapo app under Me > Third-Party Services. Since firmware 1.4.0, a plug only talks to third-party clients the way it used to while that switch is on. It has tripped people up again and again. At least nine issues tell the same story, among them #441, #449 and #473.

As of v0.11.1 of tapo, my unofficial Rust and Python client for TP-Link Tapo devices, the switch can stay off, with minor exceptions that are covered below.