A reverse engineered interface for the BMW i3 Electric Car, created initially by Terence Eden, with some code modified by Quentin Stafford-Fraser.
These API calls are designed to allow you to interact with your BMW i3. They were reverse engineered from the official BMW i Remote Android app.
Your use of these API calls is entirely at your own risk. They are neither officially provided nor sanctioned.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
There are three API servers.
https://b2vapi.bmwgroup.cn:8592Chinahttps://b2vapi.bmwgroup.usUSAhttps://b2vapi.bmwgroup.comEurope / Rest of World
In order to authenticate against the API you will need to be registered on BMW's Connected Drive service.
You will need:
- Your ConnectedDrive registered email address.
- Your ConnectedDrive registered password.
- The i Remote API Key.
- The i Remote API Secret.
You can get the i Remote details from either decompiling the Android App or from intercepting communications between your phone and the BMW server. This is left as an exercise for the reader ☺
Firstly, we use Basic authentication. That means taking the API Key and Secret and Base64 encoding them.
So key:secret becomes a2V5OnNlY3JldA==
We also need to send the following parameters as
Content-Type: application/x-www-form-urlencoded
grant_type=password
&username=whatever%40example.com
&password=p4ssw0rd
&scope=remote_services+vehicle_data
Here's how to do it with curl:
curl \
-H "Authorization: Basic a2V5OnNlY3JldA==" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=password&username=whatever%40example.com&password=p4ssw0rd&scope=remote_services+vehicle_data" \
"https://b2vapi.bmwgroup.com/webapi/oauth/token/"
If everything has worked, you should get back the following JSON:
{
"access_token": "RCQ1hLP4AFaUBW9BjcPUN3i4WgkwF90R",
"token_type": "Bearer",
"expires_in": 28800,
"refresh_token": "7WgKmEJ2kD1ydl9Hefp01eS8qDGzKnzjeORpA6vtsoFIEanz",
"scope": "vehicle_data remote_services"
}
You must include
Authorization: Bearer RCQ1hLP4AFaUBW9BjcPUN3i4WgkwF90R
in your headers with every request.
The expires_in is in seconds - giving you 8 hours before you have to renew the token.
I've no idea what the refresh_token is for. Once the access_token expires, you can simply re-authenticate and gain a new one.
You must include
Authorization: Bearer RCQ1hLP4AFaUBW9BjcPUN3i4WgkwF90R
in your headers with every request.
/webapi/v1/user/vehicles/- Remember to include the
Authorization: Bearerheader.
- Remember to include the
Returns a list of entries, one per registered vehicle.
[
{
"remote360": "NOT_SUPPORTED",
"chargingControl": "WEEKLY_PLANNER",
"countryCode": "V1-UK",
"hornBlow": "NOT_SUPPORTED",
"brand": "BMW_I",
"smartSolution": "NOT_SUPPORTED",
"hasAlarmSystem": true,
"climateControl": "NOT_SUPPORTED",
"doorUnlock": "ACTIVATED",
"rangeMap": "RANGE_POLYGON",
"climateFunction": "AIRCONDITIONING",
"lscType": "I_LSC_IMM",
"hub": "HUB_ECE",
"statisticsAvailable": true,
"doorLock": "ACTIVATED",
"intermodalRouting": "AVAILABLE",
"model": "I3 +REX",
"vehicleFinder": "ACTIVATED",
"color": "SOLARORANGE MET. M. AKZE",
"vin": "WAB1C23456V123456",
"bodytype": "I01",
"chargeNow": "NOT_SUPPORTED",
"supportedChargingModes": [
"AC_LOW",
"AC_HIGH",
"DC"
],
"sendPoi": "ACTIVATED",
"yearOfConstruction": 2014,
"climateNow": "ACTIVATED",
"lightFlash": "ACTIVATED",
"driveTrain": "BEV_REX",
"licensePlate": "AB64 1BC",
"statisticsCommunityEnabled": true,
"colorCode": "B78",
"onlineSearchMode": "MAP",
"dealer": {
"name": "BMW UK Ltd",
"country": "GB",
"postalCode": "GU14 0FB",
"city": "Farnborough",
"street": "Summit ONE"
},
"lastDestinations": "SUPPORTED"
}
]
The most important thing here is the VIN - Vehicle Identification Number. You'll need that for all the other API calls as well as the Authorization Bearer.
/webapi/v1/user/vehicles/:VIN/status- Where
:VINis your vehicle's VIN. - Remember to include the
Authorization: Bearerheader.
- Where
{
"vehicleStatus": {
"vin": "WAB1C23456V123456",
"mileage": 1234,
"updateReason": "VEHICLE_SHUTDOWN_SECURED",
"updateTime": "2015-10-30T18:45:04+0100",
"doorDriverFront": "CLOSED",
"doorDriverRear": "CLOSED",
"doorPassengerFront": "CLOSED",
"doorPassengerRear": "CLOSED",
"windowDriverFront": "CLOSED",
"windowDriverRear": "CLOSED",
"windowPassengerFront": "CLOSED",
"windowPassengerRear": "CLOSED",
"trunk": "CLOSED",
"rearWindow": "INVALID",
"convertibleRoofState": "INVALID",
"hood": "CLOSED",
"doorLockState": "SECURED",
"parkingLight": "OFF",
"positionLight": "OFF",
"remainingFuel": 8.9,
"remainingRangeElectric": 73,
"remainingRangeElectricMls": 45,
"remainingRangeFuel": 126,
"remainingRangeFuelMls": 78,
"maxRangeElectric": 134,
"maxRangeElectricMls": 83,
"fuelPercent": 99,
"maxFuel": 9,
"connectionStatus": "DISCONNECTED",
"chargingStatus": "INVALID",
"chargingLevelHv": 58,
"lastChargingEndReason": "UNKNOWN",
"lastChargingEndResult": "FAILED",
"position": {
"lat": 51.123456,
"lon": -1.2345678,
"heading": 211,
"status": "OK"
},
"chargingTimeRemaining": 45,
"internalDataTimeUTC": "2015-10-30T18:47:44"
}
}
mileageis in Km.remainingFuelis in Litres.maxRangeElectricis in Km.maxRangeElectricMlsis in miles.chargingLevelHvis the percentage of charge left in the (High voltage?) battery.maxFuelis in Litres.headingis in degrees.chargingTimeRemainingis in minutes. Please mind that this value is only available if car is actively charging otherwise it is not present in the response.
Valid chargingStatus values appear to be:
CHARGINGERRORFINISHED_FULLY_CHARGEDFINISHED_NOT_FULLINVALIDNOT_CHARGINGWAITING_FOR_CHARGING
Valid connectionStatus values appear to be:
CHARGING_DONECHARGING_INTERRUPED[sic]CHARGING_PAUSEDCHARGIN_STARTED[sic] TYPO should beCHARGING_STARTEDCYCLIC_RECHARGINGDISCONNECTEDDOOR_STATE_CHANGEDNO_CYCLIC_RECHARGINGNO_LSC_TRIGGERON_DEMANDPREDICTION_UPDATETEMPORARY_POWER_SUPPLY_FAILUREUNKNOWNVEHICLE_MOVINGVEHICLE_SECUREDVEHICLE_SHUTDOWNVEHICLE_SHUTDOWN_SECUREDVEHICLE_UNSECURED
Valid doorLockState values appear to be:
UNLOCKEDLOCKED(remotely)SECURED(with key fob)
Shows the details about your most recent trip.
/webapi/v1/user/vehicles/:VIN/statistics/lastTrip- Where
:VINis your vehicle's VIN. - Remember to include the
Authorization: Bearerheader.
- Where
{
"lastTrip":{
"efficiencyValue":0.53,
"totalDistance":141,
"electricDistance":100.1,
"avgElectricConsumption":16.6,
"avgRecuperation":2,
"drivingModeValue":0,
"accelerationValue":0.39,
"anticipationValue":0.81,
"totalConsumptionValue":0.79,
"auxiliaryConsumptionValue":0.66,
"avgCombinedConsumption":1.9,
"electricDistanceRatio":71,
"savedFuel":0,
"date":"2015-12-01T20:44:00+0100",
"duration":124
}
}
Distances appear to be in Kilometres rather than miles, so be sure to adjust accordingly. Multiply by 0.621371 to get miles.
totalDistanceis in Km.electricDistanceis in Km.avgElectricConsumptionis in kWh/100Km.avgRecuperationis in kWh/100Km.durationis in minutes.
To convert kWh/100Km to Miles/kWh.
1 / (0.01609344 * avgElectricConsumption)
Shows when the car is scheduled to charge.
/webapi/v1/user/vehicles/:VIN/chargingprofile- Where
:VINis your vehicle's VIN. - Remember to include the
Authorization: Bearerheader.
- Where
{
"weeklyPlanner":{
"climatizationEnabled":true,
"chargingMode":"DELAYED_CHARGING",
"chargingPreferences":"CHARGING_WINDOW",
"timer1":{
"departureTime":"07:30",
"timerEnabled":true,
"weekdays":[
"MONDAY"
]
},
"timer2":{
"departureTime":"13:00",
"timerEnabled":false,
"weekdays":[
"SATURDAY"
]
},
"timer3":{
"departureTime":"08:00",
"timerEnabled":false,
"weekdays":[
]
},
"overrideTimer":{
"departureTime":"07:30",
"timerEnabled":false,
"weekdays":[
"MONDAY"
]
},
"preferredChargingWindow":{
"enabled":true,
"startTime":"05:02",
"endTime":"17:31"
}
}
}
departureTimeappears to be the car's local time.
Shows the destinations you've previously sent to the car.
/webapi/v1/user/vehicles/:VIN/destinations- Where
:VINis your vehicle's VIN. - Remember to include the
Authorization: Bearerheader.
- Where
{
"destinations":[
{
"lat":51.53053283691406,
"lon":-0.08362331241369247,
"country":"UNITED KINGDOM",
"city":"LONDON",
"street":"PITFIELD STREET",
"type":"DESTINATION",
"createdAt":"2015-09-25T08:06:11+0200"
}
]
}
- An array of locations.
Shows the statistics for all trips taken in the vehicle.
/webapi/v1/user/vehicles/:VIN/statistics/allTrips- Where
:VINis your vehicle's VIN. - Remember to include the
Authorization: Bearerheader.
- Where
{
"allTrips": {
"avgElectricConsumption": {
"communityLow": 0,
"communityAverage": 16.33,
"communityHigh": 35.53,
"userAverage": 14.76
},
"avgRecuperation": {
"communityLow": 0,
"communityAverage": 3.76,
"communityHigh": 14.03,
"userAverage": 2.3
},
"chargecycleRange": {
"communityAverage": 121.58,
"communityHigh": 200,
"userAverage": 72.62,
"userHigh": 135,
"userCurrentChargeCycle": 60
},
"totalElectricDistance": {
"communityLow": 1,
"communityAverage": 12293.65,
"communityHigh": 77533.6,
"userTotal": 3158.66
},
"avgCombinedConsumption": {
"communityLow": 0,
"communityAverage": 1.21,
"communityHigh": 6.2,
"userAverage": 0.36
},
"batterySizeMax": 35820,
"savedCO2": 87.58,
"savedCO2greenEnergy": 515.177,
"totalSavedFuel": 0,
"resetDate": "1970-01-01T01:00:00+0100"
}
}
chargecycleRangeis in Km.totalElectricDistanceis in Km.savedCO2is in kg.savedCO2greenEnergyis in kg.batterySizeMaxis in Wh.
I'm not sure what units of the other values are.
Generate a polyline displaying the predicted range of the vehicle.
/webapi/v1/user/vehicles/:VIN/rangemap- Where
:VINis your vehicle's VIN. - Remember to include the
Authorization: Bearerheader.
- Where
{
"rangemap": {
"center": {
"lat": 51.123456,
"lon": -1.2345678
},
"quality": "AVERAGE",
"rangemaps": [
{
"type": "ECO_PRO_PLUS",
"polyline": [
{
"lat": 51.6991281509399,
"lon": -2.00423240661621
},
{
"lat": 51.6909098625183,
"lon": -1.91526889801025
},
...
]
},
{
"type": "COMFORT",
"polyline": [
{
"lat": 51.7212295532227,
"lon": -1.7363977432251
},
{
"lat": 51.6991496086121,
"lon": -1.73077583312988
},
...
]
}
]
}
}
ECO_PRO_PLUSdriving using the efficient Eco mode.COMFORTdriving using comfort mode.
Get maximum state of charge of your battery and some redundant data.
The URL is different from the other API servers:
https://www.bmw-connecteddrive.de/api/vehicle/navigation/v1/VIN- Where
VINis your vehicle's VIN. - Use the same
Authorization: Bearerheader as with API Server
- Where
example data from a PHEV:
{
"latitude" : 62.250584,
"longitude" : 3.7717776,
"isoCountryCode" : "DEU",
"auxPowerRegular" : 1.4,
"auxPowerEcoPro" : 1.2,
"auxPowerEcoProPlus" : 0.4,
"soc" : 4.019000053405762,
"socMax" : 5.6,
"eco" : "1c94,1206,d9d,d0f,cd3,cf9,e2d,f49,10ec,10ec,117c",
"norm" : "1e15,12f9,e54,dbf,d82,da7,eed,1017,11d0,1483,19a9",
"ecoEv" : "c31,799,5c5,5a5,5ae,5fc,6fb,861,9df,9df,9df",
"normEv" : "cd5,800,613,5f1,5fc,64c,759,8d2,a65,bf8,e7d",
"vehicleMass" : "1560",
"kAccReg" : "1440000",
"kDecReg" : "3420000",
"kAccEco" : "1548000",
"kDecEco" : "3240000",
"kUp" : "1800000",
"kDown" : "2700000",
"driveTrain" : "phev_otto",
"pendingUpdate" : false,
"vehicleTracking" : true
}
socMaxcurrent maximum charge. kind of health check of you battery
Sending information to the car is slightly complicated.
Your app communicates with the API, the API then communicates with the car's 3G modem, then you have to wait for a response.
If your car is in poor coverage, you can expect significant latency. Often much higher than a typical timeout will allow for.
At a basic level, you can just send a request - for example to lock the doors, or set off-peak charging.
Shows the status of a POSTed request
/webapi/v1/user/vehicles/:VIN/serviceExecutionStatus?serviceType=:SERVICE- Where
:VINis your vehicle's VIN. - Remember to include the
Authorization: Bearerheader.
- Where
{
"executionStatus":{
"serviceType":"DOOR_LOCK",
"status":"EXECUTED",
"eventId":"123456789012345AB1CD1234@bmw.de"
}
}
Valid statuses are:
DELIVEREDEXECUTEDINITIATEDNOT_EXECUTEDPENDINGTIMED_OUT
The following are valid :SERVICE types, but may not be supported by your vehicle.
CHARGE_NOWCHARGING_CONTROLCLIMATE_CONTROLCLIMATE_NOWDOOR_LOCKDOOR_UNLOCKGET_ALL_IMAGESGET_PASSWORD_RESET_INFOGET_VEHICLESGET_VEHICLE_IMAGEGET_VEHICLE_STATUSHORN_BLOW- This is disabled in the UK market. It may be possible to enable this by registering on the German ConnectedDrive service.
- NOTE The UK Highway Code says you may not use the horn unless moving (Rule 112).
LIGHT_FLASHLOCAL_SEARCHLOCAL_SEARCH_SUGGESTIONSLOGINLOGOUTSEND_POI_TO_CARVEHICLE_FINDER
Instructs the car to perform an action.
/webapi/v1/user/vehicles/:VIN/executeService- Where
:VINis your vehicle's VIN. - Remember to include the
Authorization: Bearerheader.
- Where
These commands are all available via the API, but may not be supported by your vehicle.
These are just what I've discovered so far.
Data must be POSTed to the server.
If the vehicle is plugged in, but not charging (due to an off peak setting?) it is possible to force the car to charge.
serviceType=CHARGE_NOW
This will activate climate control within your vehicle.
It appears to be limited to the last temperature you set when you were in the car. I can't find a way to instruct the car to reach a specific temperature.
serviceType=CLIMATE_NOW
Performs central locking.
serviceType=DOOR_LOCK
This will unlock all the doors on your vehicle.
Please use extreme caution when sending this command. Ensure that you are in sight of the vehicle and are able to lock it if needed.
serviceType=DOOR_UNLOCK
If you recievce an error after DOOR_UNLOCK that looks like
{"error":{"code":500,"description":"(SmartPhoneUtil-A-2013) Action is forbidden. secretKnowledge is missing but required!"}}
An addition field must be included
serviceType=DOOR_UNLOCKbmwSkAnswer=BMW_ACCOUNT_SECURITY_QUESTION_ANSWER
If you can't find the vehicle, or need to illuminate something in its vicinity, you can briefly activate the headlights.
serviceType=LIGHT_FLASH&count=2- I assume that
countrelates to the number of seconds to keep the light on?
- I assume that
Set the peak / off peak charging schedule.
serviceType=CHARGING_CONTROL- Additional data needs to be sent as parameter
datain JSON format. The data itself is the same as returned by Get Charging Times.
- Additional data needs to be sent as parameter
# Notes:
# - Linebreaks for "data" have been added for readability only.
curl -i \
-X POST \
-H "Content-Type: application/x-www-form-urlencode" \
--data-urlencode 'serviceType=CHARGING_CONTROL' \
--data-urlencode 'data={
"weeklyPlanner":
{
"climatizationEnabled": true,
"chargingMode": "IMMEDIATE_CHARGING",
"chargingPreferences": "CHARGING_WINDOW",
"timer1":{
"departureTime": "12:30",
"timerEnabled": true,
"weekdays": []
},
"timer2":{
"weekdays": []
},
"timer3":{
"departureTime": "19:30",
"timerEnabled": false,
"weekdays": []
},
"overrideTimer": {
"weekdays": []
},
"preferredChargingWindow": {
"enabled": false,
"startTime": "00:00",
"endTime": "00:00"
}}}' \
https://b2vapi.bmwgroup.com/webapi/v1/user/vehicles/:VIN/executeService
* Instead of `weeklyPlanner` `twoTimesTimer` can be used.
# Notes:
# - Linebreaks for "data" have been added for readability only.
curl -i \
-X POST \
-H "Content-Type: application/x-www-form-urlencode" \
--data-urlencode 'serviceType=CHARGING_CONTROL' \
--data-urlencode 'data={
"twoTimesTimer":
{
"climatizationEnabled": true,
"chargingMode": "DELAYED_CHARGING",
"chargingPreferences": "CHARGING_WINDOW",
"timer1":{
"departureTime": "12:30",
"timerEnabled": true,
},
"timer2":{
"departureTime": "13:30",
"timerEnabled": false,
},
"preferredChargingWindow": {
"startTime": "00:00",
"endTime": "00:00"
}}}' \
https://b2vapi.bmwgroup.com/webapi/v1/user/vehicles/:VIN/executeService
serviceType=VEHICLE_FINDER- I'm not sure what this does.
An example response for all POST commands:
{
"executionStatus": {
"serviceType": "LIGHT_FLASH",
"status": "INITIATED",
"eventId": "123456789012345AB1CD1234@bmw.de"
}
}
Sending a POI to the car.
POST /webapi/v1/user/vehicles/:VIN/sendpoi- Where
:VINis your vehicle's VIN. - Remember to include the
Authorization: Bearerheader. - Remember to include the
Content-Type: application/x-www-form-urlencodeheader.
- Where
body={
"poi": {
"city": "abc",
"lat": nn.nnn,
"lon": nn.nnn,
"name": "name",
"rating": -1,
"postalCode": "12345",
"street": "Musterstr. 6"
}
}
HTTP 204
Using these commands you should be able to replicate the functionality of the official app.
If you spot any errors or omissions, please raise an issue or send a Pull Request.
