search endpoint that
takes the same request shape, so learning one teaches you all five.
Why POST
Search takes a JSON body rather than query parameters. Filter sets are nested and open-ended — ranges, arrays, several keys at once — and URL-encoding them produces requests that are long, fragile and miserable to debug. A body keeps a filter set readable, diffable and storable as the same JSON you can replay into a tracker later.These POSTs are read-only and have no side effects. They are safe to retry, and they do not
need an
Idempotency-Key.Range filters
Numeric and date filters take a range object. Combine keys to close a range on both sides.category_ids are OR within the array — the
example below means “beauty or wellness, priced 15 to 45”.
OR across different filter keys. When you need one, run two searches and merge on
id — it is clearer than a query language nobody can read six months later.
Sorting
sort takes up to three keys, applied in order.
filters plus
revenue as a tiebreaker gives a list a human would agree with.
Pagination
Responses are cursor-paginated. Readnext_cursor and stop when has_more is false.
Result depth
A search pages to a depth of 1,000 rows. Past that,has_more is false even when more
entities match — total_count tells you how many there were.
This is a real limit, not a tuning knob: ranked commerce data is served as a ranking, and depth
beyond the top of it is not meaningfully ordered.
Narrow the filters
Narrow the filters
A revenue floor or a tighter price band usually turns 40,000 matches into the few hundred you
actually wanted. This is the right fix most of the time.
Hand it to a job
Hand it to a job
An export job shards for you and returns CSV or JSONL. Use it for
anything that feeds a warehouse.
Zero results
An emptydata array with total_count: 0 is a finding, not an error — and it is free. Zero-
result searches cost no credits.
If a search returns unexpectedly little, check these in order:
1
Is the market entitled?
An unentitled market returns
coverage_error, not an empty list. If you got a 200, this is
not the problem.2
Is every filter field filterable here?
A
partial field that is filterable will silently exclude entities where it is missing. Check
GET /coverage.3
Was the period adjusted?
If
period.adjusted is true, you measured a wider window than you asked for, and your growth
filters mean something slightly different.4
Are the thresholds in the right currency?
"revenue": { "gte": 100000 } is 100,000 units of your requested currency. In IDR that is
a very different bar than in USD.