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, magento or prestashop.

  • 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. 0 is 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; null if 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 null for 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_usd and market.price_max_usd follow.

  • 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_high is the top. revenue is null when there is no estimate.

  • Name
    revenue.scope
    Type
    string
    Description

    Whose revenue the band is: seller for this store, market for every store selling the product, null when we cannot tell. Do not add up market figures across the stores of one market.


GET/v1/products/search

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-types lists 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_shop for 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. UK is read as GB.

Sort & pagination

  • Name
    sort
    Type
    string
    Description

    est_revenue (the default), relevance (needs q, and the default when q is 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 a 400 whose details.allowed lists these.

  • Name
    page
    Type
    integer
    Description

    Page number, 1-500. Default 1. A search can be paged through its first 500 results: when page × page_size goes past them, the request is a 400 invalid_parameter whose details.max_page names 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 null there 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

GET
/v1/products/search
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/v1/products/:product_id

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_pct its 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_age is the age band reached most, audience.country_count the 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_usd is given on this product's own row only, and is null on 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_plays and organic_share_pct of the product's TikTok Shop listings. null unless 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

GET
/v1/products/merodacosmetics.de_9079935172920
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/v1/products/by-url

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

GET
/v1/products/by-url
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"

POST/v1/products/by-urls

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

POST
/v1/products/by-urls
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"]
}

GET/v1/products/:product_id/image

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 the X-Product-Image-Size header says full. Both are WebP.

Request

GET
/v1/products/merodacosmetics.de_9079935172920/image
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

GET/v1/products/:product_id/ads

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

GET
/v1/products/merodacosmetics.de_9079935172920/ads
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 }
}

GET/v1/products/:product_id/similar

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

GET
/v1/products/merodacosmetics.de_9079935172920/similar
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" }
  ]
}

GET/v1/products/:product_id/sellers

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 (at page_size=7, page 8 holds store 50 alone). A later page is a 400 invalid_parameter whose details.max_page names the last one.

  • Name
    page_size
    Type
    integer
    Description

    Stores per page, 1 to 25. Default 25.

Request

GET
/v1/products/merodacosmetics.de_9079935172920/sellers
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/v1/products/:product_id/trend

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) and est_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, and est_daily_spend_usd, the market's estimated daily spend.

Request

GET
/v1/products/merodacosmetics.de_9079935172920/trend
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/v1/products/:product_id/audience

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 the female_pct column to female_share_pct. The reach Meta could not age comes last, as unknown.

  • Name
    countries
    Type
    array
    Description

    The 15 countries reached most, largest first: code (ISO-2) and reach_share_pct, its share of the whole reach. The first is top_country, with top_country_share_pct; country_count counts every country reached.

Request

GET
/v1/products/merodacosmetics.de_9079935172920/audience
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/v1/products/:product_id/creative-insights

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, and pct, 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

GET
/v1/products/merodacosmetics.de_9079935172920/creative-insights
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/v1/products/:product_id/sourcing

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), title and shop, as the supplier lists them.

  • Name
    unit_cost_usd
    Type
    number
    Description

    The supplier's price for one unit, in USD. bulk_unit_cost_usd is the price from bulk_qty units, both null when the listing has no bulk price; moq is 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. null when 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. markup is the price divided by the cost. Both null when 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_usd is the most a sale can cost in ads before it loses money, in USD to the cent.

  • Name
    verdict
    Type
    string
    Description

    profitable, possible, difficult or unprofitable; null when 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; null on some older matches) and moq (null when the listing states none).

Request

GET
/v1/products/lymphoria.co_7922886246539/sourcing
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"
}

GET/v1/markets/:market_id

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) or spend (estimated daily spend).

  • Name
    cursor
    Type
    string
    Description

    The previous page's pagination.cursor; omit it for the first page. The last page has has_more: false and cursor: 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

GET
/v1/markets/karlson-berlin.com_10261128249691
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 1 and skip values. Products that were never ranked simply have no rank — 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/v1/brands/:brand_id/products

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_from on GET /v1/brands asks when the store itself was added. Applies to both bestsellers and latest. An unparseable date returns 400 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-13 keeps it. Pass a full timestamp (for example 2026-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

GET
/v1/brands/gymshark.com/products
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_parameter for a value we refuse, with details.parameter naming it. Nothing is charged.
    • Search: an unknown sort (details.allowed lists the sorts), relevance without q, an unknown source or niche, a market_country outside 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 a cursor that is not one we returned.
    • Sellers: a page past the 50 stores (details.max_page).
    • A url that is not an http(s) URL, and an image size other than full or compact.
  • 400 validation_error for a value of the wrong type or out of range, with details.errors[].field naming it: a search page over 500 or page_size over 25, a product's ads past page 5 or with page_size over 10, a look-alike limit outside 1-20, a sellers page over 50 or page_size over 25, a trend's days outside 1-90, a negative minimum, a missing url, a product id over 256 characters, or a by-urls body that is empty, holds more than 20 URLs or carries an unknown key.
  • 404 product_not_found for an unknown product id or URL (same message whatever the reason; no credit charged), 404 media_unavailable when a product's image is not on our media host (no credit charged), 404 audience_unavailable when a product's market disclosed no reach (no credit charged), 404 insights_unavailable when none of a product's market's ads has been analysed, or none has a value to show (no credit charged), 404 sourcing_unavailable when a product has no supplier match (no credit charged), 404 brand_not_found for an unknown brand, 404 market_not_found for a market no product belongs to.
  • 503 service_unavailable when the product catalogue is unreachable.

See Errors for the full list.

Was this page helpful?