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

# Find products before they peak

> Building a product discovery query that surfaces breakouts, not bestsellers.

Sorting by revenue finds products that already won. By the time a listing tops that ranking, it is
saturated: margin is compressed, creators are booked, and the window has closed.

This guide builds a query that finds products on the way up.

## What separates a breakout from a bestseller

<CardGroup cols={2}>
  <Card title="Momentum over level" icon="chart-line">
    Rank on `revenue_growth_rate`, with a revenue floor so the list stays real.
  </Card>

  <Card title="Room left in it" icon="users">
    A low `creators_count` at high revenue means the affiliate side is not crowded yet.
  </Card>
</CardGroup>

## The query

```bash theme={null}
curl -X POST https://api.unif.dev/v1/products/search \
  -H "Authorization: Bearer $UNIF_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "market": "US",
    "period": { "preset": "last_7d" },
    "currency": "USD",
    "filters": {
      "category_ids": ["cat_beauty_personal_care"],
      "price": { "gte": 15, "lte": 45 },
      "revenue": { "gte": 25000 },
      "revenue_growth_rate": { "gte": 0.5 },
      "commission_rate": { "gte": 0.15 },
      "listed_at": { "gte": "2026-06-01" }
    },
    "sort": [
      { "field": "revenue_growth_rate", "direction": "desc" },
      { "field": "revenue", "direction": "desc" }
    ],
    "limit": 50
  }'
```

Every filter is doing a specific job:

| Filter                              | Why                                                                               |
| ----------------------------------- | --------------------------------------------------------------------------------- |
| `revenue: { gte: 25000 }`           | A floor. Without it, growth ranking floats a product that went from $80 to $400.  |
| `revenue_growth_rate: { gte: 0.5 }` | +50% week over week. The actual breakout signal.                                  |
| `price: { gte: 15, lte: 45 }`       | The band where impulse buying works. Adjust to your economics.                    |
| `commission_rate: { gte: 0.15 }`    | Below 15%, creators will not prioritize it.                                       |
| `listed_at: { gte: … }`             | Recent listings. A five-year-old product growing 50% is a promotion, not a trend. |
| Second sort key                     | Breaks growth ties by size, so the top of the list is worth reading.              |

<Warning>
  `revenue_growth_rate` is a ratio: `0.5` is +50%, not 50. Passing `50` filters for products that
  grew 5,000%, and returns nothing — the most common mistake in this query.
</Warning>

## Separating real demand from discounting

A product can post explosive growth because it went on sale. Compare `avg_sale_price` against
`price.current`:

```python theme={null}
for p in results:
    paid  = p["metrics"]["avg_sale_price"]
    listed = p["price"]["current"]
    if paid < listed * 0.8:
        p["flag"] = "discount_driven"     # volume bought with margin
```

A gap wider than about 20% usually means the growth is a promotion. That can still be worth
copying — but it is a pricing decision, not a product discovery.

## Checking whether the affiliate side is crowded

`creators_count` is the competitive signal. Two products at \$150k revenue are in completely
different positions at 12 creators versus 400.

```python theme={null}
ranked = sorted(
    results,
    key=lambda p: p["metrics"]["revenue"] / max(p["metrics"]["creators_count"], 1),
    reverse=True,
)
```

Revenue per creator, sorted descending, puts the products carried by a small number of effective
creators at the top — the ones where adding one more creator still moves the number.

## Confirming it is not one video

High growth from a single viral video does not repeat. Check the shape:

```bash theme={null}
curl -X POST https://api.unif.dev/v1/videos/search \
  -H "Authorization: Bearer $UNIF_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "market": "US",
    "period": { "preset": "last_7d" },
    "filters": { "product_ids": ["prd_01k3m9x7v2q8r4t6y0b1n5d7fa"] },
    "sort": [{ "field": "revenue", "direction": "desc" }],
    "limit": 20
  }'
```

If the top video accounts for most of the product's revenue, you are looking at one lucky post. If
revenue is spread across ten or more videos from different creators, the product converts —
and that is what repeats.

```python theme={null}
video_revenue = [v["metrics"]["revenue"] for v in videos["data"]]
top_share = max(video_revenue) / sum(video_revenue)

verdict = "single_video_spike" if top_share > 0.6 else "broad_traction"
```

## Making it a standing process

Discovery is worth nothing as a one-off. Turn the query into a
[search tracker](/docs/workflows/trackers#tracking-a-search) so new entrants come to you:

```json theme={null}
{
  "name": "US beauty breakouts",
  "source": {
    "type": "search",
    "entity_type": "product",
    "search": {
      "market": "US",
      "period": { "preset": "last_7d" },
      "filters": {
        "category_ids": ["cat_beauty_personal_care"],
        "revenue": { "gte": 25000 },
        "revenue_growth_rate": { "gte": 0.5 }
      }
    }
  },
  "schedule": "daily",
  "webhook_id": "whk_01k3m9x7v2q8r4t6y0b1n5d7fa"
}
```

With no `conditions`, the tracker fires on membership change — you hear about a product the day it
enters the set, which is the entire point.

<Tip>
  Run the same tracker per leaf category rather than one broad one. Each gets its own result depth,
  and the events arrive already routed to whoever owns that category.
</Tip>
