Skip to main content

Discovery Contract — subaru (us)

The vetting gate between discovery and coding. This file is the human narrative + probe evidence; the authoritative gate state lives in the sidecar discovery-contract.gate.json, validated by check_contract_gate.py.

  • OEM / market: subaru / us
  • Discovered: 2026-07-03
  • Gate status: ✅ all hard gates green (check_contract_gate.pyVERDICT: PASS) — endpoints recorded in Postman (Public OEM APIs → Subaru → US)

Gate summary

#GateEntity / paramStatusEvidence
1Pagination contractinventory✅ PASS229/229 unique @ size 100
2Enumerator (model)models✅ PASS10 carline codes
3Enumerator (year)models✅ PASS59 years (1968–2026)
4Enumerator (trim)features✅ PASS6 trims / OBK 2026
5Enumerator (dealer)dealers✅ PASS643 dealer codes (US)
6Multi-value filterinventory✅ PASSdealerCode CSV batching clean
7Body shape / bot-blockinventory✅ PASS2xx all JSON (origin+referer)
8Body shape / bot-blockfeatures (configurator)✅ PASS2xx all JSON
9Data qualityinventory✅ PASSvin/tsrp/dealerCode ok (msrp=0 sentinel)
10Data qualitydealers✅ PASSlat/lon valid 200/200
11Data qualitymodels✅ PASScode/msrp ok
12Data qualityfeatures✅ PASSmodelCode FK + feature code ok
13Cross-source identity keyinventory→dealers✅ PASSdealerCode = dealer.id
14Cross-source identity keyinventory→models✅ PASSmodelCode = code
15Cross-source identity keyinventory→features✅ PASS(modelCode,trimCode)
16Cross-source identity keyfeatures→models✅ PASSmodelCode = code
17Real fixtureinventory✅ PASStests/data/us/retailer_inventory_sample.json
18Real fixturedealers✅ PASStests/data/us/dealers_by_zipcode_sample.json
19Real fixturemodels✅ PASStests/data/us/models_basicdata_sample.json
20Real fixturefeatures✅ PASStests/data/us/trims_limiteddetails_sample.json
21Postman recordinventory✅ PASSPublic OEM APIs → Subaru → US
22Postman recorddealers✅ PASSPublic OEM APIs → Subaru → US
23Postman recordmodels✅ PASSPublic OEM APIs → Subaru → US
24Postman recordfeatures✅ PASSPublic OEM APIs → Subaru → US

Per-entity endpoints

All on https://www.subaru.com. GET /services/* need only a UA; the inventory POST also needs origin + referer. No TLS impersonation required.

EntityEndpoint (method + URL)Auth / protocolFixtureNotes
inventoryPOST /services/graphql/retailerinventory/v2JSON body; needs origin+referer (else 403)retailer_inventory_sample.jsonbody {page,dealerCode(CSV),sortBy,itemsPerPage}; records pagedListWrapper.items, id vinNumber, total pagedListWrapper.pager.totalItems; price = tsrp/internetPrice, msrp=0 sentinel; destinationFee nullable
dealersGET /services/dealers/distances/by/zipcode?zipcode=&count=&type=ActiveUA onlydealers_by_zipcode_sample.jsonroot array of {dealer,distance}; id dealer.id; count=1000 from central zip → all 643 US dealers; geo at dealer.location.{latitude,longitude}
modelsGET /services/vehicles/models/basicdataUA onlymodels_basicdata_sample.jsonroot array; id code+year; enumerator for trims fan-out. Year enumerator: GET /services/vehicles/modelyears
features / trims / colorsGET /services/vehicles/trims/limiteddetails/{year}/{modelCode}UA onlytrims_limiteddetails_sample.jsonroot array of trims; trim id code; featureGroups[].features[] (features), exteriorColors[].msrp (color premium); FK modelCode

Bonus source (not gated): GET /services/specialoffers/byDealer?type=&year=&model=&trim= — incentives/APR.

Cross-source identity / FK alignment (HARD)

FKReferencing entity.fieldTarget entity.fieldExample valueVerified
inventory→dealersinventory.dealerCodedealer.id020250 (Liberty Subaru)
inventory→modelsinventory.modelCodemodels.codeIMP
inventory→featuresinventory.(modelCode,trimCode)trims.(modelCode,code)IMP + TLD
features→modelstrims.modelCodemodels.codeIMP

Probe evidence

1. Pagination contract — inventory ✅ PASS

POST retailerinventory/v2, dealerCode=020250 baseline, page/itemsPerPage.

  • size 100 → 229/229 unique, 3 pages, reached_end=true, param_ignored=false.
  • size 30 → 225/229 (8 pages): the reported total moved 227→229 mid-fetch — live inventory churn, not a silent cap (re-run at larger page size closed the gap).

2–5. Enumerators ✅ PASS

  • modelvehicles/models/basicdata, code → 10 distinct (ASC BRZ CTK FOR IMP OBK SOL TSK UNC WRX).
  • yearvehicles/modelyears, year → 59 distinct (1968–2026).
  • trimvehicles/trims/limiteddetails/{year}/{model}, code → 6 for OBK 2026; per (year,model).
  • dealerdealers/distances/by/zipcode?count=1000, dealer.id643 (full US). This is the fan-out enumerator for inventory.dealerCode (national query with no dealerCode returns pagedListWrapper:null, so per-dealer/dealer-batch fan-out is required).

6. Multi-value filter — inventory ✅ PASS

dealerCode CSV 020250,020207: shared rows kept identical tsrp/dealerCode/modelCode, combined call returned the full union (311), 0 contaminated fields → dealer codes may be safely batched.

7–8. Body shape / bot-block ✅ PASS

  • inventory POST (with origin+referer): 100% of 2xx parse as JSON; realistic UA not required. Without origin+referer → 403. No browser_session needed.
  • configurator GET trims/limiteddetails: 100% JSON, no friction.

9–12. Data quality / plausibility ✅ PASS

  • inventoryvinNumber 30/30 non-empty & distinct, tsrp positive, dealerCode non-empty. ⚠️ msrp is a constant 0map price to tsrp/internetPrice, never msrp.
  • dealerslocation.latitude/longitude valid & in-range 200/200; id non-empty (no 0.0 geo).
  • modelscode non-empty 17/17, msrp positive.
  • features — carrying trim's modelCode FK non-empty 6/6, feature code non-empty.

13–16. Cross-source identity keys ✅ PASS

Value-verified against the correlated fixtures (dealer 020250 → its IMP/TLD inventory → IMP trims → models.code=IMP). See alignment table above.

17–20. Real fixtures ✅ PASS

Five correlated live fixtures under projects/ai_subaru/tests/data/us/ (see per-entity table). No synthetic data.

21–24. Postman records ✅ PASS

All endpoints are recorded in the team Public OEM APIs collection under Subaru → US (collection uid 30657947-981c813f-173d-49cc-9215-3cab23957381, US folder 30657947-3183f53c-63fb-4a26-851c-27dac72d7ee7). Request IDs: inventory 2a4ed48c-2644-0cbb-7da2-79c880d1ee38, dealers 4a4f1c80-d13a-f933-a81c-1fe96414fda2, models 3b247a76-9110-6960-5223-630f0d78b006, features/configurator 7a044911-7082-b925-eda3-d317f8491d89 (plus model-years, dealers-maxDist, incentives, and geographic-data utility requests). The Subaru/US folders were created by hand (this MCP deployment has no folder-create tools); requests were then added via createCollectionRequest. A full v2.1 export also remains at docs/content/oems/subaru/subaru-us.postman_collection.json.

Notes for the implement phase

  • Inventory fan-out: enumerate all dealer codes (dealers …?count=1000 from a central zip → 643) and page retailerinventory/v2 per dealer or per dealer-batch (CSV dealerCode proven safe to combine; site uses ~22/call). Confirm the CSV batch-size ceiling during implement.
  • Pricing: inventory tsrp (total suggested retail) + internetPrice; msrp is a 0 sentinel. destinationFee nullable — decide null policy. Trim MSRP from trims/limiteddetails.msrp; destinationCharge/dealerInvoice are 0 there (use inventory destinationFee for per-vehicle freight).

Overrides

None required — every hard gate passed on probe evidence.

GateOVERRIDE reasonApproved by