Products
Two ways into products. Search the Product Library across every brand with GET /v1/products/search — one card per market, where a market is every store selling the same product — then list those stores with GET /v1/markets/{market_id}. Open any product in full by its id or its storefront URL. Or read one brand's own catalogue, its bestsellers and latest products, with GET /v1/brands/{brand_id}/products.
The product card
Product search and markets return product cards. Every key is always present; a value we do not know is null, never 0.
- Name
id- Type
- string
- Description
The product's identifier: the store's domain and its platform id, e.g.
merodacosmetics.de_9079935172920.
- Name
title- Type
- string
- Description
The product title, as the store writes it.
- Name
url- Type
- string
- Description
The product page on the store.
- Name
image_url- Type
- string
- Description
The product image on our media host. Signed and expiring, like every media URL the API returns; to get the bytes, use Download a product's image.
- Name
brand_id- Type
- string
- Description
The store's domain. Works with every brand endpoint.
- Name
vendor- Type
- string
- Description
The vendor the store names on the product.
- Name
niche- Type
- string
- Description
The brand's niche, from the canonical taxonomy.
- Name
product_type- Type
- string
- Description
The store's own product type, free text.
- Name
platform- Type
- string
- Description
The store's ecommerce platform, lowercase:
shopify,woocommerce,magentoorprestashop.
- Name
tiktok_shop_matched- Type
- boolean
- Description
Whether the product is matched to a TikTok Shop listing.
- Name
dashboard_url- Type
- string
- Description
The product's page in the Brandsearch app.
- Name
price.amount- Type
- number
- Description
The price in the store's own currency.
0is a free product.
- Name
price.currency- Type
- string
- Description
ISO-4217 currency of
price.amount.
- Name
price_usd- Type
- number
- Description
The price converted to USD.
- Name
variants_count- Type
- integer
- Description
Number of variants.
- Name
in_stock- Type
- boolean
- Description
Whether the product was in stock at the last check;
nullif never checked.
- Name
created_at- Type
- timestamp
- Description
When the product entered the store's catalogue, in the store's own timezone.
- Name
ads.total- Type
- integer
- Description
The store's ads for this product, Meta and TikTok (
ads.meta+ads.tiktok).
- Name
ads.active- Type
- integer
- Description
Its active Meta ads. TikTok ads carry no active status.
- Name
ads.last_30d- Type
- integer
- Description
Ads started in the last 30 days.
- Name
ads.first_advertised_at- Type
- timestamp
- Description
When we first saw the product advertised.
ads.last_advertised_at: the latest.
- Name
ads.est_daily_spend_usd- Type
- number
- Description
The store's estimated daily ad spend in USD, rounded to 2 significant figures. Estimated from the reach that EU and UK ads disclose, so it is
nullfor a product advertised only elsewhere.
- Name
market.id- Type
- string
- Description
The product's market: pass it to
GET /v1/markets/{market_id}.
- Name
market.sellers- Type
- integer
- Description
Stores selling the product: exact copies, variants, and this store.
- Name
market.active_ads- Type
- integer
- Description
Active ads across the market.
market.total_ads: all its ads.
- Name
market.price_min_usd- Type
- number
- Description
The lowest price across the market, in USD.
market.price_median_usdandmarket.price_max_usdfollow.
- Name
market.new_sellers_30d- Type
- integer
- Description
Stores that started selling it in the last 30 days.
- Name
market.country_count- Type
- integer
- Description
EU and UK countries the market's stores advertise in.
- Name
market.est_daily_spend_usd- Type
- number
- Description
The market's estimated daily ad spend in USD, rounded to 2 significant figures. Same EU/UK source as the store's.
- Name
revenue.est_monthly_usd_low- Type
- number
- Description
The bottom of the estimated monthly revenue band, in USD, rounded to 2 significant figures.
revenue.est_monthly_usd_highis the top.revenueisnullwhen there is no estimate.
- Name
revenue.scope- Type
- string
- Description
Whose revenue the band is:
sellerfor this store,marketfor every store selling the product,nullwhen we cannot tell. Do not add upmarketfigures across the stores of one market.
Search products
Search the Product Library: products across every brand, with at least one ad (or matched to TikTok Shop, with source=tiktok_shop), one card per market. All the stores selling the same product show once, as the market's original listing; GET /v1/markets/{market_id} lists them all.
Sorted by estimated monthly revenue unless you choose otherwise — and by relevance when you pass q without a sort. Costs 1 credit per product returned; an empty page costs none.
Filters
All optional, combine freely. niche, product_type, source and market_country take a comma-separated list.
- Name
q- Type
- string
- Description
Text search over the title (weighted most), product type, niche, tags, vendor and description, tolerating typos. Any one matching word admits a product. Max 200 characters.
- Name
niche- Type
- string
- Description
The brand's niche, from
/v1/facets/product-niches. Case and common synonyms are resolved (supplements→Health & Supplements).
- Name
product_type- Type
- string
- Description
The store's own product type, matched whole and ignoring case. Free text:
/v1/facets/product-typeslists the common ones. At most 20 values.
- Name
source- Type
- string
- Description
shopify(the default): products with at least one ad, whatever the store's platform.tiktok_shop: products matched to TikTok Shop, with or without ads.shopify,tiktok_shopfor either.
- Name
price_min_usd- Type
- number
- Description
Minimum price in USD.
price_max_usd: maximum. Both inclusive.
- Name
in_stock- Type
- boolean
- Description
true: in stock at the last check.false: sold out. A product never checked matches neither.
- Name
ads_min- Type
- integer
- Description
Minimum ads (Meta + TikTok).
active_ads_min: minimum active Meta ads.ads_30d_min: minimum ads started in the last 30 days.
- Name
first_advertised_from- Type
- string
- Description
First seen advertised on or after this date;
first_advertised_to, on or before.last_advertised_from: seen advertised on or after this date, i.e. still advertising.YYYY-MM-DD(a UTC day), ISO 8601 or unix seconds; inclusive.
- Name
est_revenue_min_usd- Type
- number
- Description
Minimum estimated monthly revenue in USD, compared with the top of the band. A product with no estimate is excluded.
- Name
sellers_min- Type
- integer
- Description
Minimum stores selling the product (
market.sellers).sellers_max: maximum.
- Name
market_country- Type
- string
- Description
ISO-2 country where the product's market advertises. EU and UK only — the data comes from their ad transparency rules — so any other country is a
400.UKis read asGB.
Sort & pagination
- Name
sort- Type
- string
- Description
est_revenue(the default),relevance(needsq, and the default whenqis given),newest(when the product entered the store's catalogue),ads,active_ads,ads_30d,daily_spend(the store's own),sellers,price_asc,price_desc,tiktok_shop_plays. A price that failed conversion ranks last either way. An unknown value is a400whosedetails.allowedlists these.
- Name
page- Type
- integer
- Description
Page number, 1-500. Default
1. A search can be paged through its first 500 results: whenpage×page_sizegoes past them, the request is a400 invalid_parameterwhosedetails.max_pagenames the last page you can reach.
- Name
page_size- Type
- integer
- Description
Results per page, 1-25. Default
10.
Good to know
- The data behind spend, audience and market countries comes from EU and UK ad transparency, so a
nullthere means unknown, not zero. - The same product sold on a store's several locale storefronts (
.com,.de, …) can appear once per storefront, and a card's image can be missing on our host.
Request
curl -G https://api.brandsearch.co/v1/products/search \
-H "X-API-Key: bsk_your_api_key" \
-d q="linen dress" \
-d price_max_usd=80 \
-d market_country=DE \
-d page_size=5
Response
{
"data": [
{
"id": "ecovest.store_9596758819144",
"title": "Plaid Linen Dress",
"url": "https://ecovest.store/products/plaid-linen-dress",
"image_url": "https://media2.brandsearch.co/resize/…/products/…webp",
"brand_id": "ecovest.store",
"vendor": "Ecovest",
"niche": "Fashion",
"product_type": null,
"platform": "shopify",
"tiktok_shop_matched": false,
"dashboard_url": "https://app.brandsearch.co/product/ecovest.store_9596758819144",
"price": {
"amount": 52.0,
"currency": "EUR"
},
"price_usd": 56.16,
"variants_count": 1,
"in_stock": true,
"created_at": "2025-07-25T16:38:11+03:00",
"ads": {
"total": 1,
"active": 0,
"meta": 1,
"tiktok": 0,
"last_30d": 1,
"first_advertised_at": "2026-07-18T14:21:45.923Z",
"last_advertised_at": "2026-07-18T14:21:45.923Z",
"est_daily_spend_usd": null
},
"market": {
"id": "ecovest.store_9596758819144",
"sellers": 1,
"active_ads": 0,
"total_ads": 1,
"price_min_usd": 56.16,
"price_median_usd": 56.16,
"price_max_usd": 56.16,
"new_sellers_30d": 1,
"country_count": 20,
"est_daily_spend_usd": null
},
"revenue": null
}
],
"pagination": {
"page": 1,
"page_size": 5,
"total": 29971,
"total_pages": 5995
}
}
Get a product
One product in full: the product card plus its audience, the stores selling it, its TikTok Shop footprint and whether a supplier match exists. product_id is the id of any product card, from search, a market, or a batch of URLs.
Costs 1 credit. An unknown id is a 404 product_not_found and costs none.
Detail fields
- Name
audience.top_country- Type
- string
- Description
The ISO-2 country the market's ads reach most, with
audience.top_country_share_pctits share of the reach. From EU and UK ads.
- Name
audience.female_share_pct- Type
- number
- Description
The share of that reach that is women;
audience.top_ageis the age band reached most,audience.country_countthe countries reached. The full breakdown is Get a product's audience.
- Name
top_sellers- Type
- array
- Description
Up to 5 stores selling the product, this one included, the most active ads first:
brand_id,price_usd,total_ads,active_ads.price_usdis given on this product's own row only, and isnullon the other stores' rows; List a product's sellers lists every store with its price in USD.
- Name
tiktok_shop- Type
- object
- Description
videos,creators,total_playsandorganic_share_pctof the product's TikTok Shop listings.nullunless the product is matched to TikTok Shop.
- Name
sourcing_available- Type
- boolean
- Description
Whether a supplier match exists for the product. When it does, Get a product's sourcing returns the supplier and the unit economics.
Request
curl https://api.brandsearch.co/v1/products/merodacosmetics.de_9079935172920 \
-H "X-API-Key: bsk_your_api_key"
Response (detail fields)
{
"id": "merodacosmetics.de_9079935172920",
"title": "Changing Foundation",
"…": "every field of the product card",
"audience": {
"top_country": "DE",
"top_country_share_pct": 47.9,
"top_age": "35-44",
"female_share_pct": 97.3,
"country_count": 34
},
"top_sellers": [
{ "brand_id": "merodacosmetics.de", "price_usd": 37.75, "total_ads": 8419, "active_ads": 3848 },
{ "…": "up to 4 more stores, each with price_usd null" }
],
"tiktok_shop": null,
"sourcing_available": true
}
Get a product by URL
The product behind a storefront product page, in the same shape as Get a product. Pass the URL as you copied it: a query string, a fragment, www., http://, a missing scheme, a collection or locale prefix (/collections/summer/products/…, /en-gb/products/…) and a trailing slash are all resolved. That includes the landing page of a Meta ad, its creative.link_url or one of its card_link_urls, so you can go from an ad to the product it promotes.
Costs 1 credit. A page we do not know is a 404 product_not_found and costs none; a value that is not an http(s) URL is a 400 invalid_parameter.
Required attributes
- Name
url- Type
- string
- Description
The product page URL, up to 2,048 characters.
Request
curl -G https://api.brandsearch.co/v1/products/by-url \
-H "X-API-Key: bsk_your_api_key" \
--data-urlencode "url=https://www.merodacosmetics.de/collections/all/products/changing-foundation?variant=1"
Get products by URLs
Up to 20 storefront URLs in one call. Returns found, the product cards in the order of their URLs, and not_found, the URLs, as you gave them, that matched no product. A product reached by two of the URLs comes back once; a string that is not a URL is simply not found.
Costs 1 credit per product found.
Request body
- Name
urls- Type
- array
- Description
1 to 20 product page URLs. Any other key in the body is a
400 validation_error.
Request
curl https://api.brandsearch.co/v1/products/by-urls \
-H "X-API-Key: bsk_your_api_key" \
-H "Content-Type: application/json" \
-d '{"urls": ["https://merodacosmetics.de/products/changing-foundation", "https://no-such-store.example/products/x"]}'
Response
{
"found": [
{ "id": "merodacosmetics.de_9079935172920", "title": "Changing Foundation", "…": "the product card" }
],
"not_found": ["https://no-such-store.example/products/x"]
}
Download a product's image
The product's image as bytes, rather than the signed, expiring link in image_url. Use it when your client cannot fetch our media host, as with AI agents (the MCP download_product_image tool calls it).
Costs 1 credit, charged once the image is found. An unknown product (404 product_not_found) or a product whose image we do not hold (404 media_unavailable, about 1 in 100) costs none.
The file is named after the product's handle, and X-Product-Title carries the product's title, percent-encoded.
Optional attributes
- Name
size- Type
- string
- Description
full(the default): the image as stored.compact: at most 800 px wide, about half the bytes, for inlining. When a compact copy cannot be made, the full image is served and theX-Product-Image-Sizeheader saysfull. Both are WebP.
Request
curl -G https://api.brandsearch.co/v1/products/merodacosmetics.de_9079935172920/image \
-H "X-API-Key: bsk_your_api_key" \
-d size=compact \
-o changing-foundation.webp
List a product's ads
The Meta ads promoting a product, on its own store and on the stores we matched as selling the same item, newest first: the ads the product's Ads tab shows in the dashboard. This can be fewer stores than the product's whole market. An ad is listed once even when we hold several copies of it, with the latest status we have. pagination.total counts distinct ads, but pagination.total_pages stops at 5, the last page served.
Each row has platform (meta), id, brand_id (the advertiser), status, start_date and dashboard_url, named and valued as on every other Meta ad: status is active, inactive or deleted (older ads are capitalised, such as Active), and status or start_date is null when we hold none. Each id is the Meta Ads Library id: open it with GET /v1/meta-ads/{ad_id} or download its creative. TikTok ads are not linked to products yet.
Costs 1 credit per ad returned. An unknown product is a 404 product_not_found and costs none.
Optional attributes
- Name
page- Type
- integer
- Description
Page number, 1 to 5. Default
1.
- Name
page_size- Type
- integer
- Description
Ads per page, 1 to 10. Default
10.
Request
curl -G https://api.brandsearch.co/v1/products/merodacosmetics.de_9079935172920/ads \
-H "X-API-Key: bsk_your_api_key" \
-d page_size=1
Response
{
"data": [
{
"platform": "meta",
"id": "1012849221805409",
"brand_id": "merodacosmetics.com",
"status": "active",
"start_date": "2026-10-01T07:00:00",
"dashboard_url": "https://app.brandsearch.co/ads/meta/1012849221805409"
}
],
"pagination": { "page": 1, "page_size": 1, "total": 3711, "total_pages": 5 }
}
Find look-alike products
Products that look like this one, by image similarity, closest first, as product cards: near-identical products and adjacent winners from other markets. The product's own market, every store already selling it, is left out, and each other market shows once. No score is returned: the order is the ranking.
Costs 1 credit per product returned. A product we hold no image signature for (about 5% of the library) returns {"data": []} and costs nothing; an unknown product is a 404 product_not_found.
Optional attributes
- Name
limit- Type
- integer
- Description
How many look-alikes, 1 to 20. Default
20. Products below the similarity floor are left out, so the list can be shorter.
Request
curl -G https://api.brandsearch.co/v1/products/merodacosmetics.de_9079935172920/similar \
-H "X-API-Key: bsk_your_api_key" \
-d limit=5
Response
{
"data": [
{ "id": "merodacosmetics.com_8267285463320", "title": "Changing Foundation", "…": "the product card" },
{ "id": "officialscarpony.store_16348727574915", "title": "Anti-Redness Colour Changing Foundation SPF 15", "…": "the product card" }
]
}
List a product's sellers
Every store selling a product, most active ads first, then most ads, at most 50. The product's own store is always on the list, flagged is_this_product. Each store is read from its own listing: price_usd is its price converted to USD, and id opens it with Get a product. A store whose listing we cannot read comes from our market data instead, with id: null and price_usd: null, since we hold no USD price for it.
This also covers stores our market data names but has not yet linked to the market, which GET /v1/markets/{market_id} does not list.
Costs 1 credit per store returned. An unknown product is a 404 product_not_found and costs none.
Optional attributes
- Name
page- Type
- integer
- Description
Page number, 1 to 50. Default
1. Every page that starts within the 50 stores is served, the last one possibly short (atpage_size=7, page 8 holds store 50 alone). A later page is a400 invalid_parameterwhosedetails.max_pagenames the last one.
- Name
page_size- Type
- integer
- Description
Stores per page, 1 to 25. Default
25.
Request
curl -G https://api.brandsearch.co/v1/products/merodacosmetics.de_9079935172920/sellers \
-H "X-API-Key: bsk_your_api_key" \
-d page_size=2
Response
{
"data": [
{
"id": "merodacosmetics.de_9079935172920",
"brand_id": "merodacosmetics.de",
"url": "https://merodacosmetics.de/products/changing-foundation",
"price_usd": 37.75,
"total_ads": 8419,
"active_ads": 3848,
"first_advertised_at": "2025-12-22T22:00:14.852Z",
"is_this_product": true
},
{
"id": "merodacosmetics.co.uk_8409205244160",
"brand_id": "merodacosmetics.co.uk",
"url": "https://merodacosmetics.co.uk/products/changing-foundation",
"price_usd": 36.77,
"total_ads": 7793,
"active_ads": 3780,
"first_advertised_at": "2025-12-22T22:00:20.525Z",
"is_this_product": false
}
],
"pagination": { "page": 1, "page_size": 2, "total": 15, "total_pages": 8 }
}
Get a product's ad trend
Whether a product's advertising is growing or slowing: one point per day over the last days days, oldest first, ending today (UTC). Each point holds the store's own ads, named as on the product card, and its market's. It shows ads, not sales: there is no revenue curve.
A figure is recorded on the days it changes and holds until the next change, so a quiet week shows the same figures every day. A figure not recorded yet is null, and so is a spend we cannot estimate (the store's ads disclosed no EU or UK reach). Today's point is the product's current figures, the ones its product card shows, and is null wherever the card has none.
Costs 1 credit, whatever days. A product with no recorded activity returns { "data": [] } and costs none; an unknown product is a 404 product_not_found and costs none.
Optional attributes
- Name
days- Type
- integer
- Description
How many days, ending today, 1 to 90. Default
30.
Point fields
- Name
date- Type
- string
- Description
The day,
YYYY-MM-DD(UTC).
- Name
ads- Type
- object
- Description
The store's own ads that day:
total(Meta and TikTok),active(live Meta ads),last_30d(ads started in the 30 days before) andest_daily_spend_usd, an estimate rounded to 2 significant figures.
- Name
market- Type
- object
- Description
advertisers, the stores running ads for the product, this one included, andest_daily_spend_usd, the market's estimated daily spend.
Request
curl -G https://api.brandsearch.co/v1/products/merodacosmetics.de_9079935172920/trend \
-H "X-API-Key: bsk_your_api_key" \
-d days=3
Response
{
"data": [
{
"date": "2026-10-03",
"ads": { "total": 8629, "active": 4054, "last_30d": 2708, "est_daily_spend_usd": 75000 },
"market": { "advertisers": 3, "est_daily_spend_usd": 75000 }
},
{
"date": "2026-10-04",
"ads": { "total": 8776, "active": 4191, "last_30d": 2774, "est_daily_spend_usd": 73000 },
"market": { "advertisers": 3, "est_daily_spend_usd": 73000 }
},
{
"date": "2026-10-05",
"ads": { "total": 8798, "active": 4203, "last_30d": 2773, "est_daily_spend_usd": 71000 },
"market": { "advertisers": 3, "est_daily_spend_usd": 71000 }
}
]
}
Get a product's audience
Who a product's market reaches with its Meta ads, over every ad the market ran, as shares of that reach: by gender, by age band and gender, and by country. The figures come from the reach Meta discloses for ads shown in the EU and the UK, so other countries never appear. It extends the audience block of Get a product, whose fields it repeats with the same values.
Shares only: no reach or spend totals.
Costs 1 credit. A product whose market disclosed no reach (about half the library: markets advertising outside the EU and the UK, or on TikTok only) is a 404 audience_unavailable and costs none, as is an unknown product (404 product_not_found).
Fields
- Name
analysed_ads- Type
- integer
- Description
The market's Meta ads the shares rest on.
- Name
female_share_pct- Type
- number
- Description
The share of the reach whose gender is known that is women;
male_share_pct, men.
- Name
top_age- Type
- string
- Description
The age band reached most.
- Name
age_gender- Type
- array
- Description
One row per age band, youngest first:
age,female_pct,male_pct. Every cell is a share of the reach whose gender is known, so the cells add up to 100 and thefemale_pctcolumn tofemale_share_pct. The reach Meta could not age comes last, asunknown.
- Name
countries- Type
- array
- Description
The 15 countries reached most, largest first:
code(ISO-2) andreach_share_pct, its share of the whole reach. The first istop_country, withtop_country_share_pct;country_countcounts every country reached.
Request
curl https://api.brandsearch.co/v1/products/merodacosmetics.de_9079935172920/audience \
-H "X-API-Key: bsk_your_api_key"
Response
{
"analysed_ads": 29541,
"country_count": 34,
"top_country": "DE",
"top_country_share_pct": 48.2,
"top_age": "35-44",
"female_share_pct": 97.4,
"male_share_pct": 2.7,
"age_gender": [
{ "age": "18-24", "female_pct": 6.5, "male_pct": 0.2 },
{ "age": "25-34", "female_pct": 26.8, "male_pct": 0.6 },
{ "age": "35-44", "female_pct": 30.5, "male_pct": 0.7 },
{ "age": "45-54", "female_pct": 18.8, "male_pct": 0.5 },
{ "age": "55-64", "female_pct": 11.3, "male_pct": 0.4 },
{ "age": "65+", "female_pct": 3.5, "male_pct": 0.2 },
{ "age": "unknown", "female_pct": 0.0, "male_pct": 0.0 }
],
"countries": [
{ "code": "DE", "reach_share_pct": 48.2 },
{ "code": "GB", "reach_share_pct": 17.2 },
{ "code": "NL", "reach_share_pct": 15.0 },
{ "code": "AT", "reach_share_pct": 10.1 },
{ "code": "BE", "reach_share_pct": 7.2 },
{ "code": "SE", "reach_share_pct": 0.7 },
{ "code": "DK", "reach_share_pct": 0.5 },
{ "code": "IE", "reach_share_pct": 0.3 },
{ "code": "PT", "reach_share_pct": 0.1 },
{ "code": "HR", "reach_share_pct": 0.1 },
{ "code": "IT", "reach_share_pct": 0.1 },
{ "code": "MT", "reach_share_pct": 0.1 },
{ "code": "CY", "reach_share_pct": 0.1 },
{ "code": "FI", "reach_share_pct": 0.1 },
{ "code": "GR", "reach_share_pct": 0.1 }
]
}
Get a product's creative insights
How a product's market advertises it: which hooks, angles, tones and audiences its Meta ads use, read from our AI analysis of those ads. The same view as the Creative DNA tab of a product in the dashboard. The ads are the ones List a product's ads returns.
Only some ads are analysed, often a few dozen per market, so analysed_ads says how many ads the shares describe. Each ad counts once, by its most recent analysis, so the shares of one facet add up to at most 100. The dashboard counts every stored copy of an ad, so its percentages can differ slightly.
Costs 1 credit. A product none of whose market's ads has been analysed (about 4 products in 5), or whose analysed ads have no value to show, is a 404 insights_unavailable and costs none, as is an unknown product (404 product_not_found). A successful response always has at least one facet.
Fields
- Name
analysed_ads- Type
- integer
- Description
The market's analysed Meta ads the shares rest on.
- Name
facets- Type
- array
- Description
Up to nine facets, in this order:
hook,angle,funnel_stage,awareness_level,pain,audience,motivation,tone,strategy_pattern. Each is{ name, label, buckets }. A facet none of the analysed ads has a value for is left out; there is always at least one.
- Name
facets[].buckets- Type
- array
- Description
The facet's five most common values, most common first:
label, the value as the dashboard shows it, andpct, the share of the analysed ads using it. Variants of one value (a different capitalisation, or a known synonym such as "General Consumers" for "General Audience") are counted together.
Request
curl https://api.brandsearch.co/v1/products/merodacosmetics.de_9079935172920/creative-insights \
-H "X-API-Key: bsk_your_api_key"
Response
{
"analysed_ads": 50,
"facets": [
{
"name": "hook",
"label": "Hook",
"buckets": [
{ "label": "Demonstration", "pct": 50.0 },
{ "label": "Aspiration", "pct": 12.0 },
{ "label": "Fear", "pct": 12.0 },
{ "label": "Social Proof", "pct": 8.0 },
{ "label": "Testimonial", "pct": 8.0 }
]
},
{
"name": "angle",
"label": "Angle",
"buckets": [
{ "label": "Convenience", "pct": 46.0 },
{ "label": "Transformation", "pct": 20.0 },
{ "label": "New Mechanism", "pct": 10.0 },
{ "label": "Social Proof", "pct": 10.0 },
{ "label": "Comparison", "pct": 8.0 }
]
},
{
"name": "funnel_stage",
"label": "Funnel stage",
"buckets": [
{ "label": "BOF", "pct": 78.0 },
{ "label": "MOF", "pct": 16.0 },
{ "label": "TOF", "pct": 6.0 }
]
},
{
"name": "tone",
"label": "Tone",
"buckets": [
{ "label": "Conversational", "pct": 58.0 },
{ "label": "Direct Response", "pct": 26.0 },
{ "label": "Urgent", "pct": 8.0 },
{ "label": "Emotional", "pct": 6.0 },
{ "label": "Inspirational", "pct": 2.0 }
]
},
"..."
]
}
Get a product's sourcing
The supplier we matched to a product on 1688 and what it means for selling it: the supplier's cost, an estimated shipping cost, the margin, the break-even return on ad spend and cost per acquisition, and a verdict. Only products whose detail shows sourcing_available: true have a match.
The match is made automatically and can be wrong: supplier.url is the listing to check it against before relying on the figures.
Costs 1 credit. A product with no match is a 404 sourcing_unavailable and costs none, as is an unknown product (404 product_not_found).
Fields
- Name
supplier- Type
- object
- Description
platform(1688),url(the listing),titleandshop, as the supplier lists them.
- Name
unit_cost_usd- Type
- number
- Description
The supplier's price for one unit, in USD.
bulk_unit_cost_usdis the price frombulk_qtyunits, bothnullwhen the listing has no bulk price;moqis the minimum order.
- Name
est_shipping_usd- Type
- number
- Description
An estimate of shipping one unit to the buyer: the supplier's delivery inside China plus international freight, rounded to 2 significant figures.
nullwhen we cannot reconcile it with the rest of the figures, for instance when the price or the supplier's cost changed since we last worked them out.
- Name
gross_margin_pct- Type
- number
- Description
The share of the selling price (
retail_price_usd) left after the supplier's cost, before shipping and payment fees (the break-even figures below take those into account); negative when the cost is higher.markupis the price divided by the cost. Bothnullwhen we found no cost.
- Name
break_even_roas- Type
- number
- Description
The return on ad spend at which a sale breaks even, after the supplier's cost, shipping and payment fees;
break_even_cpa_usdis the most a sale can cost in ads before it loses money, in USD to the cent.
- Name
verdict- Type
- string
- Description
profitable,possible,difficultorunprofitable;nullwhen the product was not assessed.
- Name
alternatives- Type
- array
- Description
Up to 3 other listings that looked like a match:
url,title,unit_cost_usd(the listing's advertised price, converted to USD;nullon some older matches) andmoq(nullwhen the listing states none).
Request
curl https://api.brandsearch.co/v1/products/lymphoria.co_7922886246539/sourcing \
-H "X-API-Key: bsk_your_api_key"
Response
{
"matched": true,
"supplier": {
"platform": "1688",
"url": "https://detail.1688.com/offer/1042656609037.html",
"title": "Hot Selling Free Shipping 60ml Lymphatic Drainage Drops Independent Site Amazon Cross-Border Bestseller",
"shop": "青禾悦动(广州)健康科技有限公司"
},
"unit_cost_usd": 1.12,
"bulk_unit_cost_usd": 0.84,
"bulk_qty": 3000,
"moq": 1,
"est_shipping_usd": 4,
"retail_price_usd": 36.95,
"gross_margin_pct": 96.9,
"markup": 32.1,
"break_even_roas": 1.21,
"break_even_cpa_usd": 30.46,
"verdict": "profitable",
"alternatives": [
{
"url": "https://detail.1688.com/offer/1014544055620.html",
"title": "Cross-Border Amazon Bestseller Lymphoria Dietary Supplement Lymphatic Drops, Easy-To-Absorb Nutrition, 60ml",
"unit_cost_usd": null,
"moq": null
},
"..."
],
"dashboard_url": "https://app.brandsearch.co/product/lymphoria.co_7922886246539"
}
List every store selling a product
A market is every store selling the same product — exact copies and variants. Pass a product card's market.id. Each store comes back as a product card, the ones with the most active Meta ads first, 50 per page.
The list can hold fewer stores than the card's market.sellers, which also counts stores whose listing is not linked to the market yet.
Costs 1 credit per store returned. Returns 404 market_not_found when no product belongs to the market.
Optional attributes
- Name
sort- Type
- string
- Description
active_ads(the default: live Meta ads) orspend(estimated daily spend).
- Name
cursor- Type
- string
- Description
The previous page's
pagination.cursor; omit it for the first page. The last page hashas_more: falseandcursor: null.
- Name
history- Type
- boolean
- Description
Add the market's daily series,
{ date, advertisers, est_daily_spend_usd }per day, oldest first. First page only;[]when none is recorded.
Request
curl https://api.brandsearch.co/v1/markets/karlson-berlin.com_10261128249691 \
-H "X-API-Key: bsk_your_api_key"
Response
{
"market_id": "karlson-berlin.com_10261128249691",
"members": [
{
"id": "williams-london-clothing.com_15532790350168",
"title": "Leona | Luxury Quilted Jacket",
"url": "https://williams-london-clothing.com/products/leona",
"image_url": "https://media2.brandsearch.co/resize/…/products/…webp",
"brand_id": "williams-london-clothing.com",
"vendor": "Williams London",
"niche": "Fashion",
"product_type": null,
"platform": null,
"tiktok_shop_matched": false,
"dashboard_url": "https://app.brandsearch.co/product/williams-london-clothing.com_15532790350168",
"price": {
"amount": 99.95,
"currency": "USD"
},
"price_usd": 99.95,
"variants_count": 24,
"in_stock": null,
"created_at": "2025-12-23T06:58:22+00:00",
"ads": {
"total": 23,
"active": 20,
"meta": 23,
"tiktok": 0,
"last_30d": 1,
"first_advertised_at": "2025-12-29T20:50:34.672Z",
"last_advertised_at": "2026-09-20T19:17:46.923Z",
"est_daily_spend_usd": 64
},
"market": {
"id": "karlson-berlin.com_10261128249691",
"sellers": 556,
"active_ads": 73,
"total_ads": 1717,
"price_min_usd": 34.9,
"price_median_usd": 69.95,
"price_max_usd": 27999.0,
"new_sellers_30d": 178,
"country_count": 24,
"est_daily_spend_usd": 140
},
"revenue": {
"est_monthly_usd_low": 15000,
"est_monthly_usd_high": 20000,
"scope": "market"
}
}
],
"pagination": {
"total": 387,
"size": 50,
"has_more": true,
"cursor": "WzAsMC4wLCJodHRwczovL2JpYW5jaGktcm9tYS5pdC9wcm9kdWN0cy9jYXBwb3R0by1zZXJlbmEiXQ"
}
}
The catalogue product
Default fields
id, title, price, url, vendor, product_type, rank, created_at
All available fields
- Name
id- Type
- string
- Description
Unique product identifier.
- Name
title- Type
- string
- Description
The product title.
- Name
price- Type
- number
- Description
The product price in USD.
- Name
url- Type
- string
- Description
Direct URL to the product page.
- Name
vendor- Type
- string
- Description
The product vendor or brand name.
- Name
product_type- Type
- string
- Description
The product category or type.
- Name
type- Type
- string
- Description
Alternate type classification.
- Name
rank- Type
- integer
- Description
The product's position in the brand's catalogue, best first. It is the real rank, so it may start above
1and skip values. Products that were never ranked simply have norank— the key is omitted, as with any other missing field.
- Name
handle- Type
- string
- Description
URL-friendly product handle/slug.
- Name
status- Type
- string
- Description
The product status (e.g.,
"active").
- Name
description- Type
- string
- Description
The product description.
- Name
tags- Type
- array
- Description
Array of product tags.
- Name
variants_count- Type
- integer
- Description
Number of product variants.
- Name
created_at- Type
- timestamp
- Description
When the product was first added.
- Name
updated_at- Type
- timestamp
- Description
When the product was last updated.
- Name
published_at- Type
- timestamp
- Description
When the product was published on the store.
Get products for a brand
This endpoint returns products for a given brand. Products are split into bestsellers and latest collections. Use product_type to request one or both.
The two collections are genuinely different: bestsellers holds only ranked products, best first, while latest is the brand's newest products, most recent first. Up to 10 products are returned per collection.
Unlike the brands and ads endpoints, this endpoint does not paginate — it returns all products in each requested collection.
The response also carries dashboard_url, a link to the brand's page in the Brandsearch app. It sits next to brand_id rather than on each product, since products have no page of their own.
Returns 404 brand_not_found if the brand does not exist. A brand we know but have no products for returns empty arrays.
Optional attributes
- Name
product_type- Type
- string
- Description
Which collection to return. One of:
all(default),bestsellers,latest.
- Name
fields- Type
- string
- Description
Comma-separated list of product fields to include.
- Name
created_from- Type
- string
- Description
Keep products added to the store on or after this date. Accepts
YYYY-MM-DD, ISO 8601, or unix seconds. This is each product's own creation date, not the brand's:created_fromonGET /v1/brandsasks when the store itself was added. Applies to bothbestsellersandlatest. An unparseable date returns400 invalid_parameter.
- Name
created_to- Type
- string
- Description
Keep products added to the store on or before this date. Same formats as
created_from. Both bounds are inclusive, and a date-only value covers that whole day.
Filtering by creation date
Use created_from / created_to to answer "what has this brand launched since June?" without pulling its whole catalogue.
- A date-only bound means that day in the store's own timezone, not UTC. A product created at 11pm in New York on 13 August counts as 13 August, so
created_to=2026-08-13keeps it. Pass a full timestamp (for example2026-08-13T23:00:00Z) when you need an exact moment. - Some products have no creation date on record (roughly a quarter of the catalogue). A date filter leaves those out, so a dated request can return fewer products, bestsellers included, than the same request without dates.
Request
curl -G https://api.brandsearch.co/v1/brands/gymshark.com/products \
-H "X-API-Key: bsk_your_api_key" \
-d product_type=bestsellers \
-d created_from=2024-06-01
Response
{
"brand_id": "gymshark.com",
"dashboard_url": "https://app.brandsearch.co/brand-analysis/gymshark.com",
"bestsellers": [
{
"id": "8234567890",
"title": "Gymshark Track Pants Tall - Strength Pink",
"price": 130.00,
"url": "https://gymshark.com/products/gymshark-track-pants-tall-pants-pink-aw26",
"vendor": "Gymshark",
"product_type": "Pants",
"rank": 1,
"created_at": "2024-06-01T00:00:00Z"
},
{
"id": "8234567891",
"title": "Gymshark Track Pants Short - Butter Yellow",
"price": 35.00,
"url": "https://gymshark.com/products/gymshark-track-pants-short-pants-yellow-aw26",
"vendor": "Gymshark",
"product_type": "Pants",
"rank": 2,
"created_at": "2024-08-15T00:00:00Z"
}
]
}
Errors
400 invalid_parameterfor a value we refuse, withdetails.parameternaming it. Nothing is charged.- Search: an unknown
sort(details.allowedlists the sorts),relevancewithoutq, an unknownsourceorniche, amarket_countryoutside the EU and UK, more than 20 values in one list, a malformed date, a minimum above its maximum, or a page past the first 500 results (details.max_page). - Markets: an unknown
sort, or acursorthat is not one we returned. - Sellers: a page past the 50 stores (
details.max_page). - A
urlthat is not an http(s) URL, and an imagesizeother thanfullorcompact.
- Search: an unknown
400 validation_errorfor a value of the wrong type or out of range, withdetails.errors[].fieldnaming it: a searchpageover 500 orpage_sizeover 25, a product's ads past page 5 or withpage_sizeover 10, a look-alikelimitoutside 1-20, a sellerspageover 50 orpage_sizeover 25, a trend'sdaysoutside 1-90, a negative minimum, a missingurl, a product id over 256 characters, or aby-urlsbody that is empty, holds more than 20 URLs or carries an unknown key.404 product_not_foundfor an unknown product id or URL (same message whatever the reason; no credit charged),404 media_unavailablewhen a product's image is not on our media host (no credit charged),404 audience_unavailablewhen a product's market disclosed no reach (no credit charged),404 insights_unavailablewhen none of a product's market's ads has been analysed, or none has a value to show (no credit charged),404 sourcing_unavailablewhen a product has no supplier match (no credit charged),404 brand_not_foundfor an unknown brand,404 market_not_foundfor a market no product belongs to.503 service_unavailablewhen the product catalogue is unreachable.
See Errors for the full list.