BMW CenterNet CRM Source
The centernet source fetches per-dealer CRM-vendor metadata from BMW's
CenterNet WCF service. It is specific to the us market of projects/ai_bmw
and feeds the dealers entity alongside dealer_locator (see
Dealer Locator Source).
Endpoint
GET https://aws-p-app1.bmwcenternet.com/center_services/api/CrmVendorApi?access_key=<KEY>&line_make=BMW
Private API — auth is a static access_key query parameter, no OAuth, no
rotation. One request returns the full BMW CRM-vendor roster; there is no
pagination.
Content negotiation
CenterNet is a WCF service that serves XML to a browser-like Accept header.
The client pins Accept: application/json explicitly to get JSON back.
Response structure
A JSON array, one object per dealer outlet:
| Field | Type | Notes |
|---|---|---|
center_code | string | 5-char dealer id + 2-char location id concatenated (e.g. "6577501" → dealer 65775 / location 01). Split into dealer_code + location_id so the entity joins against BmwUsaDealerEntity. |
crm_tool | string | |
nextgen_crm | "Y" / "N" | Coerced to bool. |
data_sharing | string | |
marketing_optin | "Y" / "N" | Coerced to bool. |
date_updated | MM/DD/YYYY or null | Parsed to a date; an unparseable value is coerced to null rather than raising. |
Assets
| Tier | Asset key | Notes |
|---|---|---|
| Raw | bmw/us/raw/centernet_crm | source=centernet, resource=crm. |
| Transformed | bmw/us/transformed/centernet_crm_dealers | Keyed on center_code. |
Security — access key redaction
The access_key is sent on the wire but must never be persisted. It is added
only to the live request's query params, never to the params dict passed
through to the raw-row writer — the persisted raw row's _request_url /
_request_params carry only line_make, never the key. See
ai_bmw/sources/centernet.py::fetch_crm_raw.
Secret
| Secrets Manager name | dagster/bmw/crm-vendor-api-key |
| Declared in | deployments/aws/terraform/solutions/secrets/secrets.tf (aws_secretsmanager_secret.bmw_crm_vendor_api_key) — shell only; the value is filled manually after the secrets solution applies |
| Value source | Copied manually from GCP Secret Manager crm-vendor-api-key in project bmw-prod-309117 |
| ECS env var | BMW_CRM_VENDOR_API_KEY, resolved from the secret via BMW_CRM_VENDOR_API_KEY_SECRET_ID (set in projects/ai_bmw/container_context.yaml) |
| Local development | export BMW_CRM_VENDOR_API_KEY=... directly — no secret-manager lookup locally |
Backfill
Partitions from 2025-07-16 onward materialize live. Partitions before that
are backfilled from the legacy BQ table 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.
Backfilled partitions lack fields the legacy table never stored:
nextgen_crm, data_sharing, marketing_optin, and date_updated are all
null on any pre-2025-07-16 partition.
There is no legacy CenterNet table
The backfill reads bmw-prod-309117.inventory.dealer_locator_data_pit — the same
table the dealer-locator backfill uses — because the legacy collector never had a
CenterNet feed. Its only CRM data is a single crm_tool STRING nested in that
table's repeated sites record, so _reshape_centernet_crm synthesizes the
vendor array from sites[].crm_tool, skipping "Not Found" (which meant the
vendor lookup returned nothing, so materializing it would invent an assignment the
collector never observed).
Backfilled partitions therefore carry fewer dealers than live: 445 vs 518 on
2026-06-25, since only dealers whose locator row happened to record a usable
crm_tool can be recovered. That is the ceiling of the legacy source, not a gap to
close — bq ls over bmw-prod-309117:inventory has no center/crm table at all.
Cadence
Daily at 10:00 UTC — matches the bmwusa private inventory schedule.