> ## Documentation Index
> Fetch the complete documentation index at: https://docs.virtual.fit/llms.txt
> Use this file to discover all available pages before exploring further.

# Recommendations API

> Rank similar, complete-the-look, and likely-purchase recommendations from your backend.

Use `POST /recommendations` to rank products from a Shopify or Custom JavaScript catalog. Call this endpoint from a trusted backend with your virtual.fit API key.

<Warning>
  Never expose your API key in browser JavaScript, a theme file, or a mobile application.
</Warning>

Send requests to `https://api.virtual.fit`.

## Request

<Tabs>
  <Tab title="Custom JavaScript">
    ```bash theme={null}
    curl --request POST \
      "https://api.virtual.fit/recommendations" \
      --header "Authorization: Bearer $VIRTUAL_FIT_API_KEY" \
      --header "Content-Type: application/json" \
      --data '{
        "site_key": "site_pub_your_key",
        "intent": "complete_the_look",
        "product_ids": ["linen-shirt", "tailored-trousers"],
        "cart_product_ids": ["canvas-tote"],
        "exclude_product_ids": ["sold-out-belt"],
        "shopper_id": "anon-session-7f19b2",
        "limit": 8,
        "surface": "outfit_builder"
      }'
    ```
  </Tab>

  <Tab title="Shopify">
    ```bash theme={null}
    curl --request POST \
      "https://api.virtual.fit/recommendations" \
      --header "Authorization: Bearer $VIRTUAL_FIT_API_KEY" \
      --header "Content-Type: application/json" \
      --data '{
        "shop_domain": "your-store.myshopify.com",
        "intent": "similar",
        "product_ids": ["gid://shopify/Product/9876543210"],
        "shopper_id": "customer-48291",
        "limit": 8,
        "surface": "product_page"
      }'
    ```
  </Tab>
</Tabs>

### Body fields

| Field                   | Required                                | Description                                                                                                                                                                                                                       |
| ----------------------- | --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `site_key`              | For Custom JavaScript                   | The public site key owned by the API-key account.                                                                                                                                                                                 |
| `shop_domain`           | For Shopify                             | The store's canonical `.myshopify.com` domain.                                                                                                                                                                                    |
| `intent`                | Yes                                     | `similar`, `complete_the_look`, or `likely_purchases`. See [Intent semantics](#intent-semantics).                                                                                                                                 |
| `product_ids`           | By intent                               | Exactly one ID for `similar`; one to five outfit products for `complete_the_look`; optional current or selected products for `likely_purchases`. For `complete_the_look`, put the primary outfit item first.                      |
| `cart_product_ids`      | For a live cart                         | The latest full cart, up to 500 product IDs. Required for cart-only `likely_purchases` requests. Cart items are excluded from results and influence multi-item ranking, but do not occupy outfit categories.                      |
| `candidate_product_ids` | No                                      | Restrict ranking to a non-empty array of at most 10,000 product IDs. Useful when your backend has already applied availability, market, or collection filters.                                                                    |
| `exclude_product_ids`   | No                                      | Up to 500 product IDs that must not appear. Use this as an explicit availability or merchandising override. Context products are excluded automatically.                                                                          |
| `shopper_id`            | Recommended for shopper-facing requests | Your stable, opaque customer or anonymous-session identifier. virtual.fit uses it only to keep one shopper from consuming the store's shared request allowance. Do not send an email address or other directly identifying value. |
| `limit`                 | No                                      | Maximum results. Defaults to `12` and is capped at `100`.                                                                                                                                                                         |
| `surface`               | No                                      | Your placement label, such as `product_page`, `cart`, or `fitting_room`.                                                                                                                                                          |

Use the same stable IDs that you send during catalog sync and commerce tracking. For Shopify, send the full GraphQL product ID, such as `gid://shopify/Product/9876543210`. Unknown or disabled candidate IDs are ignored. If `candidate_product_ids` is present, it must be an array containing at least one ID, and at least one supplied ID must resolve to an enabled product; otherwise the request fails with `400`. Omitting the field leaves ranking open to the enabled catalog. A request fails when none of its ranking context is prepared.

The JSON body is limited to 1 MiB. Each product ID and `shopper_id` is limited to 256 UTF-8 bytes. Requests over a body, identifier, or array limit fail with `413`.

## Response

```json theme={null}
{
  "items": [
    {
      "product_id": "leather-loafer",
      "target_type": "variant",
      "variant_id": "black-42",
      "recommended_option_values": {
        "color": "Black",
        "size": "42"
      },
      "rank": 1,
      "score": 1.274,
      "title": "Leather Loafer",
      "handle": "leather-loafer",
      "canonical_url": "https://shop.example/products/leather-loafer?variant=black-42",
      "image_url": "https://cdn.example.com/leather-loafer-black.jpg",
      "variant_title": "Black / 42",
      "price": "129.0000",
      "compare_at_price": "159.0000",
      "currency": "USD",
      "available": true,
      "fit_evidence": {
        "review_count": 128,
        "analyzed_review_count": 42,
        "average_rating": 4.7,
        "signals": [
          {
            "dimension_code": "overall",
            "evidence_count": 34,
            "average_score": 0.03,
            "tight_count": 5,
            "neutral_count": 25,
            "loose_count": 4
          }
        ]
      }
    }
  ],
  "unknown_context_ids": []
}
```

`rank` defines the display order. `score` is optional and meaningful only within that response. Do not re-sort items by score or compare scores across requests.

Each item includes the selected target plus the product fields needed for a
card. It deliberately omits full galleries, sibling variants, and complete
option matrices.

| Field                                   | Meaning                                                                                                                                                                                                                                 |
| --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `target_type`                           | `variant` for one exact purchasable choice, `visual_group` for a best color/style without choosing every purchase option, or `product` when no narrower target is available.                                                            |
| `variant_id`                            | Stable source variant ID when `target_type` is `variant`; otherwise `null`.                                                                                                                                                             |
| `recommended_option_values`             | The selected color/style and, for an exact variant, its full option selection. Apply these values before falling back to storefront defaults.                                                                                           |
| `canonical_url`                         | Exact variant URL when available, otherwise the product URL.                                                                                                                                                                            |
| `image_url`                             | One prepared display image for the selected target.                                                                                                                                                                                     |
| `price`, `compare_at_price`, `currency` | Selected variant pricing with product pricing as the fallback. Amounts are decimal strings.                                                                                                                                             |
| `available`                             | `true` when current availability is known; `null` when the source does not provide reliable inventory. Known-unavailable targets are omitted.                                                                                           |
| `fit_evidence`                          | Optional compact rating and fit summary. `review_count` is the source aggregate; `analyzed_review_count` is the number of full reviews available for fit analysis. Full review text and media are not included in recommendation cards. |

virtual.fit returns at most one item per product. A visually best color can be
returned as `visual_group` without inventing a size; an exact in-stock variant
can be returned as `variant`.

`unknown_context_ids` contains supplied context IDs that are not prepared in the selected catalog. The request can still succeed when at least one other context product is available.

## Same-category recommendations

Send exactly one context product:

```json theme={null}
{
  "site_key": "site_pub_your_key",
  "intent": "similar",
  "product_ids": ["linen-shorts"],
  "limit": 12
}
```

You may also send `candidate_product_ids` when your backend has a more specific constraint, such as an in-stock collection or shopper market.

You do not need to send a category. virtual.fit categorizes prepared products automatically. Keep Custom JavaScript product titles and images current so `similar` can return relevant alternatives.

Recommendation eligibility is broader than try-on eligibility. Apparel accessories such as shoes, bags, and jewellery can appear in recommendation results even when they are not supported as virtual try-on items.

## Intent semantics

### `complete_the_look`

The hosted fitting room uses this intent automatically. Call it directly for another outfit placement or your own outfit builder. It excludes categories already represented by known context products.

If no eligible compatible product remains, `items` can be empty. Treat that as a valid result instead of widening it to unrelated products.

Send one to five products already selected for the outfit. Send the current cart separately when it should also influence relevance:

```json theme={null}
{
  "site_key": "site_pub_your_key",
  "intent": "complete_the_look",
  "product_ids": [
    "linen-shirt",
    "tailored-trousers",
    "suede-jacket"
  ],
  "cart_product_ids": ["canvas-tote"],
  "limit": 8
}
```

Only `product_ids` determines which outfit categories are already occupied. `cart_product_ids` improves the ranking and is excluded from results, but a bag sitting in the cart does not prevent the outfit builder from recommending another bag.

### `likely_purchases`

Use this for “You may also like,” cart, post-add-to-cart, and general cross-sell placements. It uses the same current-product context and learned purchase behavior, but it does not remove categories already represented in that context:

```json theme={null}
{
  "site_key": "site_pub_your_key",
  "intent": "likely_purchases",
  "cart_product_ids": ["linen-shirt", "tailored-trousers"],
  "limit": 8,
  "surface": "cart"
}
```

`likely_purchases` accepts a current product, selected products, a cart, or any combination of them. All supplied products are excluded automatically. Your backend must send the latest full cart in `cart_product_ids`; it affects that request immediately. Synced cart and purchase events can improve recommendations over time.

## Error handling

### Rate limits

The recommendations API uses short-term safety thresholds of `60` requests per shopper per minute and `6,000` requests per store per minute. Treat these values as usage guidance rather than exact quotas, and always handle `429` responses.

Send `shopper_id` on shopper-facing backend requests. Different shoppers then use separate allowances even when every request leaves your infrastructure through the same IP address. Authenticated requests are not limited by the merchant backend's shared egress IP.

The hosted Shopify and Custom JavaScript browser integrations supply shopper identity automatically. Direct browser requests also use a short-term safety threshold of `600` requests per IP per minute. Treat it as usage guidance and handle `429` responses.

Rate-limited requests return `429` with `Retry-After` and `retry_after_seconds`. Wait for that duration before retrying. Keep your existing merchandising order visible while waiting.

| Status | Meaning                                                                                                                          |
| ------ | -------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | The integration selector, intent, product count, or supplied candidate set is invalid.                                           |
| `401`  | The API key is missing or invalid.                                                                                               |
| `413`  | The JSON body, a product-ID array, a product ID, or `shopper_id` exceeds its documented limit.                                   |
| `429`  | The shopper, store, or public client IP exceeded a short-term safety limit. Wait for the `Retry-After` duration before retrying. |
| `404`  | The requested storefront is missing or inaccessible, or none of the context products are prepared.                               |
| `503`  | Recommendations are temporarily unavailable. Retry with backoff or use your existing fallback order.                             |

Keep your current merchandising order as a fallback. A temporary recommendation failure should not hide eligible products or block checkout.
