CopyHero

Competitor Products Gallery For Agent

GET
/api/v1/product-research/agent/competitor-products

Bounded agent page over subscribed competitor products; the UI route keeps its larger page_size range.

Authorization

HTTPBearer
headerAuthorizationBearer <token>

Query Parameters

keyword*Keyword
Length1 <= length <= 200
cursor?Cursor

0-based Qdrant rank to start at: pass the previous page's next_cursor.

Range0 <= value
Default0
page_size?Page Size
Range1 <= value <= 100
Default50
tags?array<string>|

Only products carrying any of these tags (head state).

vendor?array<string>|

Only products with any of these vendors (head state).

product_type?array<string>|

Only products with any of these types (head state).

published_after?|

Only products the competitor published at/after this time

published_before?|

Only products the competitor published at/before this time

created_after?|

Only products the competitor created at/after this time (Shopify created_at; products without it drop out)

created_before?|

Only products the competitor created at/before this time (Shopify created_at; products without it drop out)

sales_estimate?array<>|

Only products whose sales_estimate compares true, written <op>:<n>: op is eq, gt, gte, lt or lte, n a non-negative integer. gt:0 = sold above the tail; eq:0 = in the tail; also e.g. gte:10. The estimate is compared rounded to a whole number. A null sales_estimate never matches, under any operator. Lags real sales by days. Repeat the param to bound a range (gte:5, lte:10).

revenue_estimate?array<>|

Only products whose revenue_estimate compares true in revenue_currency, written <op>:<n>: op is eq, gt, gte, lt or lte, n a non-negative decimal. Revenue is converted from the store's currency at the latest ECB rate; a null revenue, or a store whose currency the ECB does not quote, never matches. Repeat the param to bound a range.

revenue_currency?EcbCurrency

Currency of the revenue_estimate filter and sort, an ECB-quoted ISO 4217 code. The list items keep their revenue in the store's currency.

Default"EUR"
Value in"EUR""USD""JPY""CZK""DKK""GBP""HUF""PLN""RON""SEK""CHF""ISK""NOK""TRY""AUD""BRL""CAD""CNY""HKD""IDR""ILS""INR""KRW""MXN""MYR""NZD""PHP""SGD""THB""ZAR"
bestseller_top_percent?|

Only products whose bestseller_top_percent compares true, written <op>:<n>: op is eq, gt, gte, lt or lte, n a non-negative integer, e.g. lte:5 = in the top 5 % of their store's sold products (tail_start - 1). A null bestseller_top_percent never matches, so a product in the list's last group matches no operator; fewer than 100 sold products have no top 1 %.

scaling_units_per_day?|

Only products whose scaling_units_per_day compares true, written <op>:<n>: op is eq, gt, gte, lt or lte, n a non-negative decimal, e.g. gt:0 = climbed at all, gte:0.5. A null scaling_units_per_day never matches.

scaling_overtake_percent?array<>|

Only products whose scaling_overtake_percent compares true, written <op>:<n>: op is eq, gt, gte, lt or lte, n a non-negative decimal, e.g. gte:10 = overtook at least 10 % of the store's sold products. A null scaling_overtake_percent never matches. With scaling_units_per_day both apply. Repeat the param to bound a range.

price?array<>|

Only products whose price (first variant, in the store's currency) compares true. A list of <op>:<n>, op eq, gt, gte, lt or lte; every value must hold (AND). A null price never matches.

category_group?array<>|

Only products in any of these category groups, written in:<group_label>.

category_label?array<>|

Category labels, written in:<label> (any of them) and/or not_in:<label> (none of them; unclassified products pass). With category_group a product matches either one.

imported_store_ids?array<>|

Imported into each of these stores (in:<store id>, a bare id means in) and into none of not_in:<store id>; every value must hold (AND).

first_sale_at?array<>|

The first sale of the product, written <op>:<ISO 8601>; every value must hold (AND). A naive time is UTC.

watched_store_ids?array<string>|

Watched stores, written in:<id> (only these; a bare id means in) or not_in:<id> (every subscribed store but these); one operator per request. Narrows within the subscription gate; never widens it.

store_tags?array<string>|

Stores the viewer tagged, written in:<tag> (any of them; no prefix means in) or not_in:<tag> (none of them); one operator per request. Named apart from tags, which means PRODUCT tags. Narrows within the subscription gate; never widens it.

store_product_count?array<>|

The store's product count. A list of <op>:<n>, op eq, gt, gte, lt or lte; every value must hold (AND).

bestseller_sold_count?array<>|

The store's bestseller_sold_count. A list of <op>:<n>, op eq, gt, gte, lt or lte; every value must hold (AND). A store without a bestseller snapshot never matches.

store_currency?array<>|

Only stores selling in any of these ISO 4217 currencies, written in:<code>.

store_market?array<>|

Only stores that ship to any of these ISO 3166 alpha-2 countries (their meta.json ships_to_countries), written in:<code>, e.g. in:DE. A store not scanned since the field exists never matches.

Response Body

Successful Response

application/json
  1. response

GET /product-research/competitor-products — one cursor-paginated gallery page of the competitor products behind the competitor-trends counts.

Rank-cursor block search over the watchlist corpus (see app/service/competitor_products.py), not a relevance-band cutoff — there is no finite in-band total, so this carries no total_matches. No internal_product_id leaves this router — the watchlist-universe router stays the single deliberate internal-id endpoint.

keyword*Keyword
items?array<>
next_cursor?integer|null
has_more?Has More
Defaultfalse
Example Request
curl -X GET 'https://app.copyhero.app/api/v1/product-research/agent/competitor-products?keyword=string'
Example Response
{  "keyword": "string",  "items": [    {      "watched_product_id": "00dd722f-4592-4d63-b257-10210ae59f93",      "external_product_id": "string",      "title": "string",      "image_url": "string",      "source_image_url": "string",      "product_url": "string",      "price": 0,      "store_host": "string",      "published_at": "2019-08-24T14:15:22Z",      "bestseller_rank": 0,      "sales_estimate": 0,      "bestseller_list_size": 0,      "bestseller_sold_count": 0,      "bestseller_top_percent": 0,      "thumbnail_url": "string",      "blur_data_url": "string"    }  ],  "next_cursor": 0,  "has_more": false}