curl --request POST \
--url https://api.unif.dev/v1/products/search \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"market": "US",
"period": {
"preset": "last_7d"
},
"currency": "USD",
"filters": {
"price": {
"gte": 15,
"lte": 45
},
"units_sold": {
"gte": 500
},
"revenue_growth_rate": {
"gte": 0.5
},
"commission_rate": {
"gte": 0.15
},
"launched_at": {
"gte": "2026-06-01"
}
},
"sort": [
{
"field": "revenue",
"direction": "desc"
}
],
"limit": 50
}
'import requests
url = "https://api.unif.dev/v1/products/search"
payload = {
"market": "US",
"period": { "preset": "last_7d" },
"currency": "USD",
"filters": {
"price": {
"gte": 15,
"lte": 45
},
"units_sold": { "gte": 500 },
"revenue_growth_rate": { "gte": 0.5 },
"commission_rate": { "gte": 0.15 },
"launched_at": { "gte": "2026-06-01" }
},
"sort": [
{
"field": "revenue",
"direction": "desc"
}
],
"limit": 50
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
market: 'US',
period: {preset: 'last_7d'},
currency: 'USD',
filters: {
price: {gte: 15, lte: 45},
units_sold: {gte: 500},
revenue_growth_rate: {gte: 0.5},
commission_rate: {gte: 0.15},
launched_at: {gte: '2026-06-01'}
},
sort: [{field: 'revenue', direction: 'desc'}],
limit: 50
})
};
fetch('https://api.unif.dev/v1/products/search', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.unif.dev/v1/products/search",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'market' => 'US',
'period' => [
'preset' => 'last_7d'
],
'currency' => 'USD',
'filters' => [
'price' => [
'gte' => 15,
'lte' => 45
],
'units_sold' => [
'gte' => 500
],
'revenue_growth_rate' => [
'gte' => 0.5
],
'commission_rate' => [
'gte' => 0.15
],
'launched_at' => [
'gte' => '2026-06-01'
]
],
'sort' => [
[
'field' => 'revenue',
'direction' => 'desc'
]
],
'limit' => 50
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.unif.dev/v1/products/search"
payload := strings.NewReader("{\n \"market\": \"US\",\n \"period\": {\n \"preset\": \"last_7d\"\n },\n \"currency\": \"USD\",\n \"filters\": {\n \"price\": {\n \"gte\": 15,\n \"lte\": 45\n },\n \"units_sold\": {\n \"gte\": 500\n },\n \"revenue_growth_rate\": {\n \"gte\": 0.5\n },\n \"commission_rate\": {\n \"gte\": 0.15\n },\n \"launched_at\": {\n \"gte\": \"2026-06-01\"\n }\n },\n \"sort\": [\n {\n \"field\": \"revenue\",\n \"direction\": \"desc\"\n }\n ],\n \"limit\": 50\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.unif.dev/v1/products/search")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"market\": \"US\",\n \"period\": {\n \"preset\": \"last_7d\"\n },\n \"currency\": \"USD\",\n \"filters\": {\n \"price\": {\n \"gte\": 15,\n \"lte\": 45\n },\n \"units_sold\": {\n \"gte\": 500\n },\n \"revenue_growth_rate\": {\n \"gte\": 0.5\n },\n \"commission_rate\": {\n \"gte\": 0.15\n },\n \"launched_at\": {\n \"gte\": \"2026-06-01\"\n }\n },\n \"sort\": [\n {\n \"field\": \"revenue\",\n \"direction\": \"desc\"\n }\n ],\n \"limit\": 50\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.unif.dev/v1/products/search")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"market\": \"US\",\n \"period\": {\n \"preset\": \"last_7d\"\n },\n \"currency\": \"USD\",\n \"filters\": {\n \"price\": {\n \"gte\": 15,\n \"lte\": 45\n },\n \"units_sold\": {\n \"gte\": 500\n },\n \"revenue_growth_rate\": {\n \"gte\": 0.5\n },\n \"commission_rate\": {\n \"gte\": 0.15\n },\n \"launched_at\": {\n \"gte\": \"2026-06-01\"\n }\n },\n \"sort\": [\n {\n \"field\": \"revenue\",\n \"direction\": \"desc\"\n }\n ],\n \"limit\": 50\n}"
response = http.request(request)
puts response.read_body{
"object": "list",
"has_more": true,
"data": [
{
"id": "prd_01k3m9x7v2q8r4t6y0b1n5d7fa",
"object": "product",
"channel": "tiktok_shop",
"market": "US",
"title": "Ceramide Barrier Repair Serum 30ml",
"url": "<string>",
"image_url": "<string>",
"brand": "<string>",
"shop": {
"id": "shp_01k3m9x7v2q8r4t6y0b1n5d7fa",
"name": "GlowLab",
"url": "<string>"
},
"category": {
"id": "cat_beauty_personal_care",
"name": "Beauty & Personal Care",
"path": [
"<string>"
]
},
"price": {
"current": 123,
"min": 123,
"max": 123
},
"commission_rate": 123,
"rating": 2.5,
"reviews_count": 123,
"listed_at": "2023-12-25",
"metrics": {
"revenue": 123,
"units_sold": 123,
"avg_sale_price": 123,
"revenue_growth_rate": 123,
"revenue_by_source": {
"video": 123,
"live": 123,
"mall": 123,
"search": 123
},
"creators_count": 123,
"videos_count": 123,
"livestreams_count": 123,
"gpm": 123
},
"period": {
"start": "2023-12-25",
"end": "2023-12-25",
"granularity": "day",
"adjusted": true
},
"currency": "<string>",
"meta": {
"refreshed_at": "2023-11-07T05:31:56Z",
"completeness": 0.5,
"missing_fields": [
"<string>"
]
}
}
],
"next_cursor": "<string>",
"total_count": 123,
"period": {
"start": "2023-12-25",
"end": "2023-12-25",
"granularity": "day",
"adjusted": true
},
"currency": "<string>"
}{
"error": {
"type": "invalid_request_error",
"code": "parameter_invalid",
"message": "filters.revenue.gte must be a non-negative number.",
"param": "filters.revenue.gte",
"doc_url": "https://unif.dev/docs/platform/errors#parameter_invalid",
"request_id": "req_01k3m9x7v2q8r4t6y0b1n5d7fa"
}
}{
"error": {
"type": "authentication_error",
"code": "invalid_api_key",
"message": "No API key was provided in the Authorization header.",
"doc_url": "https://unif.dev/docs/platform/errors#invalid_api_key",
"request_id": "req_01k3m9x7v2q8r4t6y0b1n5d7fa"
}
}{
"error": {
"type": "billing_error",
"code": "insufficient_credits",
"message": "This request costs 25 credits and 4 remain in the current period.",
"doc_url": "https://unif.dev/docs/platform/credits",
"request_id": "req_01k3m9x7v2q8r4t6y0b1n5d7fa"
}
}{
"error": {
"type": "invalid_request_error",
"code": "parameter_invalid",
"message": "<string>",
"param": "<string>",
"doc_url": "<string>",
"request_id": "<string>"
}
}Search products
The primary product discovery endpoint. Combine category, price, momentum and commission filters to build a shortlist, then sort by whichever metric defines a winner for you.
curl --request POST \
--url https://api.unif.dev/v1/products/search \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"market": "US",
"period": {
"preset": "last_7d"
},
"currency": "USD",
"filters": {
"price": {
"gte": 15,
"lte": 45
},
"units_sold": {
"gte": 500
},
"revenue_growth_rate": {
"gte": 0.5
},
"commission_rate": {
"gte": 0.15
},
"launched_at": {
"gte": "2026-06-01"
}
},
"sort": [
{
"field": "revenue",
"direction": "desc"
}
],
"limit": 50
}
'import requests
url = "https://api.unif.dev/v1/products/search"
payload = {
"market": "US",
"period": { "preset": "last_7d" },
"currency": "USD",
"filters": {
"price": {
"gte": 15,
"lte": 45
},
"units_sold": { "gte": 500 },
"revenue_growth_rate": { "gte": 0.5 },
"commission_rate": { "gte": 0.15 },
"launched_at": { "gte": "2026-06-01" }
},
"sort": [
{
"field": "revenue",
"direction": "desc"
}
],
"limit": 50
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
market: 'US',
period: {preset: 'last_7d'},
currency: 'USD',
filters: {
price: {gte: 15, lte: 45},
units_sold: {gte: 500},
revenue_growth_rate: {gte: 0.5},
commission_rate: {gte: 0.15},
launched_at: {gte: '2026-06-01'}
},
sort: [{field: 'revenue', direction: 'desc'}],
limit: 50
})
};
fetch('https://api.unif.dev/v1/products/search', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.unif.dev/v1/products/search",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'market' => 'US',
'period' => [
'preset' => 'last_7d'
],
'currency' => 'USD',
'filters' => [
'price' => [
'gte' => 15,
'lte' => 45
],
'units_sold' => [
'gte' => 500
],
'revenue_growth_rate' => [
'gte' => 0.5
],
'commission_rate' => [
'gte' => 0.15
],
'launched_at' => [
'gte' => '2026-06-01'
]
],
'sort' => [
[
'field' => 'revenue',
'direction' => 'desc'
]
],
'limit' => 50
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.unif.dev/v1/products/search"
payload := strings.NewReader("{\n \"market\": \"US\",\n \"period\": {\n \"preset\": \"last_7d\"\n },\n \"currency\": \"USD\",\n \"filters\": {\n \"price\": {\n \"gte\": 15,\n \"lte\": 45\n },\n \"units_sold\": {\n \"gte\": 500\n },\n \"revenue_growth_rate\": {\n \"gte\": 0.5\n },\n \"commission_rate\": {\n \"gte\": 0.15\n },\n \"launched_at\": {\n \"gte\": \"2026-06-01\"\n }\n },\n \"sort\": [\n {\n \"field\": \"revenue\",\n \"direction\": \"desc\"\n }\n ],\n \"limit\": 50\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.unif.dev/v1/products/search")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"market\": \"US\",\n \"period\": {\n \"preset\": \"last_7d\"\n },\n \"currency\": \"USD\",\n \"filters\": {\n \"price\": {\n \"gte\": 15,\n \"lte\": 45\n },\n \"units_sold\": {\n \"gte\": 500\n },\n \"revenue_growth_rate\": {\n \"gte\": 0.5\n },\n \"commission_rate\": {\n \"gte\": 0.15\n },\n \"launched_at\": {\n \"gte\": \"2026-06-01\"\n }\n },\n \"sort\": [\n {\n \"field\": \"revenue\",\n \"direction\": \"desc\"\n }\n ],\n \"limit\": 50\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.unif.dev/v1/products/search")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"market\": \"US\",\n \"period\": {\n \"preset\": \"last_7d\"\n },\n \"currency\": \"USD\",\n \"filters\": {\n \"price\": {\n \"gte\": 15,\n \"lte\": 45\n },\n \"units_sold\": {\n \"gte\": 500\n },\n \"revenue_growth_rate\": {\n \"gte\": 0.5\n },\n \"commission_rate\": {\n \"gte\": 0.15\n },\n \"launched_at\": {\n \"gte\": \"2026-06-01\"\n }\n },\n \"sort\": [\n {\n \"field\": \"revenue\",\n \"direction\": \"desc\"\n }\n ],\n \"limit\": 50\n}"
response = http.request(request)
puts response.read_body{
"object": "list",
"has_more": true,
"data": [
{
"id": "prd_01k3m9x7v2q8r4t6y0b1n5d7fa",
"object": "product",
"channel": "tiktok_shop",
"market": "US",
"title": "Ceramide Barrier Repair Serum 30ml",
"url": "<string>",
"image_url": "<string>",
"brand": "<string>",
"shop": {
"id": "shp_01k3m9x7v2q8r4t6y0b1n5d7fa",
"name": "GlowLab",
"url": "<string>"
},
"category": {
"id": "cat_beauty_personal_care",
"name": "Beauty & Personal Care",
"path": [
"<string>"
]
},
"price": {
"current": 123,
"min": 123,
"max": 123
},
"commission_rate": 123,
"rating": 2.5,
"reviews_count": 123,
"listed_at": "2023-12-25",
"metrics": {
"revenue": 123,
"units_sold": 123,
"avg_sale_price": 123,
"revenue_growth_rate": 123,
"revenue_by_source": {
"video": 123,
"live": 123,
"mall": 123,
"search": 123
},
"creators_count": 123,
"videos_count": 123,
"livestreams_count": 123,
"gpm": 123
},
"period": {
"start": "2023-12-25",
"end": "2023-12-25",
"granularity": "day",
"adjusted": true
},
"currency": "<string>",
"meta": {
"refreshed_at": "2023-11-07T05:31:56Z",
"completeness": 0.5,
"missing_fields": [
"<string>"
]
}
}
],
"next_cursor": "<string>",
"total_count": 123,
"period": {
"start": "2023-12-25",
"end": "2023-12-25",
"granularity": "day",
"adjusted": true
},
"currency": "<string>"
}{
"error": {
"type": "invalid_request_error",
"code": "parameter_invalid",
"message": "filters.revenue.gte must be a non-negative number.",
"param": "filters.revenue.gte",
"doc_url": "https://unif.dev/docs/platform/errors#parameter_invalid",
"request_id": "req_01k3m9x7v2q8r4t6y0b1n5d7fa"
}
}{
"error": {
"type": "authentication_error",
"code": "invalid_api_key",
"message": "No API key was provided in the Authorization header.",
"doc_url": "https://unif.dev/docs/platform/errors#invalid_api_key",
"request_id": "req_01k3m9x7v2q8r4t6y0b1n5d7fa"
}
}{
"error": {
"type": "billing_error",
"code": "insufficient_credits",
"message": "This request costs 25 credits and 4 remain in the current period.",
"doc_url": "https://unif.dev/docs/platform/credits",
"request_id": "req_01k3m9x7v2q8r4t6y0b1n5d7fa"
}
}{
"error": {
"type": "invalid_request_error",
"code": "parameter_invalid",
"message": "<string>",
"param": "<string>",
"doc_url": "<string>",
"request_id": "<string>"
}
}Authorizations
Send your workspace API key as a bearer token:
Authorization: Bearer unif_sk_live_...
Keys are scoped to a single workspace. See Authentication.
Body
A sales channel — the commerce surface a request is about. Behind each channel is a cascade of data sources, tried in cost order until one verifies; the roster is published, but which source answered a given row is not carried in the response, so the cascade can be re-tuned without breaking your code.
tiktok_shop "tiktok_shop"
An ISO 3166-1 alpha-2 country code identifying a channel storefront.
US, GB, IE, ES, FR, DE, IT, ID, TH, VN, MY, PH, SG, JP, BR, MX "US"
The window a metric is measured over. Supply either preset, or both start and
end. Periods are whole days in the market's local time zone.
{ "preset": "last_30d" }
{
"start": "2026-08-01",
"end": "2026-08-31"
}
ISO 4217 code. Every monetary field in the response is converted to it.
3Hide child attributes
Hide child attributes
A metric or attribute name. Sortable fields are listed on each entity in the metrics reference.
asc, desc 1 <= x <= 100The next_cursor from a previous response.
Hide child attributes
Hide child attributes
Matches product title and brand.
Response
Matching products.
"list"Whether another page exists.
Hide child attributes
Hide child attributes
"prd_01k3m9x7v2q8r4t6y0b1n5d7fa"
"product"A sales channel — the commerce surface a request is about. Behind each channel is a cascade of data sources, tried in cost order until one verifies; the roster is published, but which source answered a given row is not carried in the response, so the cascade can be re-tuned without breaking your code.
tiktok_shop "tiktok_shop"
An ISO 3166-1 alpha-2 country code identifying a channel storefront.
US, GB, IE, ES, FR, DE, IT, ID, TH, VN, MY, PH, SG, JP, BR, MX "US"
"Ceramide Barrier Repair Serum 30ml"
Affiliate commission offered to creators, as a ratio.
0 <= x <= 5Hide child attributes
Hide child attributes
Revenue divided by units sold
Gross revenue per thousand views across attributed content.
The concrete window a request was measured over, after any preset was expanded and any channel-specific snapping was applied. Always read this rather than assuming you got back exactly the dates you asked for.
Hide child attributes
Hide child attributes
The smallest bucket the channel reports for this market.
day, week, month True when the requested dates were snapped to the nearest boundary the channel supports. See Periods and currency.
How current and how complete this record is.
Pass to cursor to fetch the next page. Null on the last page.
Approximate number of matches. Null when the result set is too large to count exactly.
The concrete window a request was measured over, after any preset was expanded and any channel-specific snapping was applied. Always read this rather than assuming you got back exactly the dates you asked for.
Hide child attributes
Hide child attributes
The smallest bucket the channel reports for this market.
day, week, month True when the requested dates were snapped to the nearest boundary the channel supports. See Periods and currency.
Was this page helpful?