HTTP 401 on basic REST call
I'm guessing I must be doing something wrong, but this snippet consistently returns a HTTP 401 for me. And does not return any device info. Also tried with v1 and v2 paths, same results. Can someone shed some light on this pls?

Code: Select all

import hashlib
import json
import time
import urllib.request
import urllib.error

API_KEY = "<YOUR-FOXESS-OPENAPI-KEY>"   # <-- paste your key here
DOMAIN  = "https://www.foxesscloud.com"
PATH    = "/op/v0/device/list"
UA      = ("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
           "(KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36")

timestamp = str(round(time.time() * 1000))
# signature = md5( path + "\r\n" + token + "\r\n" + timestamp ), per the OpenAPI doc.
# The doc's Python example uses a RAW f-string (fr"..."), so \r\n are the literal
# characters backslash-r-backslash-n. (Using real CRLF gives the identical 401.)
signature = hashlib.md5(
    f"{PATH}\\r\\n{API_KEY}\\r\\n{timestamp}".encode("utf-8")
).hexdigest()

headers = {
    "Token": API_KEY,
    "Timestamp": timestamp,
    "Signature": signature,
    "Lang": "en",
    "Timezone": "Europe/London",
    "Content-Type": "application/json",
    "User-Agent": UA,
}
body = json.dumps({"currentPage": 1, "pageSize": 10}).encode("utf-8")
req = urllib.request.Request(DOMAIN + PATH, data=body, method="POST", headers=headers)

try:
    with urllib.request.urlopen(req, timeout=30) as resp:
        status, resp_headers, text = resp.status, dict(resp.headers), resp.read().decode()
except urllib.error.HTTPError as e:
    status, resp_headers, text = e.code, dict(e.headers), e.read().decode()

print("HTTP status:", status)
print("Response headers:")
for k, v in resp_headers.items():
    print(f"  {k}: {v}")
print("Body:", repr(text) if text else "<empty>")
Re: HTTP 401 on basic REST call
There is working example code here for Python: https://github.com/TonyM1958/FoxESS-Cloud

From a quick look, the format string for the signature is a raw string, not a formatted one?

Or, you can just import the sample code as described on GitHub and call directly from Python without doing any of the low level work?
H1-6.0-E hybrid inverter
6 x HV2600 v2 batteries
16 x JA Solar 405w panels
7 x Tigo TS4-A-O optimisers
Re: HTTP 401 on basic REST call
Thanks for the reply!

So yeah, you're right that the doc's example (and TonyM1958's openapi.py) build the signature with a raw f-string, so the \r\n are the literal characters \,r,\,n rather than a real CRLF. My snippet does exactly that, in plain Python "...\\r\\n..." (escaped backslashes) is identical to the raw fr"...\r\n...". For completeness I also tried it with a genuine CRLF, and the result is identical.

Here is a simple repro snippet with standard libs only you can try to reproduce what I'm seeing.

Code: Select all

 import hashlib, json, time, urllib.request, urllib.error
  API_KEY = "<YOUR-KEY>"
  DOMAIN, PATH = "https://www.foxesscloud.com", "/op/v0/device/list"
  UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
  ts = str(round(time.time()*1000))
  sig = hashlib.md5(f"{PATH}\\r\\n{API_KEY}\\r\\n{ts}".encode()).hexdigest()
  h = {"Token":API_KEY,"Timestamp":ts,"Signature":sig,"Lang":"en","Timezone":"Europe/London","Content-Type":"application/json","User-Agent":UA}
  req = urllib.request.Request(DOMAIN+PATH, data=json.dumps({"currentPage":1,"pageSize":10}).encode(), method="POST", headers=h)
  try:
      with urllib.request.urlopen(req, timeout=30) as r: status, hdrs, text = r.status, dict(r.headers), r.read().decode()
  except urllib.error.HTTPError as e: status, hdrs, text = e.code, dict(e.headers), e.read().decode()
  print("HTTP", status); [print(f"  {k}: {v}") for k,v in hdrs.items()]; print("Body:", text or "<empty>")
Would you mind trying this and seeing where I've gone wrong?

Thanks a bunch!
Re: HTTP 401 on basic REST call
Additionally, using the openapi.py library from your Github repo, I get exactly the same 401 error when calling `get_device()`.

Anything else I can try?
Re: HTTP 401 on basic REST call
The error is an authorisation problem. The only common factor is the API key, so it's suggests that is invalid?

You can read back and check the key using Energy Stats app.
H1-6.0-E hybrid inverter
6 x HV2600 v2 batteries
16 x JA Solar 405w panels
7 x Tigo TS4-A-O optimisers
Re: HTTP 401 on basic REST call
This turned out to be an invalid API key...duh.

Luckily easily solved. 8-)
Post Reply