How the Score Is Computed
How a PDP score adds up from its five components, and how to read the score breakdown on PDP score responses.
Every COMPLETED PDP score response from GET /v1/pdps/id/{id} or GET /v1/pdps/asin/{asin} says how its pdp_score was computed in score_method. This guide explains the two methods, the five components of a formula score, and how to read the score_breakdown that comes with it.
Two scoring methods
score_method | What you get |
|---|---|
FORMULA | pdp_score is the sum of five weighted components. score_breakdown shows each component's score, weight, and points, scoring_version names the version of the scoring rules behind it, and formula_version gives the date its weights took effect; any of these can be null (see below). The four penalty fields are null. |
LEGACY | pdp_score comes from Vizit's earlier scoring method. There is no breakdown: score_breakdown, scoring_version, and formula_version are null, and the penalty fields (score_penalty, image_count_penalty, image_mix_penalty, image_order_penalty) say what lowered the score. |
Responses and exports can carry either method, so handle both. While status is PROCESSING or ERROR, score_method, scoring_version, formula_version, score_breakdown, unavailable_reason, and withheld_reason are all null.
Which method is my score?
Read score_method on each response. Don't assume it from your account: while an organization moves from LEGACY to FORMULA, some of its PDPs can carry one method and some the other, and a PDP switches the next time it's scored.
score = response.json()
if score["status"] != "COMPLETED":
... # still scoring, or failed: no score yet
elif score["unavailable_reason"] == "SCORE_WITHHELD":
print("withheld:", score["withheld_reason"]) # display as unscored, not 0
elif score["score_method"] == "FORMULA":
breakdown = score["score_breakdown"] # can be null, see below
for name, part in (breakdown or {}).get("components", {}).items():
print(name, part["points"], "points,", part["points_to_go"], "to go")
else: # LEGACY
penalties = [f for f in ("score_penalty", "image_count_penalty",
"image_mix_penalty", "image_order_penalty") if score[f]]
print("lowered by:", penalties)A FORMULA score can come without a breakdown; When parts of the breakdown are missing explains when.
The five components
A formula score looks at five things about the listing:
| Component | What it measures |
|---|---|
hero | The hero image's score. |
carousel | The average score of the scored carousel images. |
order | Whether the carousel images are in the order that earns full credit. With fewer than two scored carousel images, order scores 0. |
image_count | How many images besides the hero (carousel images, including informational ones) the listing has against the count its category needs for full credit. Images beyond that count lower this component too. |
asset_mix | How well the images cover the asset types in the category's ideal content mix. |
Utility, weight, and points
Each component in score_breakdown.components carries the same numbers:
utility— the component's own score, a whole number from 0 to 100.weight— the component's share of the PDP score, as a fraction from 0 to 1. The five weights add up to 1. Weights vary by category, so the same utility can be worth more in one category than in another.points— what the component adds topdp_score. When shown, the fivepointsadd up topdp_score.points_to_go— what the component could still add. For each component,pointspluspoints_to_gois its weight as a whole-number share of 100, so the fivepoints_to_goadd up to100 - pdp_score.
Points are whole numbers, rounded so that they add up exactly to pdp_score. That means a component's points can differ slightly from weight × utility, and a component whose utility shows as 100 can still have a point to go.
Here is a listing that scores 67:
| Component | utility | weight | points | points_to_go |
|---|---|---|---|---|
hero | 72 | 0.30 | 22 | 8 |
carousel | 52 | 0.25 | 13 | 12 |
order | 90 | 0.10 | 9 | 1 |
image_count | 67 | 0.15 | 10 | 5 |
asset_mix | 67 | 0.20 | 13 | 7 |
| Total | 1.00 | 67 | 33 |
The largest points_to_go shows where the listing has the most to gain: here, a stronger carousel (12 points) and a better hero image (8 points).
Tags and details
Each component also has a tag:
COMPUTED— the component was scored from the listing's content.ABSENT_CONTENT— there was nothing to score for this component, for example a listing with no carousel images, or a category that sets no content mix target. The component'sutilityis 0.
Three components add a detail object that explains their utility. hero and carousel never have one, so their detail is always null.
"order": {
"utility": 90, "weight": 0.1, "points": 9, "points_to_go": 1, "tag": "COMPUTED",
"detail": { "is_sorted": false }
},
"image_count": {
"utility": 67, "weight": 0.15, "points": 10, "points_to_go": 5, "tag": "COMPUTED",
"detail": { "count": 4, "required": 6, "short_by": 2 }
},
"asset_mix": {
"utility": 67, "weight": 0.2, "points": 13, "points_to_go": 7, "tag": "COMPUTED",
"detail": {
"types": [
{ "key": "feature_benefit", "display_name": "Feature/Benefit", "required": 2, "actual": 1, "short_by": 1 },
{ "key": "lifestyle", "display_name": "Lifestyle", "required": 2, "actual": 2, "short_by": 0 },
{ "key": "multi_pack", "display_name": "Multi-Pack", "required": 1, "actual": 0, "short_by": 1 },
{ "key": "size_and_scale", "display_name": "Size and Scale", "required": 1, "actual": 1, "short_by": 0 }
]
}
}order.detail.is_sortedistrueonly when the order earns full credit. Autilitythat rounds to 100 can still come withis_sorted: false.image_count.detailcomparescount, the listing's images besides the hero (carousel images, including informational ones), with therequiredcount.short_byis how many images are still missing; it is 0 once the listing has enough, including when it has more thanrequired.asset_mix.detail.typeshas one entry per asset type the category's ideal content mix asks for, with therequiredandactualimage counts and how many the listing isshort_by.display_namematches the classification labels on images;keyis a stable identifier to match on in code.
A detail is null when its component isn't COMPUTED or the counts can't be shown.
When parts of the breakdown are missing
Build your integration so each of these reads cleanly:
- No breakdown.
score_breakdownisnullfor everyLEGACYscore, and for aFORMULAscore when the breakdown can't be shown. Unless the score is withheld (below),pdp_scoreis still valid.scoring_versionandformula_versioncan also benullon aFORMULAscore. - No points. When points can't be shown,
pointsandpoints_to_goarenullon every component.utility,weight, andtagare still there. - Withheld score. Sometimes scoring finishes but no formula score can be produced for the listing. The response is then
COMPLETEDwithscore_method: "FORMULA",pdp_score: null,score_breakdown: null, andunavailable_reason: "SCORE_WITHHELD";vizit_certifiedandscore_changearenulltoo.withheld_reasonsays why, so you can tell your users what to fix. The hero and carousel image scores and the content mix are still in the response. Show the listing as unscored rather than as 0.
{
"status": "COMPLETED",
"pdp_score": null,
"vizit_certified": null,
"score_method": "FORMULA",
"score_breakdown": null,
"unavailable_reason": "SCORE_WITHHELD",
"withheld_reason": "HERO_IMAGE_NOT_SCORED"
}withheld_reason | What it means | What to do |
|---|---|---|
HERO_IMAGE_MISSING | The listing has no hero image. | Add a hero image. |
HERO_IMAGE_NOT_SCORED | The hero image could not be scored. | Check the image URL is reachable, or submit the PDP again. |
CAROUSEL_IMAGE_NOT_SCORED | A carousel image could not be scored. | Check the carousel image URLs, or submit the PDP again. |
CAROUSEL_IMAGES_MISSING | The listing has no carousel images that can be scored. | Add carousel images. |
IMAGE_TYPES_UNRESOLVED | Some image types could not be determined yet. | Try again later. |
CATEGORY_NOT_SUPPORTED | The product category is not set up for scoring. | Contact Vizit. |
SCORING_ERROR | Scoring failed for another reason. | Submit the PDP again; contact Vizit if it persists. |
New codes may be added, so treat a code you don't recognize like SCORING_ERROR.
In the CSV export, a withheld score leaves listing_score empty and fills the withheld_reason column.
When weights change
Vizit updates its scoring weights from time to time. New weights apply the next time a PDP is scored; an existing score keeps its weights until the PDP is scored again. formula_version tells you which weights produced a score, as the date they took effect (for example 2026-09-24). listing_score_at_ingest is the first score a PDP gets after its scoring weights or setup change, and score_change is measured from it, so score_change is 0 until the PDP is scored again. A new formula_version always restarts it; other scoring setup changes can restart it too while formula_version stays the same.
In the CSV export
subject: "pdps" exports add score_method, scoring_version, and formula_version columns, plus one utility column per component (hero_utility, carousel_utility, order_utility, image_count_utility, asset_mix_utility) that is filled on FORMULA rows. See PDP scoring columns in the Export Scores guide.
What's next
- The full response contracts are in the API Reference:
- To submit PDPs and poll for their scores: Ingest a PDP.
- To pull every PDP's scores at once: Export Scores.