diff --git a/doc/ChallengeAdded.json b/doc/Old JSON (Pre 2025-26)/ChallengeAdded.json
similarity index 100%
rename from doc/ChallengeAdded.json
rename to doc/Old JSON (Pre 2025-26)/ChallengeAdded.json
diff --git a/doc/ChallengeConsumptionUpdatedValuesReceived.json b/doc/Old JSON (Pre 2025-26)/ChallengeConsumptionUpdatedValuesReceived.json
similarity index 100%
rename from doc/ChallengeConsumptionUpdatedValuesReceived.json
rename to doc/Old JSON (Pre 2025-26)/ChallengeConsumptionUpdatedValuesReceived.json
diff --git a/doc/ChallengeDetailsInitialValuesReceived.json b/doc/Old JSON (Pre 2025-26)/ChallengeDetailsInitialValuesReceived.json
similarity index 100%
rename from doc/ChallengeDetailsInitialValuesReceived.json
rename to doc/Old JSON (Pre 2025-26)/ChallengeDetailsInitialValuesReceived.json
diff --git a/doc/ChallengeListInitialValuesReceived.json b/doc/Old JSON (Pre 2025-26)/ChallengeListInitialValuesReceived.json
similarity index 100%
rename from doc/ChallengeListInitialValuesReceived.json
rename to doc/Old JSON (Pre 2025-26)/ChallengeListInitialValuesReceived.json
diff --git a/doc/ChallengeListUpdatedValuesReceived.json b/doc/Old JSON (Pre 2025-26)/ChallengeListUpdatedValuesReceived.json
similarity index 100%
rename from doc/ChallengeListUpdatedValuesReceived.json
rename to doc/Old JSON (Pre 2025-26)/ChallengeListUpdatedValuesReceived.json
diff --git a/doc/Readme.md b/doc/Readme.md
new file mode 100644
index 0000000..58bd22d
--- /dev/null
+++ b/doc/Readme.md
@@ -0,0 +1,34 @@
+# Using get_tokens.py
+This python script will let you use it to log in to your Hilo account.
+
+From there, you will be asked to copy the full HA redirect(callback) URL and go back to your python script and press enter.
+
+Negotiation will take place and you will receive three distinct tokens:
+- Your access token
+- Your devicehub token
+- Your challengehub token
+
+Take care not to share your tokens as they are not encrypted and contain personally identifiable information.
+
+1. Run get_tokens.py, you will be provided with a link. Either click it or copy it in your favourite browser.
+
+
+2. Login using your Hilo Credentials
+
+
+
+3. Once you get to this page, select the URL and copy it to clipboard
+
+
+
+4. Go back to get_tokens.py and press enter, you'll get all 3 tokens:
+
+
+
+
+
+
+# Using Postman to send payloads to devicehub or challengehub
+## Prerequisite: you need to have your tokens on hand, they are relatively short lived so once you have them, get to connecting!
+
+
diff --git a/doc/get_tokens.py b/doc/get_tokens.py
new file mode 100644
index 0000000..df47013
--- /dev/null
+++ b/doc/get_tokens.py
@@ -0,0 +1,153 @@
+import base64
+import hashlib
+import json
+import os
+import re
+import subprocess
+from urllib.parse import urlencode, urlunparse, urlparse, parse_qs
+import requests
+import sys
+
+CLIENT_ID = "1ca9f585-4a55-4085-8e30-9746a65fa561"
+AUTH_HOST = "connexion.hiloenergie.com"
+AUTH_PATH = "/HiloDirectoryB2C.onmicrosoft.com/B2C_1A_SIGN_IN/oauth2/v2.0/authorize"
+TOKEN_PATH = "/HiloDirectoryB2C.onmicrosoft.com/B2C_1A_SIGN_IN/oauth2/v2.0/token"
+SCOPE = "openid https://HiloDirectoryB2C.onmicrosoft.com/hiloapis/user_impersonation offline_access"
+REDIRECT_URL = "https://my.home-assistant.io/redirect/oauth"
+API_HOST = "api.hiloenergie.com"
+DEVICEHUB_PATH = "/DeviceHub/negotiate"
+CHALLENGEHUB_PATH = "/ChallengeHub/negotiate"
+
+
+def create_url(base_url, path, params):
+ query_string = urlencode(params)
+ url = urlunparse(("https", base_url, path, "", query_string, ""))
+ return url
+
+
+def get_verifier_and_challenge():
+ code_verifier = base64.urlsafe_b64encode(os.urandom(40)).decode('utf-8')
+ code_verifier = re.sub('[^a-zA-Z0-9]+', '', code_verifier)
+ code_challenge = hashlib.sha256(code_verifier.encode('utf-8')).digest()
+ code_challenge = base64.urlsafe_b64encode(code_challenge).decode('utf-8')
+ code_challenge = code_challenge.replace('=', '')
+ return code_verifier, code_challenge
+
+
+def get_state():
+ return hashlib.sha256(os.urandom(40)).hexdigest()
+
+
+def build_auth_url(state, code_challenge):
+ params = {
+ "response_type": "code",
+ "client_id": CLIENT_ID,
+ "scope": SCOPE,
+ "redirect_uri": REDIRECT_URL,
+ "state": state,
+ "code_challenge": code_challenge,
+ "code_challenge_method": "S256",
+ }
+ return create_url(AUTH_HOST, AUTH_PATH, params)
+
+
+def get_code(auth_url):
+ print(f"Go there and authenticate:\n{auth_url}\n\n")
+ input("After completing authentication, copy the redirected URL from your browser and press Enter...")
+
+ try:
+ # Read URL from clipboard
+ redirect_w_token = subprocess.run(
+ ['pbpaste'], capture_output=True, text=True, check=True
+ ).stdout.strip()
+ except Exception as e:
+ print("Failed to read clipboard:", e)
+ redirect_w_token = input("Fallback: Paste the redirected URL here:\n")
+
+ print("Captured URL:", redirect_w_token[:100], "...") # show first 100 chars for sanity check
+
+ parsed_redirect = urlparse(redirect_w_token)
+ query = parse_qs(parsed_redirect.query)
+
+ if 'code' not in query:
+ raise ValueError("No 'code' parameter found in the URL. Make sure you copied the redirected URL.")
+
+ return query['code'][0]
+
+
+def exchange_code_for_token(code, code_verifier):
+ url = create_url(AUTH_HOST, TOKEN_PATH, '')
+ resp = requests.get(
+ url=url,
+ params={
+ "grant_type": "authorization_code",
+ "client_id": CLIENT_ID,
+ "redirect_uri": REDIRECT_URL,
+ "code": code,
+ "code_verifier": code_verifier,
+ },
+ allow_redirects=False
+ )
+
+ if resp.status_code != 200:
+ print("Failed to exchange code for token:", resp.status_code, resp.text)
+ sys.exit(1)
+
+ return resp.json()
+
+
+def exchange_rt_for_token(refresh_token):
+ url = create_url(AUTH_HOST, TOKEN_PATH, '')
+ resp = requests.get(
+ url=url,
+ params={
+ "grant_type": "refresh_token",
+ "refresh_token": refresh_token
+ },
+ allow_redirects=False
+ )
+
+ if resp.status_code != 200:
+ print("Failed to refresh token:", resp.status_code, resp.text)
+ sys.exit(1)
+
+ return resp.json()
+
+
+def get_challengehub_ws(token):
+ url = create_url(API_HOST, CHALLENGEHUB_PATH, '')
+ resp = requests.post(url=url, headers={'Authorization': f"Bearer {token}"})
+ print(resp.status_code)
+ return resp.json()
+
+
+def get_devicehub_ws(token):
+ url = create_url(API_HOST, DEVICEHUB_PATH, '')
+ resp = requests.post(url=url, headers={'Authorization': f"Bearer {token}"})
+ print(resp.status_code)
+ return resp.json()
+
+
+if __name__ == "__main__":
+ token = None
+
+ if len(sys.argv) == 2:
+ print("Using provided refresh token")
+ token = exchange_rt_for_token(sys.argv[1])
+ else:
+ code_verifier, challenge = get_verifier_and_challenge()
+ state = get_state()
+ url = build_auth_url(state, challenge)
+ code = get_code(url)
+ token = exchange_code_for_token(code, code_verifier)
+
+ if 'access_token' not in token:
+ print("No access token received. Full response:")
+ print(json.dumps(token, indent=True))
+ sys.exit(1)
+
+ print(f"Got Token:\n{json.dumps(token, indent=True)}")
+ challengehub_ws_info = get_challengehub_ws(token['access_token'])
+ print(f"ChallengeHub: {json.dumps(challengehub_ws_info, indent=True)}")
+ devicehub_ws_info = get_devicehub_ws(token['access_token'])
+ print(f"DeviceHub: {json.dumps(devicehub_ws_info, indent=True)}")