Discovery Contract — bmw (global / 29 markets)
Retrospective contract — raw tier implemented before formal discovery. Gates populated from code review, local API probes (2026-06-20), GCP project inspection (
ai-app-bmw), and source review ofproduct-data-pipeline(S44-Automotive/product-data-pipeline@dev).
- OEM / market:
bmw/ global (at, au, be, bg, ch, cz, de, dk, es, fi, fr, gr, hu, ie, in, it, jp, kr, lu, nl, no, pl, pt, ro, se, si, sk, th, za) - Discovered: 2026-06-20 (retrospective)
- Gate status: ✅ unblocked — UCP and RIS bugs fixed in PR #504
Source overview
| Source | Entity | Markets | Auth | Status |
|---|---|---|---|---|
Stolo (stolo-data-service.prod.stolo.eu-central-1.aws.bmw.cloud) | inventory | all 29 except za | User-Agent + X-Api-Key | ✅ headers fixed, ECS confirmation pending |
UCP (prod.ucp.bmw.cloud) | models + features + pricing | all 29 | hardcoded SPA key | ✅ fixed in PR #504 |
c2b-services (c2b-services.bmw.com) | dealers | 28 (not kr) | public | ✅ confirmed |
BMW Korea (bmw-dealer-locator.co.kr) | dealers | kr only | public (geo-blocked) | 🔴 geo-fenced |
RIS (ris.api.bmw.com/m2m/sales/masterdata) | options | credential-scoped (currently sr-rs) | OAuth2 machine2machine | ✅ fixed in PR #504 |
BMW SA BFF (buy.bmw.co.za) | inventory | za only | anonymous JWT | ✅ confirmed locally |
Gate summary
| # | Gate | Entity | Status | Evidence |
|---|---|---|---|---|
| 1 | Pagination | stolo_inventory | 🟡 OVERRIDE | startIndex/pageSize confirmed in product-data-pipeline source |
| 2 | Enumerator (model_range) | stolo_inventory | 🟡 OVERRIDE | Facets aggregate → MARKETING_MODEL_RANGE; confirmed 200 locally |
| 3 | Enumerator (drive_type) | stolo_inventory | 🟡 OVERRIDE | Drive-type fan-out for ranges >1200; confirmed in source |
| 4 | Enumerator (model_id) | ucp_model_details | 🟡 OVERRIDE | UCP matrix drives fan-out; fixed in PR #504 |
| 5 | Enumerator (ris_model_id) | ris_options | 🟡 OVERRIDE | /vehicle-configurations/v1/models; fixed in PR #504 |
| 6 | Multi-value | stolo_inventory | ✅ N/A | One model_range per request |
| 7 | Body shape | stolo | ✅ PASS | X-Api-Key added; 200 for de-de/ko-kr/en-in locally; ZA=404 (not a Stolo market) |
| 8 | Body shape | ucp | ✅ PASS | Endpoint fixed to prod.ucp.bmw.cloud; 200 confirmed locally (PR #504) |
| 9 | Body shape | c2b_dealers | ✅ PASS | 200 from local + ECS; DE=652, ZA=67, AU=56 |
| 10 | Body shape | ris | ✅ PASS | Hostname + scope fixed in PR #504; 200 confirmed on branch deployment |
| 11 | Body shape | za_inventory | ✅ PASS | 200 locally; 84 vehicles via buy.bmw.co.za BFF |
| 12 | Data quality | c2b_dealers | ✅ PASS | ZA envelope identical to DE; no schema divergence |
| 13 | Data quality | za_inventory | ✅ PASS | 84 vehicles; id/vin/model/title/price/bodyType all present |
| 14 | Fixture | stolo_inventory_facets | ⬜ TODO | Unblocked post-header-fix; needs branch deployment capture |
| 15 | Fixture | stolo_inventory | ⬜ TODO | Same — needs branch deployment |
| 16 | Fixture | ucp_model_matrix | ⬜ TODO | Endpoint fixed; needs capture |
| 17 | Fixture | ucp_model_details | ⬜ TODO | Endpoint fixed; needs capture |
| 18 | Fixture | ucp_pricing | ⬜ TODO | Endpoint fixed; needs capture |
| 19 | Fixture | c2b_dealers_de | ✅ PASS | tests/data/bmw_c2b_dealers_de_sample.json |
| 20 | Fixture | c2b_dealers_za | ✅ PASS | tests/data/bmw_c2b_dealers_za_sample.json |
| 21 | Fixture | korea_dealers | 🔴 FAIL | Geo-fenced; empty body outside Korean IP space |
| 22 | Fixture | ris_model_ids | ⬜ TODO | Hostname/scope fixed; needs VPC capture |
| 23 | Fixture | ris_options | ⬜ TODO | Hostname/scope fixed; needs VPC capture |
| 24 | Fixture | za_inventory | ⬜ TODO | Endpoint confirmed; needs branch deployment capture |
| 25 | Postman record | stolo | ✅ PASS | Collection populated in PR #504 |
| 26 | Postman record | ucp | ✅ PASS | Collection populated in PR #504 |
| 27 | Postman record | c2b_dealers | ✅ PASS | Collection populated in PR #504 |
| 28 | Postman record | ris | ✅ PASS | Collection populated in PR #504 |
| 29 | Postman record | za_inventory | ✅ PASS | Collection populated in PR #504 |
Per-entity endpoints
| Entity | Endpoint (method + URL) | Auth | Fixture | Notes |
|---|---|---|---|---|
| stolo_inventory_facets | POST stolo-data-service.prod.stolo.eu-central-1.aws.bmw.cloud/vehiclesearch/aggregate/{locale}/stocklocator | User-Agent + X-Api-Key | ⬜ pending | Returns MARKETING_SERIES→MARKETING_MODEL_RANGE facet tree |
| stolo_inventory | POST …/vehiclesearch/search/{locale}/stocklocator | User-Agent + X-Api-Key | ⬜ pending | Paginates via startIndex/maxResults; cap 1200; fan-out per model_range |
| ucp_model_matrix | GET prod.ucp.bmw.cloud/model-matrices/vehicle-trees/{vehicle-tree}/sources/{source}/brands/{brand}/countries/{country}/effect-dates/{date}/order-dates/{date} | SPA key | ❌ pending | vehicle-tree: connext-bmw (std) or connext-bmw-datafox (in/rs/th/tr) |
| ucp_model_details | GET prod.ucp.bmw.cloud/localisations/overridden-vehicle-data/sources/{source}/brands/{brand}/countries/{country}/effect-dates/{date}/order-dates/{date}/applications/{application}/models/{modelCode}/options/languages/{language} | SPA key | ❌ pending | application: connext (std) or connext-datafox |
| ucp_pricing | POST prod.ucp.bmw.cloud/pricing/calculation/public-calculation/price-lists/{source},con/brands/{brand}/countries/{country} | SPA key | ❌ pending | Extra params for CO2/NOx/Hubraum in AT/ES/FI/GR/IE/NL/NO/PT |
| c2b_dealers | GET c2b-services.bmw.com/c2b-localsearch/services/api/v4/clients/BMWSTAGE2_DLO/-/pois | public | ✅ bmw_c2b_dealers_de_sample.json | Params: brand, language, country, lat=0, lng=0, maxResults=700 |
| korea_dealers | GET bmw-dealer-locator.co.kr/api/getList | public (geo-block) | ❌ pending | Korea only; geo-fenced |
| ris_model_ids | GET ris.api.bmw.com/m2m/sales/masterdata/vehicle-configurations/v1/models | OAuth2 machine2machine + apikey | ❌ pending | datafox markets only (in, th); params: brand, country, source=datafox |
| ris_options | GET ris.api.bmw.com/m2m/sales/masterdata/vehicle-configurations/v1/models/{model_id}/options | OAuth2 + apikey | ❌ pending | sr-rs only |
| za_inventory | POST buy.bmw.co.za/api/product/products | anonymous JWT from /api/load-page/bmw | ⬜ pending | handle=bto, storefrontCode=bmw_vehicle, first=500; 84 vehicles |
Fixed bugs (PR #504)
All bugs listed below were fixed in PR #504 and are kept here for historical reference.
- UCP wrong endpoint (gates 8, 16–18): Was using
api.bmw.com; corrected toprod.ucp.bmw.cloudwith brand/country/date path structure and Akamai session for WAF bypass. - RIS wrong hostname + scope (gates 10, 22–23): Was
ris.bmwgroup.netwithscope=openid; corrected toris.api.bmw.com/m2m/sales/masterdatawithscope=machine2machine. - Stolo ZA not a supported market:
en-zareturns 404 from Stolo; replaced byBmwZaInventoryRawComponentusing the BMW SA BFF (buy.bmw.co.za).
South Africa (za) status
| Source | Status |
|---|---|
| ZA inventory (buy.bmw.co.za BFF) | ✅ Implemented — 84 vehicles, anonymous JWT auth |
| C2B dealers | ✅ Confirmed — 67 dealers |
| Stolo | ❌ Not supported — 404 |
| UCP models/pricing | ⬜ Pending UCP endpoint fix |
Cross-source identity / FK alignment
Not applicable at raw tier. FK alignment is the responsibility of the transformed tier.
Overrides
| Gate | Override reason | Approved by |
|---|---|---|
| 1 — Pagination | Confirmed in product-data-pipeline source; startIndex/pageSize contract proven | pmcguire |
| 2 — Enumerator (model_range) | Locally confirmed 200 with correct Stolo headers; facets aggregate returns correct tree | pmcguire |
| 3 — Enumerator (drive_type) | Pattern confirmed in product-data-pipeline source | pmcguire |
| 4 — Enumerator (model_id) | UCP matrix structure confirmed in product-data-pipeline source; endpoint fix needed | pmcguire |
| 5 — Enumerator (ris_model_id) | RIS model IDs endpoint confirmed in product-data-pipeline source; hostname fix needed | pmcguire |