Home energy · Node-RED
My Tesla Powerwall flow, and getting OAuth to work from a home server
How a small Linux box running Node-RED reads my Powerwall and solar through the Tesla Fleet API, sends alerts to Telegram, and keeps its Tesla login alive without me touching it.
- Runs on
- Debian Linux box, Node-RED (no Docker)
- Talks to
- Tesla Fleet API, energy endpoints
- Alerts
- Telegram bot
- Auth domain
- energy.pagraham.co.uk
What the flow does
- Live status. Every few minutes it reads the energy site's live status: solar generation, home load, battery charge level, grid import/export and whether the grid is up.
- Alerts that matter. Telegram messages when the battery reaches full, drops below a threshold, or the grid goes down and the house switches to battery. Alerts only fire on a change of state, so it doesn't nag.
- Daily summary. A short end-of-day message with what the panels made, what the house used and how much went to or from the grid.
- Commands. With the energy command scope, the flow can change the backup reserve or operating mode, for example holding charge before a planned outage.
Why the auth is the hard part
Tesla's Fleet API is built for apps with a website and a server, not for one person with a Raspberry Pi-class box. To use it you need a registered developer application with a domain you control. Tesla uses that domain twice:
- It downloads a public key from a fixed path on the domain when you register as a partner.
- After you sign in, it sends you back to a redirect URL on that domain with a one-time code.
Node-RED sits behind my home router with no public address, so neither of those can point straight at it. The answer is to let a small public website on my own domain handle the parts Tesla needs to reach, and let Node-RED do everything else.
I originally hosted the key and redirect on another of my domains. I've moved them to
energy.pagraham.co.uk so the Tesla setup lives with the rest of my personal projects.
The one-time setup
- Create a key pair. Tesla wants an EC key on the
prime256v1curve. The private key stays on the Node-RED box and is never published.openssl ecparam -name prime256v1 -genkey -noout -out private-key.pem openssl ec -in private-key.pem -pubout -out public-key.pem - Publish the public key on the domain at the exact path Tesla expects:
https://energy.pagraham.co.uk/.well-known/appspecific/com.tesla.3p.public-key.pem - Register the app on developer.tesla.com with
pagraham.co.ukas an allowed origin, the redirect URL below, and the scopesopenid offline_access energy_device_data energy_cmds.offline_accessis the important one: without it Tesla doesn't issue a refresh token. - Register as a partner for the EU region. Node-RED gets a partner token using the client
credentials, then calls
POST /api/1/partner_accountswith the domain. Tesla fetches the public key and, if it matches, the domain is registered.GET /api/1/partner_accounts/public_key?domain=…confirms it worked. - Sign in once. Open Tesla's authorise page in a browser, log in and approve. Tesla redirects
to the callback page on my domain with a
code. - Exchange the code. The code goes to Node-RED, which swaps it for an access token and a refresh token at Tesla's token endpoint.
The sign-in request
https://auth.tesla.com/oauth2/v3/authorize
?response_type=code
&client_id=<CLIENT_ID>
&redirect_uri=https://energy.pagraham.co.uk/callback
&scope=openid offline_access energy_device_data energy_cmds
&state=<random value, checked on return>
Swapping the code for tokens
POST https://fleet-auth.prd.vn.cloud.tesla.com/oauth2/v3/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&client_id=<CLIENT_ID>
&client_secret=<CLIENT_SECRET>
&code=<code from the callback>
&audience=https://fleet-api.prd.eu.vn.cloud.tesla.com
&redirect_uri=https://energy.pagraham.co.uk/callback
Getting the code from the website to Node-RED
There are two ways to bridge the public callback page and the private Node-RED box:
- Copy and paste. The callback page just shows the code. I paste it into an inject node or a small dashboard form in Node-RED, which does the exchange. Simple, and nothing at home is exposed. It's only needed once, or again if the refresh token is ever lost.
- Tunnel. A Cloudflare Tunnel exposes a single Node-RED
http inendpoint at the callback URL, so the exchange happens automatically. Neater, but it opens a path into the home network, so that endpoint must checkstateand do nothing else.
Keeping it signed in
Access tokens are short-lived, so the flow refreshes them on a timer and whenever an API call returns
401. Tesla's refresh tokens have two rules that catch people out:
- Single use. Every refresh returns a new refresh token, and the old one stops working. The new one must be saved straight away, before anything else happens.
- Three-month life. A refresh token expires after about three months if it isn't used, so the flow refreshing regularly keeps the chain alive indefinitely. Tesla allows the previous token for up to 24 hours as a safety net if a save fails.
POST https://fleet-auth.prd.vn.cloud.tesla.com/oauth2/v3/token
grant_type=refresh_token
&client_id=<CLIENT_ID>
&refresh_token=<latest refresh token>
In Node-RED the tokens live in persistent context (the localfilesystem context store
in settings.js), so they survive restarts and power cuts. A 401 login_required that a refresh
can't fix sends a Telegram message asking me to sign in again.
The endpoints the flow uses
| Call | Used for |
|---|---|
GET /api/1/products | Find the energy site ID (once) |
GET /api/1/energy_sites/{id}/live_status | Solar, load, battery %, grid status, every few minutes |
GET /api/1/energy_sites/{id}/site_info | Current reserve and operating mode |
GET /api/1/energy_sites/{id}/calendar_history | Daily energy totals for the summary |
POST /api/1/energy_sites/{id}/backup | Set backup reserve % |
POST /api/1/energy_sites/{id}/operation | Switch between self-powered and time-based control |
All calls go to the EU base URL, https://fleet-api.prd.eu.vn.cloud.tesla.com, with
Authorization: Bearer <access token>.
Lessons learned
- Region matters. UK accounts live in the EU region. Using the North America URL gives confusing errors.
- Save the new refresh token first. Most "it worked for a week then stopped" problems are a refresh token that was used but whose replacement was never stored.
- Keep secrets out of flows. Client secret and private key live in environment variables or credentials, not in function nodes, so exporting a flow to share never leaks them.
- Poll politely. Fleet API usage is metered. Every few minutes is plenty for a house.
- Keep the key published. If the public key disappears from the domain, Tesla can reject the app later.