Why product prices need a source and checked time | simlir blog
Why product prices need a source and checked time
A price without its currency, retailer and checked time cannot be explained. How to model price snapshots honestly, and what to do with stale or missing values.
A price snapshot is only trustworthy when its source and checked time travel with it. Credit: Generated for Simlir
The short answer
A useful price value needs three things beside it: the currency it is denominated in, the retailer it was observed at, and the time it was checked. Without that context an application cannot say when or where the price was seen — so it can only assert the number, never explain it. simlir returns price as an object carrying all three, and calls the value a price snapshot or last-seen price rather than a live price, because that is what it is.
The catalogue fields shopping assistants rely on: stable identifiers, comparable specs, price and availability context, images and canonical links. With a readiness checklist.
Most product attributes are stable. A dishwasher’s width is 59.8 cm today and will be 59.8 cm next month. Its GTIN does not drift. Its material composition does not depend on which shop you are standing in.
Price behaves nothing like this. It varies by retailer, changes on promotional cycles, moves within a single day in competitive categories, differs by variant and pack size, and can be withdrawn entirely when a product goes out of stock. It is not a property of the product. It is an observation of a retailer at a point in time.
Systems get into trouble when they store price with the same confidence they store width. The failure is quiet: a number that was true when it was recorded slowly becomes untrue, while the interface around it goes on presenting it with unchanged certainty. Nothing errors. A shopper simply clicks through and finds a different figure.
Price snapshot versus live price
A price snapshot
A value observed at a stated moment, at a stated retailer, in a stated currency. Good for: ranking, sorting, filtering by budget, rough display, trend analysis, deciding what to show a shopper first. Never claim: that it is what the shopper will pay right now.
A live price
A value fetched from the retailer at the moment of asking, usually as part of a commercial integration. Good for: checkout, binding quotes, and any promise about what a transaction will cost. Cost: a per-retailer relationship or request at read time, for every product, every view.
The correct architecture for a discovery or comparison surface is usually snapshots for the decision and a link out for the transaction. Snapshots are what let you rank a hundred candidates cheaply. The retailer page is what tells a shopper the real number when they are ready to act. Trying to make snapshots behave like live prices is expensive and still fails at the exact moment accuracy matters most.
Source, currency and checked time
Here is the simlir price object. Each part is doing a specific job.
29.99 is ambiguous the moment a second market exists — and it silently corrupts any cross-market comparison
retailer
Observed where
You cannot explain why two candidates differ, or send the shopper to the right place
as_of
Observed when
You cannot age the value out, warn about staleness, or defend the number when it is challenged
as_of is the field most often dropped in integration, and the one that matters most. It is the date the price was observed when the product was last rediscovered. Keep it, and your interface can say “£29.99 at Holland & Barrett, seen 7 April”. Drop it, and the same interface can only say “£29.99”, which reads as a promise.
How provenance changes what a user believes
There is a persistent assumption in product design that caveats reduce confidence. With price data the opposite is true, because the caveat is what makes the number checkable.
Unsourced
£29.99 Reads as current and authoritative. When the retailer page shows £32.50, the user concludes the product is wrong about everything, not just about this.
Sourced
£29.99 at Holland & Barrett · seen 7 April · check retailer for today’s price Reads as evidence. When the retailer page shows £32.50, the user concludes the price changed — which is exactly what happened.
The second version is also the only one that survives a support conversation. When someone asks why you showed a wrong price, “that was the price at this retailer on this date” is an answer. “Our data said so” is not.
This matters more inside an assistant than on a comparison table, because an assistant speaks in sentences and sentences carry implied certainty. “It costs £29.99” asserts a present fact. “It was £29.99 at Holland & Barrett when we last checked on 7 April” asserts something you can actually stand behind.
Handling missing and stale values
Two distinct cases, two distinct behaviours.
Missing
Do not substitute, estimate, or let a model infer a plausible figure. A product with no price is still useful — it can be shown, compared on specifications, and linked to. An invented price attached to a real product with a real retailer link is a specific, traceable falsehood.
Stale
Staleness is a judgement, not a fact, and the threshold belongs to your product rather than to the data. Set it by category volatility: fast-moving consumer categories and anything on promotional cycles age in days; large appliances and furniture tolerate weeks.
Age of as_of
Suggested treatment
Within your category’s fresh window
Show the price with its date and retailer
Ageing
Show it, but make the date prominent and add “check retailer for today’s price”
Beyond your threshold
Suppress the number, keep the product, lead with the retailer link
Absent
Rank on other criteria; state that price is unavailable
One more rule that saves a category of bugs: never mix ages inside a single comparison without showing them. If one candidate’s snapshot is from yesterday and another’s is from six weeks ago, a “cheapest” label is comparing two different moments. Either show both dates or exclude the stale row.
A practical response example
In a full product record the price object sits alongside identity, specifications and the retailer destination, so everything needed to explain a number travels together:
Three implementation notes that follow directly from this shape:
Keep the object intact end to end. The most common cause of an unexplainable price is a data layer that flattens price to a float on the way into its own store. The context is gone by the time the interface needs it.
links.retailer can be null. When there is no verified retailer product URL, both links.retailer and its compatibility alias links.buy are null. If you are relying on the link as the escape hatch for a stale price, you need a rendering for the case where there is no link.
market qualifies the currency. A GBP snapshot in the gb market is coherent. If the market you requested and the market returned disagree, treat it as an integration bug rather than reasoning about the price.
Frequently asked questions
Why not just fetch live prices?+−
Because it does not scale to discovery. A live fetch requires a request to the retailer at read time for every product on screen, which is slow, expensive, often contractually restricted, and impossible to do across a wide candidate set before ranking. Snapshots let you rank a hundred candidates cheaply; the retailer link gives the shopper the current figure at the moment it matters.
How old is too old?+−
It depends on the category, and the decision belongs to your product rather than to the data. Fast-moving consumer goods and anything on a promotional cycle age within days. Large appliances and furniture tolerate weeks. Set an explicit threshold per category, compare it against price.as_of, and make the behaviour at each stage deliberate rather than incidental.
Should I show the checked date to shoppers?+−
Yes, wherever a price influences a decision. It converts an assertion into evidence, and it is what protects you when the retailer page shows something different. A compact line under the price — retailer, date, and a prompt to check the retailer for today's figure — is enough. Hiding the date does not make the data fresher; it only makes the eventual mismatch harder to explain.
Can I use snapshots to build a price history?+−
You can build a series of observations, and that is genuinely useful for spotting movement and promotional patterns. Be precise about what it is: a record of what was seen when, not a continuous price history. Gaps exist wherever a product was not rediscovered. Label charts as observed prices, and note that use of API responses is governed by the API and data licence.
What if links.buy and links.retailer differ?+−
They should not. links.buy is a compatibility alias retained for older clients: when a verified retailer product page exists, it mirrors the same URL as links.retailer. It is not a separate comparison destination. If the row has no verified retailer page, both fields are null together.
A seven-stage architecture for shopping assistant search: intent, retrieval, structured records, provenance, comparison, MCP or REST access, and failure testing.