# Tesla Fleet API Integration — Rebuild Guide

This document describes how to rebuild the Tesla Fleet API integration on ai-mini-370 from scratch. Written for an AI assistant with access to the homelab.

---

## Overview

The integration enables programmatic control of a 2023 Model Y via Tesla's Fleet API. It consists of:

1. **Tesla Developer App** — registered at developer.tesla.com, owns the OAuth credentials
2. **OAuth tokens** — user-authorized access + refresh tokens stored in `~/.config/bot/tesla-tokens.json`
3. **EC P-256 key pair** — required for signing vehicle commands (2023+ models)
4. **Caddy public key endpoint** — Tesla verifies the key at `https://domain.net/.well-known/appspecific/com.tesla.3p.public-key.pem`
5. **tesla-http-proxy** — local signing proxy (Go binary), listens on `localhost:4443`
6. **tesla.py** — Python CLI client wrapping the Fleet API + proxy

---

## Prerequisites

- `domain.net` domain with DNS pointing to this server and port 443 forwarded
- Caddy running and managing TLS for `domain.net`
- Vaultwarden accessible (creds stored in bot-accessible collection under "Tesla Fleet API")
- Go installed (for building tesla-http-proxy)
- Python 3.11+, `requests` library

---

## Step 1: Tesla Developer App

If the app needs to be recreated:

1. Go to [developer.tesla.com](https://developer.tesla.com)
2. Create a new application
3. Request all 5 scopes: `Profile`, `Vehicle Info`, `Vehicle Location`, `Vehicle Commands`, `Charging`
4. Set callback URL to `https://domain.net/tesla-auth`
5. Save `Client ID` and `Client Secret` to Vaultwarden item **"Tesla Fleet API"**
   - Username: Client ID
   - Password: Client Secret
   - Collection: bot-accessible

---

## Step 2: EC P-256 Key Pair

Generate the key pair if `~/.config/bot/tesla-keys/` doesn't exist or keys are missing:

```bash
mkdir -p ~/.config/bot/tesla-keys
cd ~/.config/bot/tesla-keys
openssl ecparam -name prime256v1 -genkey -noout -out private.pem
openssl ec -in private.pem -pubout -out public.pem
chmod 600 private.pem
```

Get the public key content for Caddy:

```bash
cat ~/.config/bot/tesla-keys/public.pem
```

---

## Step 3: Caddy Configuration

Add these two blocks inside the `domain.net` site in `/etc/caddy/Caddyfile`:

```caddy
handle /.well-known/appspecific/com.tesla.3p.public-key.pem {
    header Content-Type "application/x-pem-file"
    respond `<PASTE PUBLIC KEY CONTENT HERE>` 200
}

handle /tesla-auth {
    header Content-Type "text/html; charset=utf-8"
    respond `<!DOCTYPE html><html><head><title>Tesla Auth</title></head><body><h2>Tesla Authorization</h2><p>Copy this full URL and send it to your bot:</p><p id="u" style="word-break:break-all;font-family:monospace;background:#eee;padding:8px"></p><script>document.getElementById("u").textContent=location.href;</script></body></html>` 200
}
```

Reload Caddy:

```bash
openclaw config validate && echo "valid"
sudo systemctl reload caddy
```

Verify the endpoint:

```bash
curl -s https://domain.net/.well-known/appspecific/com.tesla.3p.public-key.pem
# Should return the PEM public key
```

---

## Step 4: Partner Registration

This registers the developer app with Tesla and associates it with the public key.

```bash
# Get a client credentials token (not user token)
CLIENT_ID=$(bw get username "Tesla Fleet API" --session $BW_SESSION)
CLIENT_SECRET=$(bw get password "Tesla Fleet API" --session $BW_SESSION)

TOKEN=$(curl -s -X POST https://auth.tesla.com/oauth2/v3/token \
  -d "grant_type=client_credentials&client_id=${CLIENT_ID}&client_secret=${CLIENT_SECRET}&scope=openid" \
  | python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])")

# Register partner account
curl -s -X POST https://fleet-api.prd.na.vn.cloud.tesla.com/api/1/partner_accounts \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"domain": "domain.net"}'
```

Expected response: `{"response": {"public_key": "...", "domain": "domain.net"}}`

---

## Step 5: OAuth Token Flow

The scripts live at `~/.openclaw/workspace/scripts/`:

```bash
# Step 1: Generate auth URL
export TESLA_CLIENT_ID="..."   # or set BW_SESSION and script pulls from vault
export TESLA_CLIENT_SECRET="..."
python3 ~/.openclaw/workspace/scripts/tesla-auth.py step1
```

Visit the printed URL in a browser, log in with Tesla account. The `domain.net/tesla-auth` page displays the full callback URL.

```bash
# Step 2: Exchange code for tokens
python3 ~/.openclaw/workspace/scripts/tesla-auth.py step2 "https://domain.net/tesla-auth?code=...&state=..."
```

Tokens saved to `~/.config/bot/tesla-tokens.json` (chmod 600).

---

## Step 6: Vehicle Key Pairing

**This step requires physical proximity to the car and the owner's Tesla app.**

The key pairing enables signed commands (required for 2023+ vehicles).

```bash
# Build tesla-http-proxy first (see Step 7)
# Then run the pairing command
~/code/vehicle-command/tesla-http-proxy -key-file ~/.config/bot/tesla-keys/private.pem \
  -cert ~/.config/bot/tesla-proxy/server.crt \
  -tls-key ~/.config/bot/tesla-proxy/server.key \
  -host localhost -port 4443 &

# Get access token
TOKEN=$(python3 -c "
import json, time, requests
t = json.load(open('~/.config/bot/tesla-tokens.json'))
print(t['access_token'])
")

VIN="" # Your VIN

curl -sk -X POST https://localhost:4443/api/1/vehicles/${VIN}/command/add_key_request \
  -H "Authorization: Bearer $TOKEN"
```

Owner opens the Tesla app on his phone while near the car and approves the key.

---

## Step 7: tesla-http-proxy (Signing Proxy)

The proxy signs vehicle commands with the EC key. It's a Go binary from Tesla's `vehicle-command` repo.

### Build

```bash
# Install Go if needed
sudo apt install golang-go  # or download from golang.org

mkdir -p ~/code
cd ~/code
git clone https://github.com/teslamotors/vehicle-command
cd vehicle-command
go build ./cmd/tesla-http-proxy
```

### Self-signed TLS cert for the proxy

```bash
mkdir -p ~/.config/bot/tesla-proxy
openssl req -x509 -newkey rsa:4096 -keyout ~/.config/bot/tesla-proxy/server.key \
  -out ~/.config/bot/tesla-proxy/server.crt -days 3650 -nodes \
  -subj "/CN=localhost"
chmod 600 ~/.config/bot/tesla-proxy/server.key
```

### Run (current invocation)

```bash
/code/vehicle-command/tesla-http-proxy \
  -key-file ~/.config/bot/tesla-keys/private.pem \
  -cert ~/.config/bot/tesla-proxy/server.crt \
  -tls-key ~/.config/bot/tesla-proxy/server.key \
  -host localhost \
  -port 4443
```

> **Note:** Currently running as a background process (not a systemd service). Consider adding a systemd unit if it needs to survive reboots.

### Make it a systemd service (optional)

```ini
# /etc/systemd/system/tesla-http-proxy.service
[Unit]
Description=Tesla HTTP Proxy (vehicle command signing)
After=network.target

[Service]
Type=simple
User=<bot username>
ExecStart=/code/vehicle-command/tesla-http-proxy \
  -key-file ~/.config/bot/tesla-keys/private.pem \
  -cert ~/.config/bot/tesla-proxy/server.crt \
  -tls-key ~/.config/bot/tesla-proxy/server.key \
  -host localhost -port 4443
Restart=on-failure

[Install]
WantedBy=multi-user.target
```

```bash
sudo systemctl enable --now tesla-http-proxy
```

---

## Step 8: tesla.py Client

Script at `~/.openclaw/workspace/scripts/tesla.py`.

### Key constants (update if VIN/Fleet ID changes)

```python
VIN       = "" # Your VIN
FLEET_ID  = "" # Your Fleet ID
PROXY_BASE = "https://localhost:4443/api/1/vehicles"   # signed commands
FLEET_BASE = "https://fleet-api.prd.na.vn.cloud.tesla.com/api/1/vehicles"  # read-only
```

### Usage

```bash
python3 ~/.openclaw/workspace/scripts/tesla.py status
python3 ~/.openclaw/workspace/scripts/tesla.py battery
python3 ~/.openclaw/workspace/scripts/tesla.py wake
python3 ~/.openclaw/workspace/scripts/tesla.py climate on 72
python3 ~/.openclaw/workspace/scripts/tesla.py climate off
python3 ~/.openclaw/workspace/scripts/tesla.py climate status
```

### Architecture notes

- **Read commands** (status, battery, climate status) → direct Fleet API, no proxy needed
- **Write commands** (climate on/off, set temp) → routed through `localhost:4443` proxy, which signs the request with the EC key
- Token refresh is automatic — checks `expires_at` and refreshes if <5 min remaining
- `verify=False` on proxy calls — expected, proxy uses self-signed cert

---

## Troubleshooting

**"The provided key is not the expected type" from bw unlock**
- Run `bw logout && bw login` interactively to reset local key store
- Or use `export TESLA_CLIENT_ID=... TESLA_CLIENT_SECRET=...` env vars directly

**proxy_post fails with connection refused**
- tesla-http-proxy isn't running: `ps aux | grep tesla-http-proxy`
- Restart it (or set up systemd service)

**Fleet API 401**
- Token expired and refresh failed — re-run OAuth flow (Steps 5)
- Check refresh token is still valid

**Vehicle commands return `result: false`**
- Key not paired — redo Step 6 with Owner near the car
- Vehicle asleep — run `tesla.py wake` first, wait ~30s, retry

---

## File Locations Summary

| File | Purpose |
|------|---------|
| `~/.config/bot/tesla-tokens.json` | OAuth access + refresh tokens |
| `~/.config/bot/tesla-keys/private.pem` | EC P-256 private key (signing) |
| `~/.config/bot/tesla-keys/public.pem` | EC P-256 public key (served via Caddy) |
| `~/.config/bot/tesla-proxy/server.crt` | Proxy TLS cert (self-signed) |
| `~/.config/bot/tesla-proxy/server.key` | Proxy TLS key |
| `~/code/vehicle-command/tesla-http-proxy` | Signing proxy binary |
| `~/.openclaw/workspace/scripts/tesla-auth.py` | OAuth PKCE flow helper |
| `~/.openclaw/workspace/scripts/tesla.py` | Vehicle command CLI |
