Reverse Engineering the BMW i3 API

by @edent | , , , , , , | 44 comments | Read ~28,294 times.

I'm really enjoying driving the BMW i3.

I'd love to have it tweet its driving efficiency, or upload its location to my server, or let me turn on its air-conditioning when the temperature gets too warm - there are a hundred interesting things to do with the car's data. The official app has some of these features - but is slow, ugly, and a pain to use.

BMW used to have an API available for hackathons, but they shut it down.

Right! Only one thing to do - HACK THE MAINFRAME*!

*No mainframes were harmed in the writing of this blog post


The ultimate aim is to get access to all the car's functions which are exposed by the API - so I can do something like this:


For this relatively simple bit of work you will need...

With Packet Capture installed, close all other apps, start the logging, then log in to the i Remote app. Click around on a few functions, then flip back to the Packet Capture app.

You'll see a bunch of API calls which have been captured.

Click on one and you'll see the full API call, plus the JSON response. I've stripped out some of my personal details.
BMW API Details-fs8

The most important thing you'll need is the Authorization: Bearer token. This is a 32 character alphanumeric string.


The latest version can always be found on GitHub.

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.



There are three API servers.

  • China
  • USA
  • Europe / 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:

  1. Your ConnectedDrive registered email address.
  2. Your ConnectedDrive registered password.
  3. The i Remote API Key.
  4. 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

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&" \

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.

Get Vehicle Data

  • /webapi/v1/user/vehicles/
    • Remember to include the Authorization: Bearer header.

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.


    "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"
        "internalDataTimeUTC": "2015-10-30T18:47:44"


  • mileage is in Km.
  • remainingFuel is in Litres.
  • maxRangeElectric is in Km.
  • maxRangeElectricMls is in miles.
  • chargingLevelHv is the percentage of charge left in the (High voltage?) battery.
  • maxFuel is in Litres.
  • heading is in degrees.

Valid chargingStatuses appear to be:


Valid connectionStatuses appear to be:


Get Last Trip

Shows the details about your most recent trip.

  • /webapi/v1/user/vehicles/:VIN/statistics/lastTrip
    • Where :VIN is your vehicle's VIN.
    • Remember to include the Authorization: Bearer header.




Distances appear to be in Kilometres rather than miles, so be sure to adjust accordingly. Multiply by 0.621371 to get miles.

  • totalDistance is in Km.
  • electricDistance is in Km.
  • avgElectricConsumption is in kWh/100Km.
  • avgRecuperation is in kWh/100Km.
  • duration is in minutes.

To convert kWh/100Km to Miles/kWh.

1 / (0.01609344 * avgElectricConsumption)

Get Charging Times

Shows when the car is scheduled to charge.

  • /webapi/v1/user/vehicles/:VIN/chargingprofile
    • Where :VIN is your vehicle's VIN.
    • Remember to include the Authorization: Bearer header.





  • departureTime appears to be the car's local time.

Get Vehicle Destinations

Shows the destinations you've previously sent to the car.

  • /webapi/v1/user/vehicles/:VIN/destinations
    • Where :VIN is your vehicle's VIN.
    • Remember to include the Authorization: Bearer header.


         "country":"UNITED KINGDOM",
         "street":"PITFIELD STREET",


  • An array of locations.

Get All Trip Details

Shows the statistics for all trips taken in the vehicle.

  • /webapi/v1/user/vehicles/:VIN/statistics/allTrips
    • Where :VIN is your vehicle's VIN.
    • Remember to include the Authorization: Bearer header.


    "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
        "savedCO2": 87.58,
        "savedCO2greenEnergy": 515.177,
        "totalSavedFuel": 0,
        "resetDate": "1970-01-01T01:00:00+0100"


  • chargecycleRange is in Km.
  • totalElectricDistance is in Km.

I'm not sure what units of the other values are.

Get Range Map

Generate a polyline displaying the predicted range of the vehicle.

  • /webapi/v1/user/vehicles/:VIN/rangemap
    • Where :VIN is your vehicle's VIN.
    • Remember to include the Authorization: Bearer header.


    "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_PLUS driving using the efficient Eco mode.
  • COMFORT driving using comfort mode.

Sending information to the car

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.

Get Request status

Shows the status of a POSTed request

  • /webapi/v1/user/vehicles/:VIN/serviceExecutionStatus?serviceType=:SERVICE
    • Where :VIN is your vehicle's VIN.
    • Remember to include the Authorization: Bearer header.




Valid statuses are:


The following are valid :SERVICE types, but may not be supported by your vehicle.


POST a command

Instructs the car to perform an action.

  • /webapi/v1/user/vehicles/:VIN/executeService
    • Where :VIN is your vehicle's VIN.
    • Remember to include the Authorization: Bearer header.

Available Commands

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.

Initiate Charging

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

Start Climate Control

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

Lock the doors

Performs central locking.

  • serviceType=DOOR_LOCK

Unlock the doors

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

Flash the headlights

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 count relates to the number of seconds to keep the light on?

Charging Schedule

Set the peak / off peak charging schedule.

  • serviceType=CHARGING_CONTROL
    • I haven't bothered to figure this out, but the error it returns should give you some pointers:
      "description":"(SmartPhoneUtil-A-102) Bad value(s) for parameter(s): Invalid chargingProfile, expected weeklyPlanner or twoTimesTimer"

Vehicle Finder

  • serviceType=VEHICLE_FINDER
    • I'm not sure what this does.


An example response for all POST commands:

    "executionStatus": {
        "serviceType": "LIGHT_FLASH",
        "status": "INITIATED",
        "eventId": ""

What's Next

Using these commands you should be able to replicate the functionality of the official app.

As you can see, I've managed to get my car to Tweet:

It would be lovely if BMW decided to open up an official API so that people could fiddle with their cars. The API seems secure and there's limited scope for damaging the vehicles.

I've added all the documentation to GitHub. Please raise an issue or send a Pull Request with any changes.

44 thoughts on “Reverse Engineering the BMW i3 API

  1. aokincaid says:

    This was a delightful read! Awesome work.

  2. Tomas says:

    What you described is pretty standard oAuth architecture. refresh_token is there to be able to refresh your access_token after it expires without having to send username and password again.

  3. Quentin says:

    Yes, excellent stuff - Well done! Not having an Android device, I've just used a proxy called Charles ( to get the necessary data from my iPhone app...

    1. Justin says:

      Hi quentin - I've done the same thing as you. Perhaps I am missing something or not following correctly, but I'm not seeing api key/secret in my packet capture. Any hints?

      1. quentinsf says:

        Hi Justin - I don't have it in front of me, but if memory serves, the key and secret are used to get a token which is valid for a certain period. I used the token that the app was using, initially, for the API, but also printed out its expiry time, which turned out not to be very distant.

        The app will have the same expiry time, so I kept it using the proxy and the first time after the expiry, the key and secret were visible in the exchange.

        Hope that helps!

  4. William says:

    it's really good to see you doing this. But it is lamentable - outrageous even - that BMW make it so hard. Two questions arise for me. 1. What sort of consumer pressure can BMW owners/drivers exert to get BMW to give them an open API on a car they've bought? and 2. Are there legality issues eg in US under DMCA and similar legislation, also under new emerging legislation such as TTP?

    Longer term surely open source cars will prevail over closed systems.

    1. Terence Eden says:

      Hi William,

      1. Most consumers won't care - API access is a non-issue. The best way to make consumers ask for this is by providing features which rely on this access. For example "VW's cars can do XYZ, why can't BMW's?".

      2. I am not a lawyer - I'm also based in the UK, not the USA. I suggest contacting the EFF or writing to your local politicians.

      And, yes, let's hope Open Source wins out in the end!

  5. Alex B says:

    Relevant parts of the UK's Copyright, Designs and Patents Act 1988:


    and I'd imagine BMW have a click-through license somewhere that says you won't modify or copy their stuff.

    1. Terence Eden says:

      Interesting. There was nothing in the app's T&Cs which I can see. Thanks for the info.

  6. Terence, thank you for all this. Quick question, how can I get valid API Key & API Secret if I don't have an iPhone or Android (Windows all the way here) Since no iRemote app for Windows, I was planning to build my own.

    1. Terence Eden says:

      You'll need a copy of either app, sorry.

      1. That's what I was afraid of. Thanks for the reply.

  7. anonymous coward says:

    Great work Terence, although I've been unable to capture the authentication tokens as it appears BMW have now implemented some form of certificate pinning within their app. As now once the app is opened, multiple sessions are established with but no data is transfered - as if TLS is failing in handshake stages. Any ideas how I can get hold of the API key and secret?

  8. GuiToon says:

    It's very good, can you post exemple to post command in python ?

    1. Terence Eden says:

      You'll find example code on GitHub

      1. GuiToon says:

        Hi, on the GitHub I only found GET Commands.
        I need POST commands to send serviceType=CLIMATE_NOW.
        Thank you.

        1. quentinsf says:

          That may be my fault - I re-engineered some of Terence's original code, and I only did GET. I can't remember whether his original had POSTs as well - it wouldn't be difficult to add.

  9. Rick Engle says:

    Hi Terrence, is there a discussion group set up anywhere for this?
    I've made great progress on my own app but ran into a problem. After getting the vehicle status, once I retrieve that info it doesn't seem to change when I refresh that data unless I restart my app. Is there any possibility that a time stamp needs to be included too to trigger a new vehicle status update? Looking at the iPhone iRemote traffic it uses this type of URL for its status requests:


    1. Terence Eden says:

      Hi Rick,
      Best place to discuss is on GitHub


      1. Rick Engle says:

        I may have a problem spotting where to go on GitHub but I just don't see any discussion/comment sections there. Can you point me specifically on where to go? I have a lot of questions and a lot of comments on things I've discovered in my C# app on Windows.

        1. Terence Eden says:

          Hi Rick,

          Take a look at the issues page - - for each thing you're interested in discussing, raise a separate issue.



  10. GuiToon says:

    I'm not very good for coding in Python, I need help for POST commands.

  11. Has anyone managed to packet-sniff or otherwise obtain the key & secret? Are these different for every device or can someone share them. I had no luck using tPacketCapture ( or Packet Capture (app.greyshirts.sslcapture)

    1. quentinsf says:

      I think they're basically fixed for a specific app, but we don't really want to take the responsibility of sharing them. 🙂

      What I did for my iOS phone in a similar situation was get a copy of the Charles proxy app (, run it on my laptop including SSL proxying, and on my phone (a) install the root SSL key that Charles can generate and (b) tell my phone to use the laptop as a proxy. Finally, if memory serves, when watching the current keys go by, look at their expiry time and after that has passed, fire up the app again and you'll see the key and secret being sent.

      There are ways you can do this with free open source software, but Charles is very handy if you do any kind of HTTP network monitoring and worth the price, in my opinion.

      1. Thank you Quentin. Seems a fantastic bit of software and one I am likely to purchase. I am testing on both my Android phone and my iPad. I have installed the SSL certificate after setting up the proxy connection. I can also see it making requests to the API endpoint but I am getting no data and Charles shows "SSLHandshake: Received fatal alert: certificate_unknown" via Android and "SSLHandshake: Remote host closed connection during handshake". Is this a case that the app has been updated since you managed to get it to work. As an aside, I am also using the normal Remote app rather than one for the i3 / i8 car.

        1. Just a quick update. It seems that the regular Remote app doesn't want to play nice with Charles Proxy. I had to download the i Remote app and that gave me what I needed. I can now make requests and retrieve the vehicle data for my M135i 🙂 Thank you again

          1. quentinsf says:

            Ah - well done for sorting it out before I managed to respond! Interesting to know that it works for other vehicles too!

  12. Rocco says:

    One thing that I am interested in pulling is the energy consumed while charging. I can then register historical energy consumption and that allows me to make decisions about where to charge it, when, etc = save money. I can't see that data being exposed by BMW through this way, nor any other. Am I missing something?

    1. Terence Eden says:

      No, I don't think you can see the charging rate. Best you can do is measure the percentage of the battery every minute and work it out from there.

  13. I was able to successfully use your technique to have my home automation system pre-cool my BMW 535i on hot mornings. Now my kids don't get into a hot car to wait for me to take them to camp.

    Thank you so much!

  14. Paul says:

    I am using Charles and an iPhone with the iRemote app. I am unable to get the api keys. I get the following error in Charles: "Failure No request was made. Possibly the SSL certificate was rejected." I have the Charles certificate installed on the iPhone but the iRemote app does not seem to recognize it. I know it is installed properly because the proxy works for SSL sites from the Safari browser. Anyone have any ideas?

    By the way, great work on this.

    1. Terence Eden says:

      I think the newer app uses Certificate Pinning. There's some discussion about this on the GitHub page.

  15. Hello Terence,

    this is fascinating stuff. I don't own an i3, but I am currently researching data to build an estimation model for the range of electric cars in real world settings. The car manufacturers are not easily giving these out and i found no public data for this task. I was wondering if you or anyone else was collecting regular output of the "status" API durig a trip and would be willing to share some trip histories (anonymized, of course) to an open data platform or your blog?
    I am mostly interested in the lat/long coords, combined with the state of charge, remaining fuel, a timestamp and mileage. I completely understand however if you do not want to give out coordinates near your place of residence. I then would still be interested in the remaining data, since my model could also work with a combination of mileage and charging state.
    Thank you in any case.

    Best regards,

  16. Egil Helleland says:

    Hi Terence. is this not working anymore?
    i am not that good with coding.
    is there a workaround to get data from the bmw app?
    with the ssl thing?

    Thank you for doing this work BTW!

    1. @edent says:

      I don't have this car any more. I cannot help you. If you read the GitHub pages, you may find someone who can.

    2. Hi Egil -

      I did a little work on this with Terence in the early days, but I now mostly monitor my i3 using my home automation system (which is based on Home Assistant). That has a BMW ConnectedDrive plugin, and looking more carefully at it, I see it’s based on this library (which also credits Terence’s library as being an influence!).

      Hope it’s useful!

  17. Havrla says:

    this day change in response json socMAX to socmax.

    if r.status_code == 200:
    status = r.json()
    if status.get('socmax'):
    socMax = status['socmax']
    if status.get('socMax'):
    socMax = status['socMax']'SoC Data retreived successfully')
    return socMax

    1. Havrla says:

      if r.status_code == 200:
      status = r.json()
      if status.get('socmax'):
      socMax = status['socmax']
      if status.get('socMax'):
      socMax = status['socMax']'SoC Data retreived successfully')
      return socMax

  18. Havrla says:

    API off, BMW migrate to Azure and change functional 🙁

    1. Phillip says:

      Any thoughts on how to intercept Messages that leave the telematics unit? I have a retrofited atm in my f series but have know idea what the donor vin is. Figure I can build a cheap gsm sniffer capture the calls as they egress the atm. My concern is the encryption one would hope messages are encrypted as they leave the atm. So wondering if anyone can confirm that before I waste my time trying to capture traffic.

  19. Andreas says:

    Is there a similar way to get data from the new API ?

Leave a Reply

Your email address will not be published. Required fields are marked *