Skip to content
Feature · Catalog

Rank the right variant, not the parent.

When a shopper uploads a photo of a red T-shirt, they should see the red variant — not a generic parent card that maps to five colours. Trooply indexes each variant as its own CLIP embedding + Qdrant point, so visual similarity decides which colour or size ranks first. Collapse at render time when you want one card per product concept.

Ingest: POST /v1/products/with-variants Siblings: GET /v1/products/{id}/variants Collapse: collapse_variants: true Quota: 1 variant = 1 product
How it works

Three endpoints, no schema migration.

Variants ship as plain Qdrant payload — nothing to migrate on existing catalogues. Opt in per product.

Step 1

Index the parent + N variants

POST /v1/products/with-variants takes a parent id and 1-50 variants. Each variant gets its own image, embedding, and Qdrant point, with parent_product_id stamped into its payload. Parent-level metadata is merged into every variant; variant-level fields win on conflict.

Step 2

Search ranks variant-accurately

Visual search uses each variant's own embedding, so a red-shirt query surfaces the red variant above the blue one. The parent is never itself a point — only the variants are searchable. Metadata on every result includes parent_product_id so your frontend can group when it wants to.

Step 3

Collapse at render (optional)

Pass collapse_variants: true on /v1/search/text or /v1/search/url. The highest-scoring variant per parent wins and carries sibling_variant_ids in its metadata — enough for an "other colours" strip without a second API call.

Shape the requests.

Index — parent with three colour variants

POST /v1/products/with-variants
Authorization: Bearer <token>

{
  "product_id": "pegasus-42",
  "metadata": {
    "name": "Pegasus 42",
    "brand": "Nike",
    "category": "Footwear > Running",
    "price": 129
  },
  "variants": [
    { "variant_id": "pegasus-42-red",   "image_url": "https://cdn…/red.jpg",   "metadata": { "color": "red"   } },
    { "variant_id": "pegasus-42-blue",  "image_url": "https://cdn…/blue.jpg",  "metadata": { "color": "blue"  } },
    { "variant_id": "pegasus-42-black", "image_url": "https://cdn…/black.jpg", "metadata": { "color": "black" } }
  ]
}

Response confirms the count + echoes every variant id. Each variant counts one-for-one against the plan's product quota — a 5-colour × 3-size catalogue is 15 products per SKU, same way merchants budget real inventory.

List siblings for a card's "other colours" strip

GET /v1/products/pegasus-42-red/variants
 → { "parent_product_id": "pegasus-42",
     "count": 3,
     "variants": [
       { "product_id": "pegasus-42-red",   "metadata": { "color": "red",   "parent_product_id": "pegasus-42" } },
       { "product_id": "pegasus-42-blue",  "metadata": { "color": "blue",  "parent_product_id": "pegasus-42" } },
       { "product_id": "pegasus-42-black", "metadata": { "color": "black", "parent_product_id": "pegasus-42" } }
     ] }

The endpoint accepts either the parent id or any variant's own id — it resolves the parent from the variant's payload. Qdrant runs a scroll against a payload index on parent_product_id, so response time is O(variant count), not O(catalog).

Collapse at search-time for tidy result grids

POST /v1/search/text
{ "query": "classic tee", "limit": 10, "collapse_variants": true }

 → { "count": 8,
     "results": [
       { "product_id": "classic-tee-blue",
         "similarity_score": 0.28,
         "metadata": {
           "parent_product_id": "classic-tee",
           "sibling_variant_ids": ["classic-tee-red", "classic-tee-black"]
         } },
       … ] }

Without the flag, the same query returns the blue and red variants as separate rows. The flag is opt-in per request — other callers on the same tenant continue to get the full grid.

When to collapse, when not to.

Leave collapse off when…

  • Shopper is searching by image — they usually want the exact colour they uploaded.
  • You're surfacing an "available in these colours" grid on the PDP — the duplicates ARE the signal.
  • You want the ranker to train on shopper clicks against specific variants (the feedback loop records per-variant clicks regardless).

Turn collapse on when…

  • Text-search result grids where a 10-row page is visually clogged by 3 colours of the same SKU.
  • Category landing pages where you want one card per concept.
  • Any surface where sibling_variant_ids can drive an inline "other colours" chip strip.

Index a catalogue in 15 minutes.

Sign up, grab an API key, POST your parents + variants. Trooply handles CLIP embedding, colour extraction, quality scoring, moderation — per variant, automatically.