Build a stock feed
This guide is for marketplaces, comparison sites and dealer-group sites that show a Vyro dealer’s stock. By the end you will have a feed that lists the right vehicles, prices them for a state, shows the hero image, links to the dealer’s page, and stays in sync.
What “live stock” means
Section titled “What “live stock” means”A vehicle should appear on your site when all of these are true. This is the same rule the dealer’s own Vyro website uses.
| Field | Value | Why |
|---|---|---|
showroom_stocked_vehicles.showroom_id |
the dealer’s showroom | One vehicle can be shown by several showrooms. |
is_published |
true |
The dealer has made it public. |
is_listed |
true |
It should appear in lists and search. Unlisted vehicles are reachable only by direct link. |
is_sold |
false |
Sold vehicles stay published for a while so links still work. |
pricing_codes |
contains your state, e.g. AU_VIC |
The vehicle is priced (and sold) in that state. |
listing_type |
retail |
Excludes private (consumer-to-consumer) listings. |
The query
Section titled “The query”-
List vehicles for one state. Ask for the state’s price with
pricing_config(path: $state), the featured image and the fields a card needs.query StockFeed($showroomId: uuid!, $state: String!, $limit: Int!, $offset: Int!) {stocked_vehicles(where: {showroom_stocked_vehicles: { showroom_id: { _eq: $showroomId } }is_published: { _eq: true }is_listed: { _eq: true }is_sold: { _eq: false }listing_type: { _eq: retail }pricing_codes: { _contains: [$state] }}order_by: [{ updated_at: desc }, { id: asc }]limit: $limitoffset: $offset) {idcodestock_idvinconditionproduction_yearmakemodelbadgeseriesbody_typefuel_typetransmissioncolourodometerhide_odometerupdated_atprice: pricing_config(path: $state)location {nameaddress}hero: assets(where: { is_public: { _eq: true }, placement: { _eq: "featured" } }limit: 1) {srcalt}gallery: assets(where: { is_public: { _eq: true } }order_by: { order: asc }limit: 20) {srcaltplacement}}total: stocked_vehicles_aggregate(where: {showroom_stocked_vehicles: { showroom_id: { _eq: $showroomId } }is_published: { _eq: true }is_listed: { _eq: true }is_sold: { _eq: false }listing_type: { _eq: retail }pricing_codes: { _contains: [$state] }}) {aggregate {count}}}{ "showroomId": "9b1d4e7a-2c3f-4a5b-8d6e-7f8091a2b3c4", "state": "AU_NSW", "limit": 50, "offset": 0 } -
Show the price.
price.driveAwayPriceis in cents. Read the two flags before you print it. See Prices.function priceLabel(price, locale = 'en-AU', currency = 'AUD') {if (!price || price.isVariable) return 'Price on application';const amount = new Intl.NumberFormat(locale, {style: 'currency',currency,maximumFractionDigits: 0,}).format(Math.ceil(price.driveAwayPrice / 100));return price.isExclOnRoads ? `${amount} excl. on-road costs` : `${amount} drive away`;} -
Show the image. Use
hero[0], falling back togallery[0]. See Images for resizing. -
Link to the dealer. Send shoppers to the vehicle’s page on the dealer’s site, with the state so they see the same price. See Vehicle URLs.
const url = `https://${showroom.domain}/vehicles/stocked/${vehicle.code}?region_id=${state}`; -
Page through the rest. Repeat with
offset+=limituntil you havetotal.aggregate.countvehicles. See Pagination.
Keep it in sync
Section titled “Keep it in sync”Dealers change prices, sell cars and add photos all day. Poll for changes rather than re-downloading everything.
Every 15 minutes, fetch what changed. Ask for vehicles updated since your last sync, without the live-stock filters, so you also see vehicles that were sold, unpublished or removed. Then upsert the live ones and remove the rest.
query Changes($showroomId: uuid!, $since: timestamptz!, $state: String!) { stocked_vehicles( where: { showroom_stocked_vehicles: { showroom_id: { _eq: $showroomId } } updated_at: { _gt: $since } } order_by: [{ updated_at: asc }, { id: asc }] limit: 200 ) { id updated_at is_published is_listed is_sold is_trashed pricing_codes price: pricing_config(path: $state) }}- Save the largest
updated_atyou processed as the next$since. Timestamps have microseconds, so keep the full string. Don’t round-trip it through JavaScript’sDate, which keeps only milliseconds and makes you re-read rows. - If you get exactly
limitrows, go again from the lastupdated_atbefore you sleep. Pagination shows how to do this without skipping rows. - A vehicle that is sold, unlisted, unpublished, trashed or no longer priced in your state should come off your site.
Once a day, reconcile. Fetch the id of every live vehicle and remove anything you hold that is not in the list. This catches a vehicle that was removed from the showroom entirely, which a change feed can’t show you.
query LiveIds($showroomId: uuid!, $state: String!, $offset: Int!) { stocked_vehicles( where: { showroom_stocked_vehicles: { showroom_id: { _eq: $showroomId } } is_published: { _eq: true } is_listed: { _eq: true } is_sold: { _eq: false } listing_type: { _eq: retail } pricing_codes: { _contains: [$state] } } order_by: { id: asc } limit: 500 offset: $offset ) { id }}Checklist
Section titled “Checklist”- Scoped every query to the showroom, and to one state.
- Prices divided by 100, rounded up, and labelled “drive away” or “excl. on-road costs”.
- “Price on application” when
isVariableis true. - Vehicle titles built from year, make, model and badge, not the
titlefield. Showbadgeas the dealer wrote it. - Odometer hidden when
hide_odometeris true. - Images requested at the size you display.
- Links go to the dealer’s vehicle page with
?region_id=. - Token cached for its 60 minutes, polling no more than every few minutes.