---
title: "TikTok keyword opportunities: search terms with search volume, plus product and category leads"
method: POST
path: "/product-insights/opportunities"
tags: ["Products"]
---

# TikTok keyword opportunities: search terms with search volume, plus product and category leads

`POST /product-insights/opportunities`

## Request

- Base URL: `https://api.reacherapp.com/public/v1`
- URL: `https://api.reacherapp.com/public/v1/product-insights/opportunities`
- Auth: API key in header `x-api-key` and API key in header `x-shop-id`

TikTok's Product Opportunities for one shop, collected daily: ``KEYWORD`` leads are search terms buyers use (``query_words``, ``search_volume``), ``PRODUCT`` and ``CATEGORY`` leads are products and categories TikTok says are in demand. Market metrics are TikTok's display buckets (``est_market_gmv`` ``10k+``) with a parsed lower bound (``*_min``). Sorted by search volume. The newest collection per type by default; ``start_date``/``end_date`` (at most 30 days) read every day in the range, one row per opportunity with ``days_seen``.

``runs`` says what the collector has for each type — ``ok``, ``partial`` (TikTok had more than was paged through), ``collected_empty``, ``failed`` or ``not_collected`` — with ``latest_attempt_outcome`` for the newest attempt, so an empty ``data`` list always comes with its reason. Only the newest successful collection's rows are returned. Single-shop only. Returns 503 while the keyword tables are unavailable.

## Request body

- OpportunitiesRequest: Body for ``/product-insights/opportunities``.
  - `opportunity_type` 'KEYWORD' | 'PRODUCT' | 'CATEGORY', nullable: ``KEYWORD`` (search terms with search volume), ``PRODUCT`` or ``CATEGORY`` leads; every type when omitted.
  - `start_date` string, date, nullable: With ``end_date``, read every day in the range instead of the newest snapshot.
  - `end_date` string, date, nullable
  - `limit` integer: Opportunities per page.
  - `page` integer: 1-based page.

## Response `200`

Successful Response

- ProductOpportunitiesResponse
  - `shop_id` integer, required
  - `mode` 'latest' | 'range', required
  - `start_date` string, nullable
  - `end_date` string, nullable
  - `runs` OpportunityRun[], required
    - `opportunity_type` string, required
    - `snapshot_date` string, nullable: Day the data shown was collected for (YYYY-MM-DD).
    - `status` 'not_collected' | 'collected_empty' | 'partial' | 'failed' | 'ok' | 'region_not_supported', required: ``ok``: the newest stored collection. ``partial``: stored, but TikTok had more than the collector paged through. ``collected_empty``: collected, TikTok returned nothing. ``failed``: every attempt in the window failed. ``not_collected``: no attempt in the window — the last 35 days, or that day of the requested range. ``region_not_supported``: TikTok serves SEO words only for US, UK and Southeast Asian shops.
    - `latest_attempt_outcome` string, nullable: Outcome of the newest attempt, which may be newer than the data shown, e.g. ``failed`` after an earlier ``ok``.
    - `run_id` string, nullable
    - `coverage` OpportunityRunCoverage
      - `upstream_total_count` integer, nullable: TikTok's own count of opportunities of this type.
      - `rows` integer, nullable: Opportunities stored by the run.
      - `is_complete` boolean, nullable
  - `data` ProductOpportunity[], required
    - `opportunity_id` string, required
    - `opportunity_type` string, required
    - `title` string, nullable
    - `query_words` string[]: The search terms of a KEYWORD lead.
    - `search_volume` string, nullable
    - `search_volume_min` integer, nullable
    - `est_market_gmv` string, nullable: TikTok's display bucket, e.g. ``10k+``.
    - `est_market_gmv_min` integer, nullable: Lower bound of ``est_market_gmv``, in ``currency``.
    - `currency` string, required
    - `est_market_sales_volume` string, nullable
    - `est_market_sales_volume_min` integer, nullable
    - `competitor_product_count` string, nullable
    - `competitor_product_count_min` integer, nullable
    - `video_view_pv` string, nullable
    - `video_view_pv_min` integer, nullable
    - `category_name_level_1` string, nullable
    - `category_name_level_2` string, nullable
    - `category_name_level_3` string, nullable
    - `tags` string, nullable
    - `brand_name` string, nullable
    - `reward_info` string, nullable
    - `external_product_id` string, nullable
    - `create_time` string, nullable
    - `expire_time` string, nullable
    - `position` integer, nullable
    - `first_seen` string, required
    - `last_seen` string, required: The values shown are from this day.
    - `days_seen` integer, required
  - `pagination` PublicApiCorePaginationPaginationMeta, required: Pagination metadata returned in responses.
    - `total_count` integer, required
    - `page` integer, required
    - `page_size` integer, required
    - `total_pages` integer, required

## Other responses

- `422`: Validation Error

## Changes

> 76 revisions in range; 1 not diffed.

- **2026-10-21** `b98092453f76`: 1 info
  - endpoint added

[Operation history](https://skmtc.dev/reacherapp/apis/reacher-data-api/changes/product-insights/opportunities/post.md)

---

[API](https://skmtc.dev/reacherapp/apis/reacher-data-api.md) - [All operations](https://skmtc.dev/reacherapp/apis/reacher-data-api/llms.txt) - [OpenAPI document](https://skmtc.dev/reacherapp/apis/reacher-data-api/revisions/63ce74b30b5a?raw)
