1. This site uses cookies. By continuing to use this site, you are agreeing to our use of cookies. Learn More.
  2. Anuncie Aqui
    Anuncie aqui você Também: fdantas@4each.com.br

[Python] Python 'requests' works fine, but returns '403' on one request curl returns '200'

Discussão em 'Python' iniciado por Stack, Setembro 9, 2025.

  1. Stack

    Stack Membro Participativo

    I'm using a python library called libtado (https://github.com/germainlefebvre4/libtado). It works fine, but one request responds with 403, although the same request gets 200 when Google Chrome makes it. I've read quite a few topics about it here on Stackoverflow and other sites, and I can't pinpoint the problem.

    This python script:

    from libtado.api import Tado
    t = Tado(token_file_path=token_file_path)

    print(t.get_schedule_timetables(2))
    print("-------------------")
    print(t.set_early_start(2, False))
    print("-------------------")
    t.set_schedule(2, 1)


    ... gives this output:

    (venv-libtado) PS C:\Git\tado-schedules> python tado-schedules-and-sheets.py -s 0
    [{'id': 0, 'type': 'ONE_DAY'}, {'id': 1, 'type': 'THREE_DAY'}, {'id': 2, 'type': 'SEVEN_DAY'}]
    -------------------
    {'enabled': False}
    -------------------
    Traceback (most recent call last):
    File "C:\Git\tado-schedules\tado-schedules-and-sheets.py", line 164, in <module>
    t.set_schedule(2, 1)
    File "C:\Git\tado-schedules\venv-libtado\Lib\site-packages\libtado\api.py", line 1028, in set_schedule
    return self._api_call('homes/%i/zones/%i/schedule/activeTimeTable' % (self.id, zone), payload, method='PUT')
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    File "C:\Git\tado-schedules\venv-libtado\Lib\site-packages\libtado\api.py", line 273, in _api_call
    res = call_put(url, data)
    ^^^^^^^^^^^^^^^^^^^
    File "C:\Git\tado-schedules\venv-libtado\Lib\site-packages\libtado\api.py", line 254, in call_put
    r.raise_for_status()
    File "C:\Git\tado-schedules\venv-libtado\Lib\site-packages\requests\models.py", line 1026, in raise_for_status
    raise HTTPError(http_error_msg, response=self)
    requests.exceptions.HTTPError: 403 Client Error: for url: https://my.tado.com/api/v2/homes/[myzone]/zones/2/schedule/activeTimeTable


    So the first HTTP calls get 200. One of them is GET (https://github.com/germainlefebvre4...dee1f02a45ff00e3be6debb49/libtado/api.py#L949), one is PUT (https://github.com/germainlefebvre4...ee1f02a45ff00e3be6debb49/libtado/api.py#L1473). (I've tried a few others, they work as well.)

    But set_schedule() (https://github.com/germainlefebvre4...ee1f02a45ff00e3be6debb49/libtado/api.py#L1010) doesn't work... The architecture is the same as the other calls, though...

    And if I mimic that action in tado's official website app, the request gets 200. This is said request from Google Chrome (copied for bash):

    curl 'https://my.tado.com/api/v2/homes/[myzone]/zones/2/schedule/activeTimetable?ngsw-bypass=true' \
    -X 'PUT' \
    -H 'accept: application/json, text/plain, */*' \
    -H 'accept-language: nl,en-US;q=0.9,en;q=0.8,de;q=0.7' \
    -H 'authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImd0eSI6WyJhdXRob3JpemF0aW9uX2NvZGUiLCJyZWZyZXNoX3Rva2VuIl0sImtpZCI6Im0ycUJ3aEE1TUx3M2VSM29QSjQxT1duclR3byJ9.eyJhdWQiOlsicGFydG5lciJdLCJleHAiOjE3NTc0MDYxNzYsImlhdCI6MTc1NzQwNTU3NiwiaXNzIjoidGFkbyIsIm5iZiI6MTc1NzQwNTU3Niwic3ViIjoiYmI3ZWVmZTctNzU1MC00MTFjLTkwNWQtNDc3MDU2MjZiNjljIiwianRpIjoiN2JkYmQ2ODYtMTVmMC00MzBjLWFjMDUtOTgwMzM1OGU2ZjhkIiwiZW1haWwiOiJkZWJvZWNrLmVyaWtAZ21haWwuY29tIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJyb2xlcyI6W10sImF1dGhfdGltZSI6MTc1NzQwMDY2OSwiYXBwbGljYXRpb25JZCI6ImFmNDRmODllLWFlODYtNGViZS05MDVmLTZiZjc1OWNmNjQ3MyIsInRpZCI6IjFkNTQzYWQ1LWE4YWMtNDcwNC1iOWUyLTI2ODM4YjRkNjUxMyIsInNpZCI6ImFlNzAyMzM5LTRhNWItNGQ4MC05MmUxLTZhODllZjI2MWUzNSIsInRhZG9faG9tZXMiOlt7ImlkIjoxMzc1MTY2fV0sImxvY2FsZSI6Im5sIiwidGFkb19zY29wZSI6WyJob21lLnVzZXIiXSwidGFkb191c2VybmFtZSI6ImRlYm9lY2suZXJpa0BnbWFpbC5jb20iLCJuYW1lIjoiRXJpayBEZSBCb2VjayIsInRhZG9fY2xpZW50X2lkIjoidGFkby13ZWItYXBwIn0.aFMRX22to3p9g_ChhDkCn8Rc4mB8CbgkHKS0lSzq4b97Du8kdnUGM9ReNgy7ixd1lnQakbJYt-ATdIo77QV5HS2k6O_28cbj6XL6gVbDmQLyV4ADOIeFbFyYua4fTfOJE6vQDfDGGFtbBmEC6mrGCHqMg7Rgn4FSwxByEa51hgej0udnm0D8WpxDQu87jr4bQOBTQnpMbQ0FOeNXGOoFQ0kHZzWogomM2IXs62ntt3O_WgefSLEd6qdJ6vNOCZRo_JpleGkbEWWk3OF9zmwhHVdcmqXv09KQxDrbzCDSJhBCQc96PbmQ9wcYyewHPgrvBlDgeKkl5W0eyKjlmrQlNA' \
    -H 'content-type: application/json' \
    -H 'origin: https://app.tado.com' \
    -H 'priority: u=1, i' \
    -H 'referer: https://app.tado.com/' \
    -H 'sec-ch-ua: "Chromium";v="140", "Not=A?Brand";v="24", "Google Chrome";v="140"' \
    -H 'sec-ch-ua-mobile: ?0' \
    -H 'sec-ch-ua-platform: "Windows"' \
    -H 'sec-fetch-dest: empty' \
    -H 'sec-fetch-mode: cors' \
    -H 'sec-fetch-site: same-site' \
    -H 'user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36' \
    -H 'x-amzn-trace-id: tado=webapp-release/v3786' \
    --data-raw '{"id":1}'


    So I replaced (https://github.com/germainlefebvre4...5ff00e3be6debb49/libtado/api.py#L103C4-L106C6) with:

    self.access_headers = {
    'Authorization': f'Bearer {access_token}',
    #'User-Agent': 'python/libtado', # Changed to mimic Google Chrome
    # Added to mimic Google Chrome:
    ":authority": "my.tado.com",
    ":method": "PUT",
    ":path": "/api/v2/homes/[myzone]/zones/2/schedule/activeTimetable?ngsw-bypass=true",
    ":scheme": "https",
    "accept": "application/json, text/plain, */*",
    "accept-encoding": "gzip, deflate, br, zstd",
    "accept-language": "nl,en-US;q=0.9,en;q=0.8,de;q=0.7",
    "authorization": "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImd0eSI6WyJhdXRob3JpemF0aW9uX2NvZGUiLCJyZWZyZXNoX3Rva2VuIl0sImtpZCI6Im0ycUJ3aEE1TUx3M2VSM29QSjQxT1duclR3byJ9.eyJhdWQiOlsicGFydG5lciJdLCJleHAiOjE3NTc0MDYxNzYsImlhdCI6MTc1NzQwNTU3NiwiaXNzIjoidGFkbyIsIm5iZiI6MTc1NzQwNTU3Niwic3ViIjoiYmI3ZWVmZTctNzU1MC00MTFjLTkwNWQtNDc3MDU2MjZiNjljIiwianRpIjoiN2JkYmQ2ODYtMTVmMC00MzBjLWFjMDUtOTgwMzM1OGU2ZjhkIiwiZW1haWwiOiJkZWJvZWNrLmVyaWtAZ21haWwuY29tIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJyb2xlcyI6W10sImF1dGhfdGltZSI6MTc1NzQwMDY2OSwiYXBwbGljYXRpb25JZCI6ImFmNDRmODllLWFlODYtNGViZS05MDVmLTZiZjc1OWNmNjQ3MyIsInRpZCI6IjFkNTQzYWQ1LWE4YWMtNDcwNC1iOWUyLTI2ODM4YjRkNjUxMyIsInNpZCI6ImFlNzAyMzM5LTRhNWItNGQ4MC05MmUxLTZhODllZjI2MWUzNSIsInRhZG9faG9tZXMiOlt7ImlkIjoxMzc1MTY2fV0sImxvY2FsZSI6Im5sIiwidGFkb19zY29wZSI6WyJob21lLnVzZXIiXSwidGFkb191c2VybmFtZSI6ImRlYm9lY2suZXJpa0BnbWFpbC5jb20iLCJuYW1lIjoiRXJpayBEZSBCb2VjayIsInRhZG9fY2xpZW50X2lkIjoidGFkby13ZWItYXBwIn0.aFMRX22to3p9g_ChhDkCn8Rc4mB8CbgkHKS0lSzq4b97Du8kdnUGM9ReNgy7ixd1lnQakbJYt-ATdIo77QV5HS2k6O_28cbj6XL6gVbDmQLyV4ADOIeFbFyYua4fTfOJE6vQDfDGGFtbBmEC6mrGCHqMg7Rgn4FSwxByEa51hgej0udnm0D8WpxDQu87jr4bQOBTQnpMbQ0FOeNXGOoFQ0kHZzWogomM2IXs62ntt3O_WgefSLEd6qdJ6vNOCZRo_JpleGkbEWWk3OF9zmwhHVdcmqXv09KQxDrbzCDSJhBCQc96PbmQ9wcYyewHPgrvBlDgeKkl5W0eyKjlmrQlNA",
    "content-length": "8",
    "origin": "https://app.tado.com",
    "priority": "u=1, i",
    "referer": "https://app.tado.com/",
    "sec-ch-ua": "\"Chromium\";v=\"140\", \"Not=A?Brand\";v=\"24\", \"Google Chrome\";v=\"140\"",
    "sec-ch-ua-mobile": "?0",
    "sec-ch-ua-platform": "\"Windows\"",
    "sec-fetch-dest": "empty",
    "sec-fetch-mode": "cors",
    "sec-fetch-site": "same-site",
    "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36",
    "x-amzn-trace-id": "tado=webapp-release/v3786"
    }


    I would expect 200 now, but no...

    Does anyone have any idea how to fix this behavior?

    (Obviously, [myzone] is my redaction...)

    PS Here's the unofficial API documentation: https://kritsel.github.io/tado-openapispec-v2/swagger#/

    Continue reading...

Compartilhe esta Página