UpsellVehiclesBlockBanner
Displays vehicle recommendations in a horizontally scrolling strip, using an existing deal or inventory vehicle as the reference (the seed). Recommendations prefer the same vehicle class and can optionally mix deals and inventory.
Includes the common banner props. For Shopify products, use UpsellBlockBanner.
CMS setup
- Create a banner with typename
UpsellVehiclesBlock. - Set
foreignEntityIdto an existing vehicle identifier from the selected source. This is the vehicle identifier, not the CMS banner id. - Choose Fahrzeug-Empfehlungsmodus:
inventoryfor inventory vehicles ordealfor deals. The default isdeal; an inventory identifier must explicitly useinventory. - Set Empfehlungs-Limit to the maximum number of cards to display. Default: 10; minimum: 1.
- Enable Cross-Sell aktivieren only if recommendations may include the other source.
- Configure the title, image fit, and common banner appearance, then add the banner to a page.
A blank seed identifier prevents the backend from returning the banner. A non-empty identifier that cannot be resolved produces a recommendation API 404. Normal banner activation and draft-preview rules still apply.
CMS fields and rendered props
| CMS field | Rendered prop | Meaning / default |
|---|---|---|
foreignEntityId | foreignEntityId | Required for a usable banner; resolved by the selected source’s existing vehicle lookup |
upsellVehiclesRecommendationMode | recommendationMode | deal (default) or inventory |
upsellRecommendationLimit | recommendationLimit | Maximum total cards, default 10, minimum 1 |
crossSellEnabled | crossSellEnabled | Allow both sources; default false |
imageBlockObjectFit | imageObjectFit | Shared banner image-fit setting |
showNavigation is no longer used for this banner. crossSellEnabled does not control arrows or scrolling.
Props
CMS banner id; distinct from the reference vehicle identifier.
Reference vehicle identifier resolved in the selected source.
Source of the reference vehicle. Defaults to deal in CMS configuration.
Maximum total recommendations displayed, not the API page size. Default 10; minimum 1.
Allow deal and inventory candidates together. Defaults to false.
Shared image-fit setting passed to both inventory and deal cards.
Sources and vehicle classes
Source describes the listing: deal or inventory. Vehicle class describes the vehicle, such as Car or Motorbike. These are independent.
| Seed source | crossSellEnabled | Eligible sources |
|---|---|---|
inventory | false | Inventory only |
deal | false | Deals only |
| Either | true | Deals and inventory |
Enabling cross-sell makes the other source eligible; it does not guarantee a mixture or reserve slots for it. Same-class candidates from either enabled source compete on ranking.
Same-class priority and backfill
The seed is excluded from its own source. Candidates matching its normalized vehicle class are ranked first. Candidates of another class, including those with missing class data, are used only when the entire same-class pool contains fewer than one page of results. They fill the remaining slots up to one page, and never displace same-class results.
For a page size of 10:
| Same-class candidates | Result |
|---|---|
| 14 | Page one has 10; page two has 4. No cross-class backfill. |
| 10 | One full page of same-class results. |
| 6 | Six same-class results, then up to four fallback results. |
| 0 | Up to ten fallback results. |
There are no additional fallback pages. If the seed itself has no vehicle class, ordinary ranking applies to all candidates because a class comparison is unavailable.
Cross-class backfill works with crossSellEnabled=false within the selected source. A motorcycle inventory seed can therefore receive car inventory fallback without cross-sell. Car deals require crossSellEnabled=true.
The banner requests pages of min(6, recommendationLimit). Its normal fallback threshold is therefore six, even when its display limit is ten. Five same-class results can receive one fallback card; eight same-class results remain eight, with no fallback to reach ten. Direct API callers choose their own page size.
Rendering and loading
- Inventory recommendations reuse the inventory archive card (
CarListItem); deal recommendations reuseCarDealBlockthrough the deal archive adapter. Pricing, actions, and disclosures follow those existing card components. - Inventory cards support the consumption-information dialog when the required data is available.
- Cards remain in a horizontal strip with scroll snapping and a hidden scrollbar. There is no wrapping archive grid or navigation toggle.
- Card width follows a three-column archive’s maximum content width, rather than one third of the viewport:
min(100%, (var(--max-width-content) - 3rem) / 3). A wide strip may show more than three cards; a narrow container caps each card at its available width. - Recommendations load in the browser, with session credentials included. Skeleton cards reserve space during loading and for the upcoming page.
- The first page loads immediately. Further pages load when the end marker intersects the strip’s scroll area. Loading stops at the configured limit or the API’s last page.
- Returned cards are deduplicated by
source:id. The same id in two sources is treated as two listing identities. - Changing the seed, mode, limit, or cross-sell setting clears the loaded recommendations and resets horizontal scroll.
- A failed request keeps already loaded cards. A later end-marker intersection can retry; there is no dedicated error or empty-state message. An empty result can leave the title visible without cards.
Recommendation ranking
The engine uses weighted attribute matching, not machine learning. Text attributes are normalized before exact comparison. Missing seed attributes do not contribute to the available weight; a missing candidate attribute cannot match.
| Attribute | Weight | Match rule |
|---|---|---|
| Price | 3 | Within an inclusive ±20% range on the same price basis |
| Brand | 3 | Exact normalized match |
| Vehicle class | 3 | Exact normalized match; also determines the priority group |
| Model id | 4 | Exact normalized match when the seed has a model id |
| Model name | 3 | Used instead of model id only when the seed has no model id |
| Fuel | 2 | Exact normalized match |
| Vehicle type/category | 2 | Exact normalized match |
Price comparison prefers the seed’s purchase price. Only when the seed has no purchase price does it use its monthly price, against the candidate’s monthly price. Purchase and monthly amounts are never compared with each other. Prices are read in cents from gross offer values; inventory also supports its legacy purchase-price field.
Inventory class and category come from its vehicle fields. Deals read class and category/type from their optional line payload. Missing deal class data places that deal in the fallback group when the seed class is known.
Similarity and ranking score
similarity = matched attribute weight / available seed attribute weight
score = similarity + history bonussimilarity ranges from 0 to 1, or is 0 when no seed attributes are available. Multiplying it by 100 gives a feature-match percentage, not a probability that the visitor will buy or prefer the vehicle. The API does not return a separate percentage.
score adds personalization and can reach 1.2. Within each class group, results sort by score descending, then by the number of unique matched signals, then by stable candidate order. The same-class group always precedes fallback, even if a fallback candidate has a higher score.
Session personalization
The engine reads up to 50 latest product-search events for the current session and uses deal/inventory events from that window. These are archive/search responses, not 50 clicks or 50 recommended vehicles.
| History signal | Bonus |
|---|---|
| Candidate appeared in a previous archive result from the same source | 0.1, awarded once |
| Distinct search/filter term occurs in the candidate’s normalized search text | 0.05 per term, at most 0.1 total |
The total bonus is capped at 0.2. Duplicate events and repeated terms do not accumulate more points. Result identifiers are source-specific; search terms can match across both sources. Search text contains brand, model, class, vehicle type, and fuel.
Without session history the bonus is zero. Merely displaying recommendations does not record another product-search event, so reloading the banner does not boost its own results. Appearing in an archive response is an exposure signal, not proof that the visitor clicked a card. Recommendation ranking does not currently use clicks or conversions.
API
GET /v1/recommendations/cars/inventory/2527110?page=1&pageSize=6&crossSellEnabled=true| Parameter | Location | Behavior |
|---|---|---|
mode | Path | Required: deal or inventory |
id | Path | Required: reference vehicle identifier |
page | Query | One-based page; default 1 |
pageSize | Query | Default 10; maximum 50; also sets the fallback threshold |
crossSellEnabled | Query | Optional boolean true or false; default false |
Keep pageSize unchanged while paging. recommendationLimit is a banner display limit, not an API query parameter. Archive filter/sort settings are not forwarded into recommendation candidate selection.
The endpoint is public and uses the default request throttle. Session context is optional. It returns Cache-Control: no-store, because rankings may be personalized. Invalid modes or boolean values fail validation; an unresolved seed returns 404.
Response
The HTTP response wraps the paginated result in data. Each recommendation contains the complete source-specific car payload. The example below abbreviates that payload; identifiers and scores are illustrative.
{
"data": {
"data": [
{
"source": "inventory",
"id": "candidate-id",
"similarity": 0.625,
"score": 0.725,
"matchedSignals": ["purchase-price", "vehicle-class", "fuel", "vehicle-type", "session-result"],
"car": { "id": "candidate-id", "vehicleClass": "Motorbike" }
}
],
"resultCount": 1,
"totalPages": 1,
"hasPrevPage": false,
"hasNextPage": false
}
}Possible matched signals are purchase-price, monthly-price, brand, vehicle-class, model-id, model, fuel, vehicle-type, session-result, and session-search.
resultCount describes the eligible result set after seed exclusion and fallback selection, not the number of cards on the current page or the banner’s display limit. Requests beyond the last page return an empty recommendation array.
Limits and troubleshooting
The engine loads at most 300 candidates per enabled source, using the existing archive services. Class availability and ranking are evaluated inside that bounded pool, not across the entire catalog. Increasing the banner limit does not enlarge the candidate pool. Catalog or session changes between requests can change ordering; pagination is not a frozen snapshot.
There is no recommendation persistence, recommendation cache, source quota, price-increase requirement, or guarantee of an exact model match. The banner suggests alternatives; an “upsell” can have a lower price than the seed.
| Symptom | Check |
|---|---|
| Banner missing entirely | Seed id is non-empty; banner activation and page configuration are valid |
| Title but no cards | Inspect the recommendation request; confirm the id belongs to the selected mode |
| Recommendation API returns 404 | Seed is unresolved; deal is the default even if an inventory id was entered |
| Fewer cards than the configured limit | Eligible pool may be smaller; fallback only fills one API page |
| Only inventory or only deals | Cross-sell defaults to false; enabling it does not guarantee mixed results |
| Other vehicle classes appear | Same-class pool is below one page, or the seed has no class |
| A deal is treated as fallback | Its line.vehicleClass may be missing or different |
| Results differ between visitors | Session history contributes a bounded personalization bonus |
Banner example
const banner: UpsellVehiclesBlockBanner = {
typename: BannerTypename.UpsellVehiclesBlock,
id: 'b_123',
title: 'Weitere Fahrzeuge für dich',
titleTag: 'h2',
titleAlign: 'left',
cssId: null,
cssClasses: null,
imageObjectFit: 'contain',
foreignEntityId: '2527110',
recommendationMode: UpsellRecommendationMode.Inventory,
recommendationLimit: 10,
crossSellEnabled: true
};Unlike Shopify upsells, vehicle upsells always use a vehicle seed and the local recommendation engine. They do not accept a manual product list or nested funnel banners, and recommendationMode is a source enum rather than Shopify’s boolean switch.
Related vehicle documentation: inventory archive, deal archive, inventory detail, and deal detail.