node-tesla-api
A modern NodeJS implementation of the (unofficial) Tesla API.
![NPM](https://nodei.co/npm/node-tesla-api.png)
Under development: features outlined below may not be final.
Usage
npm i node-tesla-api
I'm only focussing on the oauth
, vehicles
, and logs
parts of the Tesla API for now.
The API follows the commands outlined in the (unofficial) Tesla API, but uses camelCase instead of underscores.
You'll need to have access to a Tesla, and have a valid Tesla username and password to make use of this API.
Please feel free to use my Tesla referral code if you buy a Tesla - we both get some free charging that way.
My code is: david60377
Example
const { oauth, vehicles } = require('node-tesla-api')
const sleep = async delay => new Promise(resolve => setTimeout(resolve, delay))
const wakeCar = async ({ id, token, retry = 0, maxRetries = 3 }) => {
if (retry === maxRetries) return
const {
response: { state }
} = await vehicles.vehicle({ id, token })
if (state === 'online') return
await vehicles.wake({ id, token })
await sleep(DELAY)
await wakeCar({ id, token, retry: retry + 1, maxRetries })
}
const start = async (email, password) => {
const { accessToken: token } = await oauth.token({
email,
password,
clientSecret: 'get-me-from-pastebin',
clientId: 'also-get-me-from-pastebin'
})
console.log('token', token)
const { response: cars } = await vehicles.list({ token })
console.log('cars', cars)
const { idS: id } = cars.find(car => car.displayName === 'Terry')
await wakeCar({ id, token })
const { response: state } = await vehicles.vehicleState({ id, token })
console.log('state', JSON.stringify(state, null, 2))
}
start('your-tesla@account.email', 'Y0uRP@55w0rd').catch(err => {
console.error(err)
})
API
The API is broken down into
oauth
— you provide your username and password and get back a token
.vehicles
- using the token
you interact with your vehicleslogs
- using the token
you can perform various diagnostics, but only if the token has the correct embedded permissions.
oauth
Controls how you obtain, refresh and revoke tokens.
const { oauth } = require('node-tesla-api')
token
Returns the accessToken
you will need to invoke the other api functions.
const { accessToken, refreshToken, tokenType, createdAt, expiresIn } = await oauth.token({
email: 'your-tesla@account.email',
password: 'your-password',
clientSecret: 'get-me-from-pastebin',
clientId: 'also-get-me-from-pastebin'
})
refresh
Returns a refreshed accessToken
. Use this if your original token has expired.
const { accessToken, refreshToken, tokenType, createdAt, expiresIn } = await oauth.refresh({
refreshToken: 'the refresh token you got back from the `token` function',
clientSecret: 'get-me-from-pastebin',
clientId: 'also-get-me-from-pastebin'
})
revoke
Revokes an accessToken
. Use this to log the user out.
await oauth.revoke({ token: 'the access token you got back from the `token` function' })
vehicles
Using the accessToken issued above you use the vehicles
functions to interact with your car.
const { vehicles } = require('node-tesla-api')
list
Get a list of your vehicles.
const { response: cars } = await vehicles.list({ token })
const {
id,
vehicleId,
vin,
displayName,
optionCodes,
color,
accessType,
tokens,
state,
inService,
idS,
calendarEnabled,
apiVersion,
backseatToken,
backseatTokenUpdatedAt
vehicleConfig,
} = cars[0]
vehicle
Get the basic details of a specific vehicle.
const {
id,
vehicleId,
vin,
displayName,
optionCodes,
color,
accessType,
tokens,
state,
inService,
idS,
calendarEnabled,
apiVersion,
backseatToken,
backseatTokenUpdatedAt
vehicleConfig,
} = await vehicles.vehicle({ id, token })
The keen observer will note that this is the same data as returned in the vehicle list response.
wake
When you first get the details of your car, you need to check the state
to see if it's 'online'
or not.
If the car's state
is 'asleep'
or otherwise not 'online'
then you need to wake it up before you can do anything with it.
Note You might also want to check to see if the car is inService
(true
or false
) if you are going to do something like move it, or start it.
To wake up the car you send it a wake
command as follows:
await vehicles.wake({ id, token })
Now just because you told the car to wake, doesn't mean that the car will actually wake up. Sometimes your car can go into a deep-sleep mode, or it might even be off, or disconnected from the network.
You need to keep checking the state
and reissuing the wake
command until either the car really wakes up, or decide to stop trying.
const wakeCar = async ({ id, token, retry = 0, maxRetries = 3 }) => {
if (retry === maxRetries) return
const {
response: { state }
} = await vehicles.vehicle({ id, token })
if (state === 'online') return
await vehicles.wake({ id, token })
await sleep(DELAY)
await wakeCar({ id, token, retry: retry + 1, maxRetries })
}
Then you can just call
await wakeCar({ id, token })
ToDo: Compose a high-level API that simplifies the use of this low-level API wrapper. (See issues/28)
vehicleData
Now you know how to wake your car, let's take a look at the full set of car data.
const {
response: {
id,
userId,
vehicleId,
vin,
displayName,
optionCodes,
color,
accessType,
tokens,
state,
inService,
idS,
calendarEnabled,
apiVersion,
backseatToken,
backseatTokenUpdatedAt,
vehicleConfig: {
canAcceptNavigationRequests,
canActuateTrunks,
carSpecialType,
carType,
chargePortType,
eceRestrictions,
euVehicle,
exteriorColor,
hasAirSuspension,
hasLudicrousMode,
keyVersion,
motorizedChargePort,
plg,
rearSeatHeaters,
rearSeatType,
rhd,
roofColor,
seatType,
spoilerType,
sunRoofInstalled,
thirdRowSeats,
timestamp: vcTimestamp,
useRangeBadging,
wheelType,
},
chargeState: {
batteryHeaterOn,
batteryLevel,
batteryRange,
chargeCurrentRequest,
chargeCurrentRequestMax,
chargeEnableRequest,
chargeEnergyAdded,
chargeLimitSoc,
chargeLimitSocMax,
chargeLimitSocMin,
chargeLimitSocStd,
chargeMilesAddedIdeal,
chargeMilesAddedRated,
chargePortColdWeatherMode,
chargePortDoorOpen,
chargePortLatch,
chargeRate,
chargeToMaxRange,
chargerActualCurrent,
chargerPhases,
chargerPilotCurrent,
chargerPower,
chargerVoltage,
chargingState,
connChargeCable,
estBatteryRange,
fastChargerBrand,
fastChargerPresent,
fastChargerType,
idealBatteryRange,
managedChargingActive,
managedChargingStartTime,
managedChargingUserCanceled,
maxRangeChargeCounter,
minutesToFullCharge,
notEnoughPowerToHeat: null,
scheduledChargingPending,
scheduledChargingStartTime,
timeToFullCharge,
timestamp: chsTimestamp,
tripCharging,
usableBatteryLevel,
userChargeEnableRequest
},
climateState: {
batteryHeater,
batteryHeaterNoPower,
climateKeeperMode,
defrostMode,
driverTempSetting,
fanStatus,
insideTemp,
isAutoConditioningOn,
isClimateOn,
isFrontDefrosterOn,
isPreconditioning,
isRearDefrosterOn,
leftTempDirection,
maxAvailTemp,
minAvailTemp,
outsideTemp,
passengerTempSetting,
remoteHeaterControlEnabled,
rightTempDirection
seatHeaterLeft,
seatHeaterRearCenter,
seatHeaterRearLeft,
seatHeaterRearRight,
seatHeaterRight,
sideMirrorHeaters,
timestamp: clsTimestamp,
wiperBladeHeater: false
},
driveState: {
gpsAsOf,
heading,
latitude,
longitude,
nativeLatitude,
nativeLocationSupported,
nativeLongitude,
nativeType,
power,
shiftState,
speed,
timestamp: dsTimestamp,
},
guiSettings: {
gui24HourTime,
guiChargeRateUnits,
guiDistanceUnits,
guiRangeDisplay,
guiTemperatureUnits,
showRangeUnits,
timestamp: gsTimestamp,
},
vehicleState: {
apiVersion,
autoparkStateV2,
autoparkStyle,
calendarSupported,
carVersion,
centerDisplayState,
df,
dr,
fdWindow,
fpWindow,
ft,
isUserPresent,
lastAutoparkError,
locked,
mediaState: {
remoteControlEnabled
},
notificationsSupported,
odometer,
parsedCalendarSupported,
pf,
pr,
rdWindow,
remoteStart,
remoteStartEnabled,
remoteStartSupported,
rpWindow,
rt,
sentryMode,
sentryModeAvailable,
smartSummonAvailable,
softwareUpdate: {
downloadPerc,
expectedDurationSec,
installPerc,
status,
version,
},
speedLimitMode: {
active,
currentLimitMph,
maxLimitMph,
minLimitMph,
pinCodeSet,
},
summonStandbyModeEnabled,
timestamp,
valetMode,
valetPinNeeded,
vehicleName
}
} = await vehicles.vehicleState({ id, token })
vehicleState
The car's current state (This is the same as the vehicleState
field in the response to vehicles.vehicleData()
)
const {
response: {
apiVersion,
autoparkStateV2,
autoparkStyle,
calendarSupported,
carVersion,
centreDisplayState,
df,
dr,
fdWindow,
fpWindow,
ft,
isUserPresent,
lastAutoparkError,
locked,
mediaState: {
remoteControlEnabled,
},
notificationsSupported,
odometer,
parsedCalendarSupported,
pf,
pr,
rdWindow,
remoteStart,
remoteStartEnabled,
remoteStartSupported,
rpWindow,
rt,
sentryMode,
sentryModeAvailable,
smartSummonAvailable,
softwareUpdate: {
downloadPerc,
expectedDurationSec,
installPerc,
status,
version,
},
speedLimitMode; {
active,
currentLimitMph,
maxLimitMph,
minLimitMph,
pinCodeSet,
},
summonStandbyModeEnabled,
timestamp,
valetMode,
valetPinNeeded,
vehicleName
}
} = await vehicles.vehicleState({ id, token })
vehicleConfig
The car's configuration (This is the same as the vehicleConfig
field in the response to vehicles.vehicleData()
)
const {
response: {
canAcceptNavigationRequests,
canActuateTrunks,
carSpecialType,
carType,
chargePortType,
eceRestrictions,
euVehicle,
exteriorColor,
hasAirSuspension,
hasLudicrousMode,
keyVersion,
motorizedChargePort,
plg,
rearSeatHeaters,
rearSeatType,
rhd,
roofColor,
seatType,
spoilerType,
sunRoofInstalled,
thirdRowSeats,
timestamp,
useRangeBadging,
wheelType
}
} = await vehicles.vehicleConfig({ id, token })
chargeState
The car's current charge state. (This is the same as the chargeState
field in the response to vehicles.vehicleData()
)
const {
response: {
batteryHeaterOn,
batteryLevel,
batteryRange,
chargeCurrentRequest,
chargeCurrentRequestMax,
chargeEnableRequest,
chargeEnergyAdded,
chargeLimitSoc,
chargeLimitSocMax,
chargeLimitSocMin,
chargeLimitSocStd,
chargeMilesAddedIdeal,
chargeMilesAddedRated,
chargePortColdWeatherMode,
chargePortDoorOpen,
chargePortLatch,
chargeRate,
chargeToMaxRange,
chargerActualCurrent,
chargerPhases,
chargerPilotCurrent,
chargerPower,
chargerVoltage,
chargingState,
connChargeCable,
estBatteryRange,
fastChargerBrand,
fastChargerPresent,
fastChargerType,
idealBatteryRange,
managedChargingActive,
managedChargingStartTime,
managedChargingUserCanceled,
maxRangeChargeCounter,
minutesToFullCharge,
notEnoughPowerToHeat: null,
scheduledChargingPending,
scheduledChargingStartTime,
timeToFullCharge,
timestamp,
tripCharging,
usableBatteryLevel,
userChargeEnableRequest
}
} = await vehicles.chargeState({ id, token })
climateState
The car's current climate state. (This is the same as the climateState
field in the response to vehicles.vehicleData()
)
const {
response: {
batteryHeater,
batteryHeaterNoPower,
climateKeeperMode,
defrostMode,
driverTempSetting,
fanStatus,
insideTemp,
isAutoConditioningOn,
isClimateOn,
isFrontDefrosterOn,
isPreconditioning,
isRearDefrosterOn,
leftTempDirection,
maxAvailTemp,
minAvailTemp,
outsideTemp,
passengerTempSetting,
remoteHeaterControlEnabled,
rightTempDirection
seatHeaterLeft,
seatHeaterRearCenter,
seatHeaterRearLeft,
seatHeaterRearRight,
seatHeaterRight,
sideMirrorHeaters,
timestamp,
wiperBladeHeater: false
}
} = await vehicles.climateState({ id, token })
driveState
The car's current location and driving state. (This is the same as the driveState
field in the response to vehicles.vehicleData()
)
const {
response: {
gpsAsOf,
heading,
latitude,
longitude,
nativeLatitude,
nativeLocationSupported,
nativeLongitude,
nativeType,
power,
shiftState,
speed,
timestamp
}
} = await vehicles.driveState({ id, token })
guiSettings
Localisation settings including units for distances, temperatures, and charge, as well as the clock type. (This is the same as the guiSettings
field in the response to vehicles.vehicleData()
)
const {
response: {
gui24HourTime,
guiChargeRateUnits,
guiDistanceUnits,
guiRangeDisplay,
guiTemperatureUnits,
showRangeUnits,
timestamp: gsTimestamp
}
} = await vehicles.guiSettings({ id, token })
mobileEnabled
Is mobile access enabled?
const { response: mobileEnabled } = await vehicles.mobileEnabled({ id, token })
serviceData
Current servicing data.
const {
response: {
...serviceData
}
} = await vehicles.serviceData({ id, token })
nearbyChargingSites
Lists Tesla-operated charging stations near to the car.
const {
response: {
congestionSyncTimeUtcSecs,
destinationCharging: [
{
location: {
lat: dcLat,
long: dcLong,
},
name: dcName,
type: dcType,
distanceMiles: dcDist
}
],
superchargers: [
{
location: {
lat: scLat,
long: scLong,
},
name: scName,
type: scType,
distanceMiles: scDist,
availableStalls,
totalStalls,
siteClosed
}
],
"timestamp"
}
} = await vehicles.nearbyChargingSites({ id, token })
upcomingCalendarEntries
If you have allowed your car to share your calendar then this will list your upcoming calendar entries.
const {
response: { result, reason }
} = await vehicles.upcomingCalendarEntries({ id, token })
Commands
It's great to be able to get data from your car, but what about making it do things? We already saw the wake
command above.
Commands all return a response with a result
(boolean) and, if the result
is false
, a reason
string.
honkHorn
Beeps the car's horn. toot!
const {
response: { result, reason }
} = await vehicles.honkHorn({ id, token })
flashLights
Flashes the car's headlights.
const {
response: { result, reason }
} = await vehicles.flashLights({ id, token })
autoConditioningStart
Start the car's climate control system. It will use the temperature and set-warming options you've previously set. Repeated calls to this will not cause an error, though obviously if the car's climate control system is on it's not going to turn on again.
const {
response: { result, reason }
} = await vehicles.autoConditioningStart({ id, token })
autoConditioningStop
Stop the car's climate control system.
Note: If you call this when the car's climate control system is already off you will get a ECONNABORTED
error rather than a response of { result: false, reason: 'some reason' }
.
const {
response: { result, reason }
} = await vehicles.autoConditioningStop({ id, token })
setTemps
Sets the temperature for the car's climate control system.
The request requires the parameter driverTemp
. It also accepts a passengerTemp
but only the driverTemp
is actually used right now. This may change in the future.
Notes
- The values for
driverTemp
and passengerTemp
are always in Metric (°C
) no matter what you have set in guiSettings
. - If you set the temperature very low or very high the HVAC system will start heating or cooling immediately.
const driverTemp = 23.4
const {
response: { result, reason }
} = await vehicles.setTemps({ id, token, driverTemp })
setPreconditoningMax
Toggles the climate controls between Max Defrost and the previous setting.
You can pass on: true
, or 'on: false' to this multiple times, without error.
const {
response: { result, reason }
} = await vehicles.setPreconditioningMax({ id, token, on: true })
setSeatHeater
Sets the seat heater level for the nominated seat.
Note You must have already turned the car's climate system on first with autoConditioningStart
for this to work. If you don't you'll get an error. Also in testing I found this API call quite prone to timeout errors.
Value | Seat |
---|
0 | front left |
1 | front right |
2 | rear left |
3 | rear center |
4 | rear right |
const {
response: { result, reason }
} = await vehicles.setSeatHeater({ id, token, heater: 0, level: 1 })
setSteeringWheelHeater
Turns the steering wheel heater on or off.
Note You must have already turned the car's climate system on first with autoConditioningStart
for this to work. If you don't you'll get an error.
Also Note I am not sure that my Model 3 even has a steering wheel heater so all I get from this is a timeout error.
const {
response: { result, reason }
} = await vehicles.setSteeringWheelHeater({ id, token, on: true })
mediaTogglePlayback
Either toggles the media between playing
and paused
, or if the car is tuned to a radio station, this mutes or un-mutes the audio.
Note You, or someone, must have turned the car on and be sitting in it for this to work. If you don't you'll get a 'user_not_present' error.
const {
response: { result, reason }
} = await vehicles.mediaTogglePlayback({ id, token })
mediaNextTrack
Skips the media to the next track, unless the car is tuned to a radio station.
Note You, or someone, must have turned the car on and be sitting in it for this to work. If you don't you'll get a 'user_not_present' error.
const {
response: { result, reason }
} = await vehicles.mediaNextTrack({ id, token })
mediaPrevTrack
Skips the media to the previous track, unless the car is tuned to a radio station.
Note You, or someone, must have turned the car on and be sitting in it for this to work. If you don't you'll get a 'user_not_present' error.
const {
response: { result, reason }
} = await vehicles.mediaPrevTrack({ id, token })
mediaNextFav
Skips the media to the next 'favourite', however that is defined, e.g if the car is tuned to a radio station, it switches to the next radio station in your favourites.
Note You, or someone, must have turned the car on and be sitting in it for this to work. If you don't you'll get a 'user_not_present' error.
const {
response: { result, reason }
} = await vehicles.mediaNextFav({ id, token })
mediaPrevFav
Skips the media to the previous 'favourite', however that is defined, e.g if the car is tuned to a radio station, it switches to the previous radio station in your favourites.
Note You, or someone, must have turned the car on and be sitting in it for this to work. If you don't you'll get a 'user_not_present' error.
const {
response: { result, reason }
} = await vehicles.mediaPrevFav({ id, token })
mediaVolumeUp
Turns the volume up.
Note You, or someone, must have turned the car on and be sitting in it for this to work. If you don't you'll get a 'user_not_present' error.
const {
response: { result, reason }
} = await vehicles.mediaVolumeUp({ id, token })
mediaVolumeDown
Turns the volume down.
Note You, or someone, must have turned the car on and be sitting in it for this to work. If you don't you'll get a 'user_not_present' error.
const {
response: { result, reason }
} = await vehicles.mediaVolumeDown({ id, token })
share
Share a video url, or navigation destination. If the car is in 'theatre mode' the url will launch the appropriate player. If the value
you send is a street address, then it will set the navigation system to navigate to that address.
If the car can't understand the value you send it will return an error.
const value = '1 Commonwealth Avenue, Yarralumla ACT 2600, Australia'
const {
response: { result, reason }
} = await vehicles.share({ id, token, value, locale: 'en-AU' })
setSentryMode
Turn sentry mode on or off.
const {
response: { result, reason }
} = await vehicles.setSentryMode({ id, token, on: true })
actuateTrunk
Opens (or 'pops' depending on your hardware) the 'frunk' or boot.
const {
response: { result, reason }
} = await vehicles.actuateTrunk({ id, token, which: 'front' })
windowControl
Lets you 'vent' or 'close' the car's windows.
Note to 'close' the windows you must also provide a gps location that's close to the car. You can get the car's current gps location from vehicles.driveState
. Alternatively you could issue a lock
command which will also close the windows if the car was not locked.
const {
response: { result, reason }
} = await vehicles.windowControl({ id, token, command: 'vent' })
or
const {
response: { result, reason }
} = await vehicles.windowControl({ id, token, command: 'close', lat: -35.297476, lon: 149.12579 })
sunRoofControl
Lets you 'vent' or 'close' the car's sun roof if it has a sun roof.
const {
response: { result, reason }
} = await vehicles.sunRoofControl({ id, token, state: 'vent' })
triggerHomelink
Lets you trigger the HomeLink controller if it is connected.
Note You must also provide a gps location that's close to the homelink device.
const {
response: { result, reason }
} = await vehicles.triggerHomelink({ id, token, lat: -35.297476, lon: 149.12579 })
scheduleSoftwareUpdate
If you have a pending software update, this command lets you schedule a time for the install to happen. If you do not provide an offset then it will attempt to install immediately.
If there is no software update available you'll get a result: false
and reason: update_not_available
.
const {
response: { result, reason }
} = await vehicles.scheduleSoftwareUpdate({ id, token })
or, to install in an hour:
const {
response: { result, reason }
} = await vehicles.scheduleSoftwareUpdate({ id, token, offset: 3600 })
cancelSoftwareUpdate
If you have a pending software update, and you've scheduled an installation time, then this lets you cancel that installation.
If there is no software update scheduled you'll get a result: false
and reason: no_update_scheduled
.
const {
response: { result, reason }
} = await vehicles.cancelSoftwareUpdate({ id, token })
setValetMode
Valet Mode limits the car's top speed to 110 Kilometres per hour and 80kW of acceleration power. It also disables Homelink, Bluetooth, and Wifi settings, as well as the ability to disable mobile access to the car.
It also hides your favourites, as well as your home, and work locations in navigation.
If you provide a password then anyone with that password, (a numeric PIN) can disable Valet Mode from the screen in the car. You can always disable Valet Mode from the API without a password however. It only applies to the person driving your car.
const {
response: { result, reason }
} = await vehicles.setValetMode({ id, token, on: true, password: 1234 })
resetValetPin
Clears the Valet Mode PIN
, meaning that once Valet Mode is disabled, you can enable it with or without a new PIN
.
const {
response: { result, reason }
} = await vehicles.resetValetPin({ id, token })
logs
Using the accessToken
issued above, if you have the appropriate 'entitlements' (defined by Tesla and embedded into the token Tesla give you) you can you use the logs
functions to perform diagnostics on your account. Use logs.getEntitlements
to check if you have the right permissions.
const { logs } = require('node-tesla-api')
getEntitlements
Does your token
allow you to perform diagnostics?
const {
response: { eligible }
} = await logs.getEntitlements({ token })
getLogs
If you are eligible for logs you can get the account logs associated with your token. hint you probably are not eligible.
const {
response: { result, reason }
} = await logs.getLogs({ token })
sendEntitlements
It's hard to know what this does.
const {
response: { result, reason }
} = await logs.sendEntitlements({ token })
Development
branch | status | coverage | audit | notes |
---|
develop | ![CircleCI](https://circleci.com/gh/davesag/node-tesla-api/tree/develop.svg?style=svg) | ![codecov](https://codecov.io/gh/davesag/node-tesla-api/branch/develop/graph/badge.svg) | ![Vulnerabilities](https://snyk.io/test/github/davesag/node-tesla-api/develop/badge.svg) | work in progress |
main | ![CircleCI](https://circleci.com/gh/davesag/node-tesla-api/tree/main.svg?style=svg) | ![codecov](https://codecov.io/gh/davesag/node-tesla-api/branch/main/graph/badge.svg) | ![Vulnerabilities](https://snyk.io/test/github/davesag/node-tesla-api/main/badge.svg) | latest stable release |
Prerequisites
- NodeJS. I use
nvm
to manage Node versions — brew install nvm
.
Install dependencies
npm install
Linting
npm run lint
Note this will also run whenever you commit file changes.
Formatting via Prettier
npm run prettier
Note: this will also run whenever you commit file changes.
Testing
npm test
or with code coverage
npm run test:unit:cov
Contributing
Please see the contributing notes.
To Do
- Cross check the API with the latest updates
- Add API unit tests and bring test coverage to 100%
- DRY up the code some more
- Improve documentation