BMW USA Dealer Locator Source
The dealer_locator source fetches the US-market dealer roster from the public
bmwusa.com dealer locator servlet. It is specific to the us market of
projects/ai_bmw and feeds the dealers entity alongside centernet (see
CenterNet CRM Source).
Endpoint
GET https://www.bmwusa.com/bin/dealerLocatorServlet/?getdealerdetailsByRadius/57717/50000?includeSatelliteDealers=true
One request sweeps every US dealer — the servlet is queried once per partition
with a hardcoded center-of-US zip code (57717) and a 50000-mile radius, no
pagination. The response is a single ~1.9 MB JSON envelope containing ~446
dealer objects. There is no authentication.
The URL's doubled ? is sent to the wire verbatim (fetch_dealers_raw calls
http_get_with_retry directly rather than the paginated/event helpers) —
passing an explicit params dict would otherwise overwrite the baked-in query
string.
Akamai / bot detection
The site sits behind Akamai, but the discriminator is header/client
consistency, not the client itself: a plain httpx/requests GET with the
default User-Agent succeeds, while the same request carrying a spoofed
browser User-Agent hangs indefinitely (verified empirically — a browser UA
on a non-browser TLS fingerprint trips bot detection). Do not add a
browser User-Agent to this fetch; it sends only an explicit
Accept: application/json, text/plain, */* header.
Response shape
dataContent.dealerDetails.dealerDetailsObjects: [
{
centerID: "...",
newVehicleSales: [ { ... } ],
certifiedPreowned: [ { ... } ],
service: [ { ... } ],
ccrc: [ { ... } ],
},
...
]
The same centerID can appear on multiple objects (e.g. distinct site
classes), and each object carries up to four site arrays. Only
newVehicleSales sites are extracted into BmwUsaDealerEntity — a physical
dealer's certified-preowned, service, and CCRC sites are deliberately not
extracted. They are preserved verbatim in the raw response for any future use.
Assets
| Tier | Asset key | Notes |
|---|---|---|
| Raw | bmw/us/raw/dealer_locator_dealers | source=dealer_locator, resource=dealers. One row per partition (the full envelope). |
| Transformed | bmw/us/transformed/dealer_locator_dealers | One row per new-vehicle-sales site; key is (dealer_code, location_id). |
dealer_code is the parent object's centerID (not present on the site object
itself — merged in before validation); location_id is the site's own
LocationID. A dealer's ag_code (BMW AG dealer code) can differ from its
dealer_code for satellite sites.
Provenance
Migrated from the legacy S44-Automotive/bmw-dealer-data repo (a GCP Cloud
Function writing to BigQuery bmw-prod-309117.inventory.dealer_locator_data_pit).
The new ingestion queries the same public servlet directly rather than reading
the legacy table.
Backfill
Partitions from 2025-07-16 onward materialize live. Partitions before that
are backfilled from the legacy BQ table (dealer_locator_data_pit) via
reshape functions in ai_bmw.backfill, with asset-backfill defs under
defs/us/. The legacy table's history is weekly from July 2024 through
December 2025, then daily — a partition without a legacy snapshot stays
unmaterialized rather than being filled with stale or interpolated data.
Backfilled partitions lack fields the legacy table never stored — dpNumber,
businessPartnerId, and programFlags are null (or absent from the raw
envelope) on any pre-2025-07-16 partition.
Cadence
Daily at 10:00 UTC — matches the bmwusa private inventory cadence.