Tesla Fleet API × Home Assistant
Getting the Tesla Fleet API running in Home Assistant — and why the "just host a file" step is the whole problem.
- Tesla Model Y
- Tesla Fleet API
- Cloudflare Worker
- Home Assistant

Why the Fleet API
Most “Tesla in Home Assistant” integrations run on Tesla’s private owner endpoints — the same API the official mobile app uses. It works until it doesn’t: Tesla rate-limits, throttles, and breaks it on a schedule no one controls, and poll-based integrations wake the car to ask each question, draining battery for the privilege. The Tesla Fleet API is the official route, and in exchange for a bit more setup it offers three things:
- Scoped OAuth — the app only gets what you grant it.
- Signed commands — vehicle commands are signed with your key and delivered through the official channel.
- Fleet Telemetry — a push stream where the car sends data to a server, instead of the server waking the car to poll.
Here is the full setup, end to end. Each step below is what I did for my Model Y, and each step links to the official Tesla Fleet API docs so you can cross-check the details.
Reference: What is Fleet API? (getting started) · Fleet Telemetry overview
Prerequisites
- A Tesla account with multi-factor authentication enabled (required before you can register an application).
- A vehicle — in my case a Model Y — that you can interact with in the Tesla app.
- A domain on Cloudflare (any subdomain will do, e.g.
fleet.example.com). - Home Assistant with HACS (the integration is a custom one).
opensslon whatever machine you generate the keys on.
Step 1 — Generate the key pair
Every Fleet API application uses an ECDSA key pair (P-256 curve):
- The private key signs the requests that matter — it is what lets the app issue Vehicle Commands. Keep it secret.
- The public key is what Tesla reads from your domain to prove you control it.
Generate both from one machine:
openssl ecparam -name prime256v1 -genkey -noout -out private-key.pem
openssl ec -in private-key.pem -pubout -out public-key.pem
Then place them deliberately:
private-key.pem→ on the machine that will sign with it. In this setup, that is the Home Assistant instance, where the integration keeps its secret. One machine, zero repos — even a private one.public-key.pem→ into the Worker in the next step. It is public by construction; nothing sensitive about it.
Docs: What is Fleet API? — Step 3: Generate a Public/Private Key Pair (the official openssl commands above come from this page).
Step 2 — Host the public key (the step the docs undersell)
Tesla requires the public key to be served at an exact, fixed path on the application’s domain:
https://<your-domain>/.well-known/appspecific/com.tesla.3p.public-key.pem
This looks like “just host a file” — and that mental model is the trap:
- It is an ownership anchor, not a one-time formality. Tesla treats control of that HTTPS endpoint as proof you own the app’s domain, and it re-checks. Their own troubleshooting docs list a missing key at this path as a cause of pairing failures — availability is a requirement for the rest of the app’s life.
- A home server is usually out: home networks are behind CGNAT, and the endpoint that anchors the app’s identity should not depend on house uptime.
- A static website is the wrong home: a static site serves committed files, which turns a liveness requirement into a repo edit.
The solution I use is a Cloudflare Worker on a subdomain — free tier, no infrastructure, boring on purpose.
Setup:
- In the Cloudflare dashboard, create a Worker (e.g.
tesla-key). - Attach the subdomain you want to use (
fleet.example.com) to the Worker as a custom domain. This sends every request on that subdomain to the Worker; the code below returns the key only on the exact path and a404everywhere else. - Replace the default Worker code with the snippet below, pasting your public key between the backticks.
- Deploy, then verify:
curl https://<your-domain>/.well-known/appspecific/com.tesla.3p.public-key.pemmust return200with the key body.curl https://<your-domain>/must return404.
The whole worker:
const TESLA_PUBLIC_KEY = `<YOUR PUBLIC KEY>`;
export default {
async fetch(request) {
const url = new URL(request.url);
if (url.pathname === "/.well-known/appspecific/com.tesla.3p.public-key.pem") {
return new Response(TESLA_PUBLIC_KEY, {
headers: {
"Content-Type": "application/x-pem-file",
"Cache-Control": "public, max-age=86400"
}
});
}
return new Response("Not found", { status: 404 });
}
};
Keep it boring: 200 on the key path with the PEM content type, no redirects, 404 everywhere else. Rotating the key later is just a new pair plus a redeploy — no other moving parts.
Docs: the register endpoint reference states the requirement verbatim — a PEM EC public key on the secp256r1 curve “must be and remain hosted at https://<app domain>/.well-known/appspecific/com.tesla.3p.public-key.pem” — and the partner endpoints include the check you can use to confirm the registered key. “Must be and remain hosted” is the sentence the getting-started guide’s “host a file” wording hides.
Step 3 — Register the application with Tesla
- From the Fleet API getting-started page, click Create Application and Access Dashboard.
- On the registration form, fill in:
- your application name and a short description of what it does,
- the scopes the integration needs — for this setup:
offline_access,vehicle_device_data,vehicle_location, andvehicle_cmds. Skip everything else; scopes are checkboxes, and a smaller grant is a smaller attack surface.
- Finish the flow — Tesla issues a partner auth token for the application.
- Call the register endpoint with that token — once per region of operation. This is the step that quietly bites people who drive a car into a second country: the app is only “registered” in the regions you called.
Docs: What is Fleet API? — Step 4: Call the Register Endpoint · register endpoint reference · Authentication overview (scopes) · Regions and Countries.
Step 4 — Pair the vehicle
Pairing is deliberately an owner action in the Tesla app:
- Open
https://tesla.com/_ak/<your-domain>— the domain that hosts the public key. - Follow the prompt to add the application to the vehicle in the Tesla app.
- If it fails, checklist:
- The Tesla app is signed in with the same Tesla account used to authorize the application.
- The app was registered in the car’s region (Step 3, item 4).
- The public key is still reachable at the
/.well-known/path (Step 2, item 4).
Docs: What is Fleet API? — Next Steps points to “Pairing a public key to a vehicle”; the Fleet Telemetry overview documents the tesla.com/_ak/ pairing deep-link and the same three failure modes (Tesla-app account mismatch, app not registered in the car’s region, key no longer available at the /.well-known/ path).
Step 5 — Install the integration in Home Assistant
- In HACS, add the
tesla_fleetintegration. This is the community integration I used; its README in the HACS entry covers installation and options. - Restart or reload custom integrations, then Add Integration → Tesla Fleet in Home Assistant.
- Complete the OAuth flow with your Tesla credentials. The integration keeps the private key locally and signs Vehicle Command traffic through Tesla’s signed command flow — the private key never leaves the instance.
- After pairing (Step 4), the car appears with its full entity set. On my Model Y that is roughly 80 entities, including:
- charge state, charge rate, cable and port status
- battery level and range, odometer, speed, power draw
- tire pressures (all four corners)
- doors, windows, locks, frunk/trunk/sunroof covers
- climate plus seat and steering-wheel heaters
- location and route device trackers
- buttons: wake, honk, flash lights, HomeLink
- an update entity for OTA software releases
Docs: the Vehicle Commands reference documents signed command traffic, and the Vehicle Endpoints reference lists the state behind the entities in step 4.
What’s next: Fleet Telemetry
The end state is push, not pull. Instead of polling at all, the car streams fields — speed, location, state of charge, doors, tire pressures — to a server at intervals as fast as 500 ms, re-sending a field only when its value changes and its interval has elapsed. Tesla’s own pricing example is telling: a lean field configuration costs roughly a cent per hour of driving.
The remaining design question is where the WebSocket endpoint lives:
- a tunnel from Home Assistant out to the public internet,
- a Worker-terminated stream, or
- the integration’s own server.
I have left this open deliberately — the car is not on telemetry yet, and the choice is an infrastructure decision, not an API one.
Docs: the Fleet Telemetry overview covers the server setup requirements (a publicly reachable WebSocket endpoint with a vehicle-trusted certificate), the vehicle configuration flow, the field-streaming behavior, and the cost example cited above; the Available Data page lists every streamable field.
Gotchas
The ones that actually cost time:
- The key endpoint is a liveness requirement, not a one-time check. The app is only as alive as the URL that proves its domain.
- Registration is per region. A car in a region you forgot will report the application as unregistered.
- MFA on the Tesla account is required before you can register the application at all.
- The private key is the only thing that can sign commands. One machine, zero repos.
- Keep the VIN out of any public write-up — a full VIN is a serial number on a moving vehicle.
Outcome
The car in Home Assistant now rides on the official API: live state feeding the dashboards and automations the house already runs, and commands going through the signed, documented channel — no dependency on rate-limited private endpoints. The part that took the most real effort was the twenty-line Worker, and that is the point: the Fleet API’s onboarding is designed around domain ownership, and solving that step boringly — exact path, long uptime, no secrets — is what makes the integration keep working. Push telemetry is the documented next step, replacing the last of the polling with a stream the car sends on its own.