Skip to main content

Async Python client for the BMW CarData customer API.

Project description

bmw-cardata - BMW CarData Python Library

Async Python client for the BMW CarData customer API (REST) and the customer MQTT streaming feed.

[!IMPORTANT] The CarData REST API is rate-limited to 50 requests per day per user (BMW-side limit, not enforced by this library). Once exceeded, calls return HTTP 403 CU-429 API rate limit reached until the next day. For frequent updates use the MQTT streaming feed instead.

Prerequisites (one-time setup in the BMW customer portal)

Before this library can talk to the API you must complete a few steps in the BMW CarData customer portal:

  1. Generate a CarData client IDPortal → CarData → Create CarData Client. Note the client ID; you will pass it to DeviceCodeFlowClient.
  2. Subscribe to services — subscribe the client to both CarData API and CarData Stream. Without this the OAuth scopes (cardata:api:read, cardata:streaming:read) will not be granted and the API/stream calls will be rejected.
  3. For REST telematic data: create a container selecting the telematic keys you want. See spec/telematic_catalogue.json for valid technical_descriptor values. You can also create containers from code via CarDataClient.create_container(...).
  4. For MQTT streaming: open Configure data stream and pick the streamable keys (those with "streamable": true in spec/telematic_catalogue.json). Without this step the MQTT broker will reject the subscribe with code:128.

Notes:

  • The REST API has a daily rate limit of 50 requests per user. Prefer streaming for frequent updates.
  • Only one streaming connection per gcid is allowed at a time.
  • Refresh tokens are valid for 14 days; access/id tokens for 1 hour.

Authentication (OAuth 2.0 Device Code Flow with PKCE)

import asyncio

from bmw_cardata import DeviceCodeFlowClient


async def main() -> None:
    async with DeviceCodeFlowClient(client_id="<your-client-id>") as auth:
        device = await auth.request_device_code()
        print(f"Open {device.verification_uri} and enter code {device.user_code}")
        token = await auth.poll_for_token(device.device_code, interval=device.interval)
        print("access_token:", token.access_token)
        print("id_token:", token.id_token)
        print("gcid:", token.gcid)
        print("refresh_token:", token.refresh_token)


asyncio.run(main())

Persist the refresh_token and use it to resume the session without prompting the user again:

auth = DeviceCodeFlowClient(client_id="...", refresh_token="<stored-refresh-token>")
token = await auth.refresh()  # new access/id/refresh tokens

REST client

import asyncio

from bmw_cardata import CarDataClient
from bmw_cardata.models import CreateContainerRequest


async def main() -> None:
    async with CarDataClient(access_token="<bearer-access-token>") as client:
        containers = await client.list_containers()
        for c in containers.containers:
            print(c.container_id, c.name, c.state)

        new = await client.create_container(
            CreateContainerRequest(
                name="my-container",
                purpose="example",
                technical_descriptors=["vehicle.powertrain.electric.battery.stateOfCharge"],
            )
        )
        data = await client.get_telematic_data("WBA...", container_id=new.container_id or "")
        for key, entry in data.telematic_data.items():
            print(key, entry.value, entry.unit, entry.timestamp)


asyncio.run(main())

You can also pass an async callable that returns a fresh token (handy for refresh flows):

async def token_provider() -> str:
    return await refresh_if_needed()

client = CarDataClient(access_token=token_provider)

Streaming (MQTT)

The customer MQTT broker authenticates with the user's gcid as username and the OAuth id_token (issued with the openid scope) as password.

import asyncio

from bmw_cardata import StreamingClient


async def main() -> None:
    async with StreamingClient(gcid="<gcid>", id_token="<id-token>") as stream:
        async for message in stream.stream():  # all VINs of this gcid
            print(message.vin, message.data)


asyncio.run(main())

To subscribe to a single VIN:

async for message in stream.stream(vin="WBA..."):
    ...

Endpoints covered

REST (CarDataClient):

  • list_containers, create_container, get_container, delete_container
  • get_mappings
  • get_basic_data(vin)
  • get_telematic_data(vin, container_id)
  • get_vehicle_image(vin) → raw bytes
  • get_smart_maintenance_tyre_diagnosis(vin)
  • get_charging_history(vin, from_, to, next_token=...)
  • get_location_based_charging_settings(vin, next_token=...)

Streaming (StreamingClient):

  • stream(vin=None) — async iterator of StreamingMessage

Development

pip install -e .[dev]
ruff format src
ruff check src
ty check src

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

bmw_cardata-0.1.0a3.tar.gz (67.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

bmw_cardata-0.1.0a3-py3-none-any.whl (47.1 kB view details)

Uploaded Python 3

File details

Details for the file bmw_cardata-0.1.0a3.tar.gz.

File metadata

  • Download URL: bmw_cardata-0.1.0a3.tar.gz
  • Upload date:
  • Size: 67.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for bmw_cardata-0.1.0a3.tar.gz
Algorithm Hash digest
SHA256 2632da3ff86e18e503865bd5f0a0b8308009d1667ab722f02800c825ab7f30b5
MD5 8fd4c5b7608bb9a2b63a29f72b40d909
BLAKE2b-256 e3139ae5b6d3bc28d16bc37aea1c6b07bbf9ee9d19e380686224f544e337b72e

See more details on using hashes here.

Provenance

The following attestation bundles were made for bmw_cardata-0.1.0a3.tar.gz:

Publisher: pypi.yaml on zweckj/bmw-cardata

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file bmw_cardata-0.1.0a3-py3-none-any.whl.

File metadata

  • Download URL: bmw_cardata-0.1.0a3-py3-none-any.whl
  • Upload date:
  • Size: 47.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for bmw_cardata-0.1.0a3-py3-none-any.whl
Algorithm Hash digest
SHA256 4ddd68ff3a356b6b779e6dec9f33ce1f15c874d9aefe9c2d014624da786ac48d
MD5 8c17eb386adbc288936c2b7132a0d96b
BLAKE2b-256 3b443fb73f85f0207ffe1287d516c2289c6421f98f71b27f5be74deca14fe7c6

See more details on using hashes here.

Provenance

The following attestation bundles were made for bmw_cardata-0.1.0a3-py3-none-any.whl:

Publisher: pypi.yaml on zweckj/bmw-cardata

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page