Skip to content

Finding your Top Products

Free beta

This query is part of the Product Intelligence API, which is in beta and free while it's in beta. Both the schema and the pricing can change.

Where productInsight answers "what do I know about this specific product?", the topProducts query answers the inverse question: "which of my products best match this criterion?". Given a performance dimension (best sold on Tuesdays, highest lifetime value, best candidates for a 10% discount), it returns a ranked list of your products.

Typical uses:

  • Campaign targeting: "give me the 10 best products to promote this Tuesday"
  • Discount optimization: "which products should get the 10% voucher?"
  • Time-based promotions: "what sells on Saturday mornings?"
  • Feeding ranked product lists into email campaigns, repricing engines or ad platforms

The API is available on the same URL as the rest of the Product Intelligence API, with the same authentication:

https://core.helloretail.com/pi/graphql

Products are ranked by how they perform on the requested dimension. Products with too little purchase history are excluded, so the results are backed by meaningful amounts of data.

The query

topProducts(
    websiteUuid: String!
    dimension: RankDimension!
    weekday: Int
    hourFrom: Int
    hourTo: Int
    discountFrom: Int
    discountTo: Int
    filters: [MetricFilter]
    limit: Int = 10
): [RankedProduct]

You can only request products for websites that you have access to.

Dimensions

The dimension argument selects what to rank by. Some dimensions need one or more extra arguments:

  • DAY_OF_WEEK: Products most often bought on a specific day of the week. Requires weekday (Monday is 1, Sunday is 7).
  • TIME_OF_DAY: Products most often bought within an hour window. Requires hourFrom and hourTo (0-23, both inclusive; "around 5pm" could be hourFrom: 16, hourTo: 18).
  • DAY_AND_TIME_OF_DAY: Products most often bought on a specific day within an hour window. Requires weekday, hourFrom and hourTo.
  • DISCOUNT: Products most likely to be purchased within a given discount range. Requires discountFrom and discountTo (1-100, both inclusive).

    The underlying discount data is grouped in bands of 10 percentage points (1-10, 11-20, 21-30, ...), so a range covers every band it touches: discountFrom: 15, discountTo: 35 ranks across the 11-20, 21-30 and 31-40 bands, exactly as discountFrom: 11, discountTo: 40 would. To rank a single band, make the range its own width (discountFrom: 11, discountTo: 20, or any single value inside the band).

    As a special case, the range exactly 0-0 ranks by full-price (undiscounted) purchases. 0 cannot be part of a wider range. - PRODUCT_LIFETIME_VALUE: Products whose buyers generate the most expected future revenue. - RETENTION_RATE: Products whose buyers are most likely to come back and buy again. - RECURRENCE_RATE: Products most likely to be repurchased.

Passing a dimension without its required arguments (or with arguments it does not accept) returns a validation error telling you which arguments are missing.

Filters

Results can optionally be narrowed with threshold conditions on the products' metrics. Filters combine with AND:

filters: [
    {metric: RETENTION_RATE, operator: GT, value: 0.4},
    {metric: DAY_OF_WEEK, weekday: 6, operator: GT, value: 0.2},
    {metric: DISCOUNT, discountFrom: 0, discountTo: 0, operator: GTE, value: 0.9}
]
  • metric: Any of the ranking dimensions (PRODUCT_LIFETIME_VALUE, RETENTION_RATE, RECURRENCE_RATE, DAY_OF_WEEK, TIME_OF_DAY, DAY_AND_TIME_OF_DAY, DISCOUNT), plus PRICE.
  • operator: One of GT, GTE, LT, LTE.
  • value: The threshold.

The parameterized metrics take the same arguments inside the filter as their dimension takes on the query: DAY_OF_WEEK a weekday, TIME_OF_DAY an hourFrom/hourTo window, DAY_AND_TIME_OF_DAY all three, and DISCOUNT a discountFrom/discountTo range — with the same rules, including the 10-point band rounding and the 0-0 full-price special case. The condition compares against the same 0-1 share that dimensionValue reports for that dimension, so the example above reads: retention above 0.4, more than 20% of purchases on Saturdays, and at least 90% of purchases at full price.

This makes rank-by-one-dimension, filter-by-another queries possible — for example, rank by RECURRENCE_RATE but exclude products whose sales are discount-driven.

Note that PRICE filters on the typical price at which the product sells, which can differ from its current catalog price. Products without data for a filtered metric are excluded.

Limit

limit controls how many products are returned: default 10, maximum 100.

Example

query {
    topProducts(
            websiteUuid: "a7ce8931-6473-4627-9127-ceafa74ae0a8",
            dimension: DISCOUNT,
            discountFrom: 1,
            discountTo: 10,
            filters: [{metric: RETENTION_RATE, operator: GT, value: 0.4}],
            limit: 3) {
        url
        title
        productNumber
        price
        score
        dimensionValue
        retentionRate
    }
}

A wider range works the same way — this ranks across everything from 11% through 40% off (the range rounds out to the 10-point bands it touches):

query {
    topProducts(
            websiteUuid: "a7ce8931-6473-4627-9127-ceafa74ae0a8",
            dimension: DISCOUNT,
            discountFrom: 15,
            discountTo: 35,
            limit: 3) {
        url
        title
        score
        dimensionValue
    }
}

Tip: Try copying the above query to the graphql tester, change the websiteUuid to one of your own websites, and execute the query.

This returns a result like:

{
    "data": {
        "topProducts": [
            {
                "url": "https://example.com/products/baggy-denim-jeans-5",
                "title": "Baggy Denim Jeans",
                "productNumber": "BDJ-005",
                "price": 79.95,
                "score": 0.8571,
                "dimensionValue": 0.3921,
                "retentionRate": 0.5914
            },
            {
                "url": "https://example.com/products/slim-fit-chinos-2",
                "title": "Slim Fit Chinos",
                "productNumber": "SFC-002",
                "price": 69.95,
                "score": 0.6234,
                "dimensionValue": 0.3105,
                "retentionRate": 0.5321
            },
            {
                "url": "https://example.com/products/wool-sweater-9",
                "title": "Wool Sweater",
                "productNumber": "WS-009",
                "price": 119.00,
                "score": 0.5102,
                "dimensionValue": 0.2274,
                "retentionRate": 0.4406
            }
        ]
    }
}

The returned products

Products are returned best first, ordered by their score: a 0-1 indication of how well the product matches the requested dimension. Higher is better.

Each result offers:

  • url: The product's URL, identifying it in your catalog.
  • title: The product's display name.
  • productNumber: The product's number in your catalog.
  • price: The product's price.
  • imgUrl: The product's image URL.
  • score: How well the product matches the requested dimension, as a 0-1 score.
  • dimensionValue: The product's value on the requested dimension. For DAY_OF_WEEK, for example, this is the share of purchases that happen on that day.

In addition, every metric available on productInsight (productLifetimeValue, retentionRate, discountData, purchaseDayOfWeekData and the rest) can be selected directly on each result.

If no products match, because the filters exclude everything or your products lack data on the requested dimension, the query returns an empty list, not an error.

Example request using javascript

const query = `query {
    topProducts(
            websiteUuid: "{website id}",
            dimension: DAY_OF_WEEK,
            weekday: 2,
            limit: 10) {
        url
        title
        score
    }
}`;
const response = await fetch("https://core.helloretail.com/pi/graphql?apiKey=XXX", {
    "headers": {
        "Content-Type": "application/json"
    },
    "body": JSON.stringify({query}),
    "method": "POST"
});
console.log(await response.json());