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 built-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. This was 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 structure
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.
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.
The live asset issues one full-US sweep and stores every dealer in a single raw
row, so the backfill sets bq_aggregate_partition to emit one row per partition
too — omitting it produces one row per dealer, a structure no live partition has.
Backfilled partitions yield 371 transformed dealers for 2026-06-25, matching live.
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 schedule.