Skip to content
Last updated

Confidence score

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 trust object 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.

Where the score appears

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.

The trust object

"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"] }
}
FieldMeaning
scoreOverall confidence, 0 (don't trust) to 1 (fully trusted). A weighted average of the five components — the exact arithmetic is shown below.
componentsThe five sub-scores that make up score, each also 0–1. They tell you why a score is what it is.
flagged_offersIndices (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_reasonsWhy each index was flagged: price, color, capacity, size, pack, model. Present only when flagged_offers is non-empty.

The five components

Each component measures one independent dimension of result quality.

identity — is this the product you asked for? (weight 0.25)

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).

completeness — are the expected fields there? (weight 0.20)

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.

price_integrity — is the pricing believable? (weight 0.25)

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.

offer_contamination — what fraction of the offers are clean? (weight 0.15)

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_offers in practice);
  • variant — on Google key: product results, 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) or model (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.

freshness — how recent is the data? (weight 0.15)

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.

How score is computed

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.81

The weights are fixed per API version and published here — if we ever rebalance them, that is an announced, versioned change.

Using the score

Treat score as a routing signal, not a hard gate:

ScoreReadingSuggested handling
≥ 0.8High confidenceIngest directly.
0.5 – 0.8Usable with caveatsIngest, but spot-check high-value SKUs. Check which component drags the score.
< 0.5Low confidenceReview before trusting; consider re-crawling with max_age: 0 or more identifying data.

And when a specific component is the problem:

Low componentLikely causeWhat to do
identityWrong product matchedRe-submit with richer input (Google: key: product). Don't use the prices.
completenessLayout change / reduced pageUse what's present; expect the gap, don't infer zero.
price_integrityOutlier offers or a price jumpCheck flagged_offers; if empty, verify against a second source before repricing.
offer_contaminationJunk offers in the listExclude the flagged indices, recompute your aggregates.
freshnessCached / aged dataLower max_age if you need current data (a fresh crawl is billed as usual).

flagged_offers in practice

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.

Questions we get

  • 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 — success and trust are 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 identity 0.7? On a product we see for the first time there is no earlier observation to compare against, and identity reports 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. Google key: product lookups are the exception — there identity is the match confidence itself.
  • Should I gate on score or on a component? For "is this the exact product" decisions, gate on components.identity. Identity is one of five components, so a weak match with perfect pricing and freshness can still score 0.9 overall. score is a routing signal for data quality as a whole.
  • Every offer is a different variant — is that caught? On Google key: product results, yes, within limits: an offer whose title contradicts yours on colour, capacity, size, pack count or a sibling model code is flagged (see offer_contamination), with the class named in flagged_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 on offers[].name for those.
  • Is "not found" the same as "no offers"? No. not found means the marketplace does not know the product. A product with no purchasable offer right now comes back as a normal result with offers_count: 0 and buybox: null; see Errors & limits.

Next steps