Every kwery result ships with a confidence score — a machine-readable answer to the question "how much should I trust this data point?". You don't have to guess whether a crawl landed on the right product, whether the offer list looks contaminated, or whether the data is stale: the result tells you, per result, on every result, at no extra cost.
Rolling out. Trust scoring is being enabled per source over the coming weeks, so the
trustobject may not yet appear on your results. Build your pipeline to treat it as optional for now — when it is absent, nothing else about the result changes, and no action is needed on your side to start receiving it once your sources are live.
The trust object is attached to every result, in every delivery channel:
- batch downloads (
GET /job/{id}/download), - stream webhook deliveries,
- polled stream results (
GET /stream/{id}/results).
There is nothing to enable and no extra credits are consumed — trust scoring is part of producing the result.
"trust": {
"score": 0.81,
"components": {
"identity": 1.0,
"completeness": 0.85,
"price_integrity": 0.70,
"offer_contamination": 0.90,
"freshness": 0.55
},
"flagged_offers": [2, 5],
"flagged_reasons": { "2": ["price"], "5": ["color"] }
}| Field | Meaning |
|---|---|
score | Overall confidence, 0 (don't trust) to 1 (fully trusted). A weighted average of the five components — the exact arithmetic is shown below. |
components | The five sub-scores that make up score, each also 0–1. They tell you why a score is what it is. |
flagged_offers | Indices (into the result's offers[] array) of offers flagged as price outliers or, on Google product lookups, as a different variant. Empty when nothing looked suspicious. |
flagged_reasons | Why each index was flagged: price, color, capacity, size, pack, model. Present only when flagged_offers is non-empty. |
Each component measures one independent dimension of result quality.
High when the returned product's name and brand match what you requested. For Google Shopping key: product lookups, identity is the match confidence: title similarity first, price proximity next (neutral when you send no price), brand presence last — or 1.0 outright when your deeplink's retailer appears in the offer list. A result that only just cleared the match gate therefore shows an identity just above it, not a rounded-up default (see how matching works). Low when the crawl landed on the wrong product, a variant you didn't ask for, or the wrong marketplace locale.
On the first observation of a product on any other source there is nothing to compare against yet, and identity reports the neutral default 0.7. It is a "first time seen" value, not a doubt about the match; later observations of the same product score against it.
When it's low: treat the whole result with suspicion — price data about the wrong product is worse than no data. Re-submit with more identifying data (on Google, use key: product with title and deeplink filled in — see Google Shopping).
Scored against the set of fields we expect for that source (each source page documents its fields). Missing critical fields — price and offers — are penalised extra; missing nice-to-have metadata (reviews, category names) costs less.
When it's low: the marketplace page may have changed layout or served a reduced variant. The data present is still real; what's missing is missing, not zero.
Checks that the pricing is internally consistent (min ≤ avg ≤ max, offers clustered sensibly) and in line with what we've seen historically for this product. Drops when the offer set contains price outliers, or the average price jumps far from the previous snapshot.
When it's low: look at flagged_offers first — usually one or two contaminated offers (wrong variant, accessory listed as the product, currency glitch on the seller side) explain it. A low price_integrity with no flagged offers means the whole price level moved — which may be a genuine market move (a sale) rather than an error.
Defined as 1 − (flagged offers / total offers). 1.0 means no offer was flagged; a result with 10 offers and 2 flagged scores 0.8. An offer is flagged for one of two reasons:
- price — its price is far from the rest of the offer set (see
flagged_offersin practice); - variant — on Google
key: productresults, its title contradicts your title on a variant class:color(Black against a requested White),capacity(256GB against 128GB),size(65" against 55"),pack(24-pack against 12-pack) ormodel(a sibling model code such as T3016US against T3017US). Colour words are recognised in English and German; the other classes are language-free. A class is only checked when your title carries a token of it, and only contradicts when the offer's title carries one too — an offer that names no colour is not flagged, and neither is one that names both.
flagged_reasons (keyed by offer index) says which applied.
When it's low: the aggregates (price_min in particular) may be dragged by junk offers or by the wrong variant. Recompute your own minimum over the non-flagged offers — the indices in flagged_offers tell you exactly which to exclude.
Highest for a just-completed crawl, decaying as the data ages. Results assembled with cached data (within your max_age window) carry the age of the original observation — freshness is honest about that recency. A cached "not found" also decays: the longer since we last confirmed a product is absent, the less certain that answer is.
When it's low: you asked for (or accepted) cached data. If you need current data, lower max_age on the request to force a fresh crawl.
score is the weighted average of the five components. The weights sum to 1.0, so the score always lands in 0–1. For the example above:
score = identity·0.25 + completeness·0.20 + price_integrity·0.25
+ offer_contamination·0.15 + freshness·0.15
= 1.0·0.25 + 0.85·0.20 + 0.70·0.25 + 0.90·0.15 + 0.55·0.15
= 0.25 + 0.17 + 0.175 + 0.135 + 0.0825
= 0.81The weights are fixed per API version and published here — if we ever rebalance them, that is an announced, versioned change.
Treat score as a routing signal, not a hard gate:
| Score | Reading | Suggested handling |
|---|---|---|
| ≥ 0.8 | High confidence | Ingest directly. |
| 0.5 – 0.8 | Usable with caveats | Ingest, but spot-check high-value SKUs. Check which component drags the score. |
| < 0.5 | Low confidence | Review before trusting; consider re-crawling with max_age: 0 or more identifying data. |
And when a specific component is the problem:
| Low component | Likely cause | What to do |
|---|---|---|
identity | Wrong product matched | Re-submit with richer input (Google: key: product). Don't use the prices. |
completeness | Layout change / reduced page | Use what's present; expect the gap, don't infer zero. |
price_integrity | Outlier offers or a price jump | Check flagged_offers; if empty, verify against a second source before repricing. |
offer_contamination | Junk offers in the list | Exclude the flagged indices, recompute your aggregates. |
freshness | Cached / aged data | Lower max_age if you need current data (a fresh crawl is billed as usual). |
When flagged_offers is non-empty, those offers are priced far enough from the median of the offer set to look like data errors or contamination — a €4.90 "offer" among €200 listings is almost never the product you're pricing against. The offers are still included in offers[] (we report, you decide); the flags plus offer_contamination let you exclude them deterministically:
usable_offers = offers.filter((o, i) => !trust.flagged_offers.includes(i))Automated repricing against price_min should always use the flag-filtered minimum.
- Does trust cost credits? No. It's computed on every result as part of the standard lookup — you pay the source's normal credit weight, trust included.
- Does a low score mean the result failed? No —
successandtrustare independent. A result can be successful (the lookup completed) and still warrant review. Failed lookups are never billed and carry no trust object. - Can I filter by score in the API? Filtering is client-side by design: you always get the data plus the score, and your pipeline decides. This keeps the billing model simple (you pay for the lookup, not for our opinion of it).
- Is the score comparable across sources? Yes — the components and weights are identical for every source, so 0.8 means the same thing on Idealo and on Walmart.
- The GTIN matched exactly, so why is
identity0.7? On a product we see for the first time there is no earlier observation to compare against, andidentityreports the neutral default 0.7. It is "first time seen", not a doubt about the match; the next observation of that product scores against the first. Googlekey: productlookups are the exception — there identity is the match confidence itself. - Should I gate on
scoreor on a component? For "is this the exact product" decisions, gate oncomponents.identity. Identity is one of five components, so a weak match with perfect pricing and freshness can still score 0.9 overall.scoreis a routing signal for data quality as a whole. - Every offer is a different variant — is that caught? On Google
key: productresults, yes, within limits: an offer whose title contradicts yours on colour, capacity, size, pack count or a sibling model code is flagged (seeoffer_contamination), with the class named inflagged_reasons. It cannot catch an offer whose title omits the variant, and it does not know which distinctions matter to you — keep your own check onoffers[].namefor those. - Is "not found" the same as "no offers"? No.
not foundmeans the marketplace does not know the product. A product with no purchasable offer right now comes back as a normal result withoffers_count: 0andbuybox: null; see Errors & limits.
- Audit & provenance — the full crawl trail behind each result.
- Change events — get notified when watched products change.
- Self-serve → Cached re-serves — how
max_ageinteracts withfreshness.