Skip to main content

Ford Public API Sources

Ford exposes its data through one public cxservices API used by the ai_ford pipeline. No authentication credentials are required.

API Domain

Domainapplication-idUsed for
www.ford.com/cxservices/07152898-698b-456e-be56-d3d83011d0a6All endpoints — model catalog, dealer locator, inventory search, Build & Price configurator

All requests send application-id, x-requested-with: XMLHttpRequest, and Accept: application/json (see ai_ford.sources.constants).

Single host

The same cxservices API is also reachable at shop.ford.com. Responses are byte-identical across both hosts, and the www.ford.com application-id is authorized for every endpoint (the shop.ford.com app-id returns 401 for inventory search). The pipeline therefore standardizes on www.ford.com with a single application-id; the previous shop.ford.com split has been removed.

Raw dependency graph

The raw tier is fully data-driven — no model names or years are hardcoded. The model catalog is fetched live and drives every downstream fetch:

NodeRaw assetUpstreamEndpoint
aford/us/raw/model_slices_modelsproducts/ModelSlices.json?make=Ford
bford/us/raw/model_slices_model_trimsaproducts/ModelSlices.json (per model/year)
cford/us/raw/dealer_locator_dealersdealer/Dealers.json
dford/us/raw/inventory_searcha + cinventory/Search.json
eford/us/raw/configurator_configbconfig/Details.json

Model List (node a)

GET https://www.ford.com/cxservices/products/ModelSlices.json?make=Ford

Called with only make=Ford, ModelSlices.json returns the full catalog as Response.Model — a list of every model-year, each with ModelName, Year, VehicleLineCode, VehicleType, CatalogId, and model-level Pricing (Low/High base MSRP, destination charge, plan pricing).

This is the authoritative, real-time enumerator of which models and years exist (~130+ model-year records spanning roughly 2021–2027). It replaced the previously hardcoded model and year lists; model_trims and inventory both derive their (model, year) fan-out from this asset.

Catalog model names

ModelName is the exact string the other endpoints expect, and it does not always match the marketing name. For example F-150 model-years 2022+ are "F-150 F-150", and the Mustang Mach-E is "Mache". Always pass the catalog ModelName verbatim — never a guessed display name.

Model Trims (node b)

GET https://www.ford.com/cxservices/products/ModelSlices.json
?appContext=T1&planType=MSRP&modelSliceDefiners=modelTrimName
&make=Ford&model={ModelName}&year={Year}

Called per (model, year) from the Model List with modelSliceDefiners=modelTrimName, the response carries the model-level fields above plus Response.Model.ModelSlices.ModelSlice[] — one entry per trim:

FieldDescription
idTrim id (e.g. F25-BASE) — matches the inventory Trim.ID
nameHuman-readable trim name (e.g. Base, Big Bend®)
ConfigTokenBase configuration token for the trim — consumed by the configurator (node e)
PricingPer-trim pricing

This source produces the trim-grain models entity (enriched with the model-level VehicleLineCode / VehicleType / CatalogId) and supplies the ConfigTokens that drive the feature catalog.

Dealer Locator (node c)

GET https://www.ford.com/cxservices/dealer/Dealers.json
ParamDescription
makeAlways Ford
stateUS state code — returns all dealers in that state
maxDealersMaximum dealers to return (API rejects values >500)

Response: Response.Dealer[] — dealer objects with PACode, Name, Address (nested Street1, City, State, PostalCode, Country), Latitude, Longitude, Phone, Fax, dealerType, FordCertified, TimeZone.

Coverage strategy: one request per US state via the state param. States are non-overlapping, so no deduplication is needed; the largest state stays well under the 500-dealer cap.

Inventory Search (node d)

GET https://www.ford.com/cxservices/inventory/Search.json
ParamDescription
makeAlways Ford
modelCatalog ModelName (from the Model List asset)
yearModel year (from the Model List asset)
dealerPACodeDealer PA code (from the dealer locator asset) — required
postalCodeRequired by validation but ignored when dealerPACode is present
limitResults per page. Max 100 — the API rejects larger values with HTTP 400
offsetZero-based offset into the result set

There is no Radius parameter, and PageSize / PageNumber are not real params (earlier code used them and silently got a single unpaginated page).

Pagination. offset must stay within [0, Total], where Total comes from Response.VehicleSearch.Counts.Total:

  • offset == Total → HTTP 200 with an empty result.
  • offset > Total → HTTP 400.

So paging is driven by the count, not by detecting a short page: fetch offset=0, read Counts.Total, then advance offset by limit only while offset < Total. This never issues a request at or beyond Total.

Response shape varies with result count_extract_vehicle_list normalizes all three:

Result countResponse.VehicleSearch.Vehicles
many{"Vehicle": [ {...}, {...} ]} (list)
one{"Vehicle": {...}} (single object, not a list)
zero"" (an empty string, not an object)

Each vehicle contains:

  • Model: Model.ModelName, Model.Year, Model.Make
  • Trim: Trim.ID (e.g. 501A), Trim.Name (e.g. XLT)
  • Pricing: Pricing.MSRP, Pricing.Invoice
  • Specs: NormalizeTransmission, NormalizeDrive, NormalizeFuel, NormalizeCylinder, NormalizeDisplacement, NormalizeDoorType, NormalizeExtPaint, NormalizeTrimColor
  • Stock: Vin, StockNumber, DealerPACode, DOL (days on lot), DLRMileage, VehicleStage, DemoFlag
  • Media: ImageToken, WindowStickerURL
  • Features: ConfigToken — an encoded string of the vehicle's installed option codes (codes after the Config[|Make|Model|Year|Trim| prefix; ~-prefixed codes are excluded options). Parsed per vehicle into feature_refs (see below).

Per-vehicle features. Each inventory row carries feature_refs — a list of {feature_code} structs parsed from its ConfigToken (installed codes only). The consolidated tier resolves these one-to-many against the features entity via a list-mode foreign key (on: [trim_id, model_year, feature_refs.feature_code]), collecting the matched feature_ids into a feature_ids list on the inventory row. This is how each vehicle's configuration is captured. (Caveat: for relabeled nameplate-variant VINs the ConfigToken is the base config's, per the pollution note above — so a hybrid's codes may reflect the gas configuration.)

Coverage strategy: the search requires a (dealer, model, year) filter — there is no bulk or radius-only variant. make/model/year accept comma-separated values forming an equal-length positional matrix, so the component sends one matrix call per dealer covering every (model, year) from the Model List, paged by Counts.Total. A 136-tuple matrix returns a dealer's full inventory with no cap. Dealer PA codes and the (model, year) universe are both discovered at materialization time. This collapses what would be ~136 per-model-year calls per dealer into one paginated call.

Upstream dependency: the inventory raw component declares upstream_dep: [ford/us/raw/model_slices_models, ford/us/raw/dealer_locator_dealers] and receives both as a dict via fetch_with_upstreams().

Model-filter pollution — model_name is unreliable; identity is trim_id

The model filter does not do exact matching for same-nameplate powertrain families. Validated against live data:

  1. Variant relabeling. When the matrix includes a nameplate family (Escape / Escape Hybrid / Escape Plugin Hybrid — one vehicle line, shared trim ids), the API relabels the variants onto the base name (Escape HybridEscape) and rewrites their ConfigToken / ImageToken to the base config. This also happens for a single base-model query: model=Escape alone returns the hybrid VINs as Escape.
  2. Near-lossless, except promos. The relabeled record keeps the correct Trim.ID (e.g. F45-PLATINUM), drivetrain, and core pricing (BaseMSRP, Invoice, plan prices identical). The only differences are model_name, the tokens, and special-event promotional pricing — the base name picks up promo fields (SpecialEventBasePrice/SpecialEventMSRPDiscount) that the variant name does not surface.

Distinct models never cross-contaminate (F-150 F-150 vs F-150 Lightning, Bronco vs Bronco Sport, Escape vs Expedition are all clean) — so batching every model into one call is safe. Because of the variant relabeling, the models entity is keyed on (trim_id, model_year), not model_name (see below), so a relabeled VIN still joins correctly.

Models entity — two complementary sources

The consolidated models entity merges two trim-grain sources on (trim_id, model_year)not model_name, which the inventory endpoint relabels for nameplate variants. trim_id (e.g. F45-PLATINUM, encoding the vehicle line) is consistent across inventory, the catalog, and the configurator.

  • model_trims (catalog) — full trim universe, VehicleLineCode, VehicleType, base ConfigTokens, authoritative pricing.
  • inventory — drivetrain attributes (transmission, displacement, fuel, cylinders) and on-lot trim signals that the catalog does not carry.

Each contributes columns the other lacks, so both are kept rather than one being dropped. The cross-entity foreign keys (inventory → models, features → models) also join on (trim_id, model_year).

Because (trim_id, model_year) collapses rows that differ on non-key columns, the catalog (model_trims) models source uses a min-MSRP tiebreak: the same trim id comes back under each nameplate variant query (Escape / Escape Hybrid / Escape Plugin Hybrid), and hybrids/PHEVs price higher, so the lowest-MSRP row is the ICE/base config — keeping model_name canonical (Escape rather than Escape Hybrid). The inventory models source is left to the component's default deterministic dedup (it already labels everything with the base name post all-models relabel). The non-blocking discrepancies check still surfaces the collapsed conflicts. A future enhancement will add a variant_names aggregation (the list of nameplate names a trim is sold under) once the transformed component can collect a column into a list while retaining its scalar canonical value.

ConfigToken format

Ford encodes a vehicle configuration as a ConfigToken string:

Config[|Ford|F-150|2026|501A.|67T.61B.86M.~99P.~44G.]

Segments after the metadata prefix (Config[|Make|Model|Year|Trim|) contain option codes separated by .; codes prefixed with ~ are excluded. The base ConfigToken for each trim comes from Model Trims (node b) and drives the feature catalog fetch.

Configurator — feature catalog (node e)

GET https://www.ford.com/cxservices/config/Details.json
?appContext=T1&configToken={ConfigToken}&planType=MSRP
&returnAttributes=modelId;modelTrimName;modelTrimId
&partAttributes=part.*&sortPartPrice=Descending

The ConfigTokens are read from the upstream Model Trims asset (ford/us/raw/model_slices_model_trims) — one base token per trim — so the feature catalog is enumerated dynamically rather than from a hardcoded model list.

Response: Response.PartState.PartClass[] — part classes, each containing a list of Part objects.

PartClass groups (typical)

PartClassContent
PackagesEquipment packages (Co-Pilot360, Tow Package, etc.)
Exterior / Interior OptionsWheels, bumpers, seats, trim, electronics
AccessoriesDealer-installed accessories
Engine / Transmissions / DrivePowertrain choices
Paint / Wheel / Seat TypeColors, wheels, seating

Part fields

FieldDescription
PartIDOption code (matches ConfigToken codes)
NameHuman-readable feature name
DescriptionLonger description
CategoryCategory name
PriceMSRP price delta (string, e.g. "7695.0")
StateIncluded, Completed, Selectable, Excluded, UserSelected
IsProductStandard"true" if standard equipment
MarketingDoNotDisplay"true" for internal parts hidden from the UI

Parts with MarketingDoNotDisplay=true are internal (powertrain assignments, body codes, pep codes). The pipeline stores all parts and preserves the marketing_do_not_display flag for downstream filtering.

Environment Variables

VariablePurposeDefault
FORD_INVENTORY_MAX_DEALERSCap dealer count for dev/testingNone (all)
FORD_INVENTORY_MAX_PAGESCap pages per dealer queryNone (all)
FORD_INVENTORY_MAX_WORKERSInventory fan-out thread pool size10
FORD_CONFIGURATOR_MAX_CONFIGSCap ConfigTokens fetched by the configuratorNone (all)