API Referencev3

Vision Identify

POST /v3/vision/identify. Identify a luxury item from one or more photos.

POST /v3/vision/identify

Legacy path: POST /api/v3/vision/identify (same behavior).

Send one or more photos of an item and get matching catalog rows in the same shape as Search catalog, each with a confidence. Ask for models (the catalog model, e.g. Neverfull), styles (the model's material or pattern, e.g. Damier Ebene) or products (model + style, with variants and pricing) with taxonomy_node, and narrow with the same filters as catalog search.

Two-step flow

The recommended flow for a listing app, one request each:

  1. Model step — the front photo, taxonomy_node: "model": a ranked shortlist of catalog models. Add "facets" to includes to get the brands present in the shortlist with their counts, so you can ask the user "is it one of these brands?" and re-run with a brand filter when the shortlist mixes brands. Add "next_node" to get each model's most likely styles in the same call, for a card that shows the style the photo matched.
  2. Product step — all photos, taxonomy_node: "product", filters: [{ "field": "model", "include": ["model_…"] }] for the model the user picked: that model's products ranked. Use taxonomy_node: "style" with the same filter to get the same ranking as style rows (id, name, images, pricing summary) without the variants.

When the user confirms a product, send its id and the prediction_id to Vision feedback; send the prediction_id alone if they chose none.

Try it in the API Explorer: it opens on the model step: replace the example URL with a photo of your item, and after a call it offers to send feedback for that prediction.

Request

Model step

curl -X POST https://api.pricehero.ai/v3/vision/identify \
  -H "Authorization: Bearer ph_test_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "images": [{ "base64": "data:image/jpeg;base64,/9j/4AAQ..." }],
    "taxonomy_node": "model",
    "filters": [{ "field": "category", "include": ["handbags"] }],
    "page_size": 10,
    "includes": ["taxonomy", "facets"]
  }'

Product step

All photos of the item, restricted to the model the user picked:

curl -X POST https://api.pricehero.ai/v3/vision/identify \
  -H "Authorization: Bearer ph_test_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "images": [
      { "base64": "data:image/jpeg;base64,/9j/4AAQ..." },
      { "base64": "data:image/jpeg;base64,/9j/4AAQ..." },
      { "url": "https://example.com/my-bag-side.jpg" }
    ],
    "taxonomy_node": "product",
    "filters": [
      { "field": "category", "include": ["handbags"] },
      { "field": "model", "include": ["model_FR8RWgt7www6LCLVic8hUg"] }
    ],
    "includes": ["taxonomy", "pricing"]
  }'

One photo, legacy form

The original one-image form still works and returns products:

curl -X POST https://api.pricehero.ai/v3/vision/identify \
  -H "Authorization: Bearer ph_test_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://example.com/my-bag.jpg", "includes": ["taxonomy", "pricing"] }'

Body parameters

ParameterTypeDefaultDescription
imagesarray;Up to 5 photos, each { "url": "https://…" } or { "base64": "data:image/…;base64,…" } (raw base64 also accepted). The first image is the front view. Base64 is limited to 1 MB per image (413 above).
url, base64string;One-image alias of images (kept for existing integrations). Ignored when images is present.
taxonomy_nodestringproductmodel for the model shortlist, style for styles (model + material), product for products. model and style belong to the handbags hierarchy, so those requests are scoped to handbags on their own; naming a category whose hierarchy lacks the node is a 400 that lists that category's nodes.
category_idstring;Alias of a category filter; kept for existing integrations.
filtersarray;Same as catalog search: { "field": "category" | "brand" | "model" | …, "include": ["id", …], "exclude": [...] }. OR within one object, AND across objects. A category entry with one id (e.g. handbags) is recommended: it selects the recognition for that category and scopes the results; several categories act as a plain facet on the photo-and-text path. A model entry on the product step selects that model's styles (at most 3 ids).
pageinteger0Page index (0-based)
page_sizeinteger10Page size, max 50 on every node (larger values are capped). "Show more" on the model step is the next page; the model step pages up to rank 100.
includesstring[]["taxonomy"]taxonomy, pricing, facets (free: counts per attribute value over the returned rows) and next_node (free: each row's top rows at the next level of its category hierarchy).
allow_trainingbooleanfalseLets Price Hero keep this request's photos to improve recognition. Only for organizations whose API Commercial Agreement includes training; otherwise a 403, with test keys too. A test key can send it to build the same request as live: it is validated the same way but photos are never kept, and the response carries a warning saying so. Applies to photo recognition only (a handbags category filter); otherwise the request succeeds with a warning that it was not applied. Photos are deleted within 7 days unless their feedback is accepted. Photos whose feedback is held for review are kept up to 60 days until the review, and deleted if it is not confirmed by then.

Response

Same envelope and item shape as catalog search (resolved_taxonomy is omitted when the rows span several categories), plus:

  • confidence on every row, in [0, 1]: how strongly the photos match that row. Rows are ordered by it. Model rows below a minimum similarity are not returned, so a page can be shorter than page_size and total_results stops there. Compare rows with each other rather than against a fixed threshold: a new photo of a catalog item typically scores 0.3–0.7 on its right row, higher only for near-identical photos.
  • total_results and total_pages count exactly on every step; on the model step they count the models above the similarity floor within the engine's reach of 100 ranks.
  • prediction_id on every successful response: pass it to Vision feedback once the user has chosen.
  • warnings when the request used a deprecated or alternate spelling (same information as the response headers), on a test key when the sandbox reduced the answer (see above), and when allow_training was sent but not applied (a test key, or a request outside photo recognition).
  • facets when requested: { "<field>": [{ "id", "name", "count" }] }, most frequent first. The rows are ranked by confidence, so the first brand in data is the best-scoring one.
  • next_node when requested, on every row: { "node", "level", "data": [...] }, the row's top rows at the next level of its category hierarchy, node and level spelled as in resolved_taxonomy, three rows at most, ranked by confidence, in the item shape without attributes (the parent row and node already say what a child is). Style children carry no pricing; product children carry their variants' pricing when pricing is included, and the request is then billed as a product-row request. A model row carries its most likely styles, a style row its products; a product row has no next level and carries null. It is computed only when requested, so leave it out when speed matters more than the preview.

Model and style rows (taxonomy_node: "model" / "style") have no variants; they carry attributes (brand, model, and for a style its model), the entity's images, and, with pricing, a summary pricing.market_estimates (best estimate). Product rows carry variants exactly as in catalog search.

{
  "success": true,
  "results": 2,
  "total_results": 10,
  "page": 0,
  "total_pages": 1,
  "credits_used": 5,
  "includes": ["taxonomy", "facets"],
  "prediction_id": "0f2d5f4e-8c1a-4a2b-9e5d-1c3b7a9d2e40",
  "resolved_taxonomy": { "node": "model", "level": 3 },
  "facets": {
    "brand": [
      {
        "id": "brand_9YJxjSHS5UK1jFmA2dThev",
        "name": "Louis Vuitton",
        "count": 2
      }
    ]
  },
  "data": [
    {
      "id": "model_FR8RWgt7www6LCLVic8hUg",
      "name": "Sevigne Clutch",
      "confidence": 0.98,
      "attributes": [
        { "field": "category", "id": "handbags", "name": "Handbags" },
        { "field": "brand", "id": "brand_9YJxjSHS5UK1jFmA2dThev", "name": "Louis Vuitton" }
      ],
      "popularity": 40,
      "images": ["https://cdn.pricehero.ai/img/model_FR8RWgt7www6LCLVic8hUg.jpg"],
      "thumbnails": ["https://cdn.pricehero.ai/thumb/model_FR8RWgt7www6LCLVic8hUg.jpg"]
    },
    {
      "id": "model_VdAGNxAApLCRukwzSUBZSb",
      "name": "Sevigne Handbag",
      "confidence": 0.57,
      "attributes": [
        { "field": "category", "id": "handbags", "name": "Handbags" },
        { "field": "brand", "id": "brand_9YJxjSHS5UK1jFmA2dThev", "name": "Louis Vuitton" }
      ],
      "popularity": 35,
      "images": ["https://cdn.pricehero.ai/img/model_VdAGNxAApLCRukwzSUBZSb.jpg"],
      "thumbnails": ["https://cdn.pricehero.ai/thumb/model_VdAGNxAApLCRukwzSUBZSb.jpg"]
    }
  ]
}

A product-step response looks like a catalog search for products, with confidence on each product and prediction_id at the top level.

Test keys see only the sandbox subset of the catalog, as on catalog search: the shortlist is drawn from the sandbox-enabled models and products, so use photos of items in the sandbox catalog when testing, and only sandbox ids in filters (a model outside the sandbox is refused with filters: <id> is not in the sandbox catalog; an id that is no model at all is filters: unknown model id, as for a live key). When the sandbox reduces the answer, the response says so in warnings: the closest match in the full catalog is outside the sandbox (with the two scores and nothing else about that item), or some of the matching products were left out because they are not in the sandbox (counts only). An empty data means nothing matched; it is not charged. On the photo-and-text path (no category scope), two outcomes answer 200 with success: false and an error instead, also uncharged: the photo is not a handbag, small leather good or watch, or no catalog product matched the description. Treat recognition as an assist and keep a text search fallback.

Without a category filter the request behaves as it always has: one photo is analyzed and products are returned, the same shape with confidence and prediction_id added. The category scope is what turns on photo recognition for that category, so send it whenever you know it.

Image guidelines

  • Resize on the client to about 1024 px on the longest side, JPEG quality ~85 (around 200 KB). Recognition works at a much smaller size, so larger uploads only cost transfer time; base64 above 1 MB is rejected with 413.
  • First image = the front of the item; add a back and a side view for the product step (1–3 photos is enough).
  • Formats: JPEG, PNG, WebP. URLs must be publicly reachable image files (fetched with a 12 MB cap; redirects are followed to public hosts only).

Errors

StatusWhen
400No image, more than 5 images, an image entry with neither url nor base64, an unknown taxonomy_node, a malformed filters, an unknown brand/model id, or taxonomy_node: "model" with a category whose hierarchy has no model node
403allow_training for an organization whose agreement does not include training (test and live keys)
402Not enough credits
413A base64 image above 1 MB
503Recognition temporarily unavailable; retry

Credits

See Credits. A vision request is billed once per call when it returns at least one row: 5 credits with taxonomy, 10 with pricing on product rows, including product children returned through next_node. pricing on model rows is a free summary, and facets and next_node are always free.

Vision Identify — Price Hero Docs