TikTok Creator Search Insights API: Find Content Gaps

Build a TikTok content-gap research workflow with Creator Search Insights, demographics, trend data, related videos, and cursor pagination.

A dark research console turning TikTok search signals into a ranked content opportunity map

TikTok’s Creator Search Insights is more useful than a list of popular hashtags. It exposes searches that TikTok labels as content gaps, then lets you inspect the audience, trend context, and related videos behind an idea. That makes it a workable first stage for an editorial research agent: discover opportunities broadly, validate each one with real posts, and keep the final decision with an editor.

This tutorial uses four endpoints from the TikTok API catalog. I ran every call on October 25, 2026 (UTC). Field names in the examples are observations from those responses; the endpoint reference remains the contract. For creator-level research rather than topic discovery, use the separate TikTok creator research API guide.

Key takeaway

  • Start with creator-search-insights and tab: content_gap; do not infer a gap from popularity alone.
  • Preserve query_id_str as a string. Detail and trend calls require it.
  • Validate a promising phrase with creator-search-insights-videos before briefing a creator.
  • Treat missing demographics as missing data, not as a zero-valued audience segment.

What the live run returned

The first live page used Science, English, a limit of 10, and the content_gap tab. It returned ten ideas, cursor: 10, and has_more: true. The first item was “mount hood,” labelled content_gap, with a popularity score of 70 and 1,020 related videos. Those values are a snapshot, not a forecast or an SEO difficulty score.

Evidence card showing the first content-gap item returned by TikTok Creator Search Insights The first observed content-gap result and the page cursor from the October 25 live call.

The detail call accepted past_30_days plus age,country. Its payload exposed age, location, and demographics keys, but individual dimensions can be sparse. A pipeline should retain the raw “unknown” state instead of replacing it with a confident audience profile.

Evidence card listing the keys returned by the TikTok insight detail endpoint The detail response exposes optional audience dimensions; sparse results must remain sparse.

The related-video call returned ten posts under search_item_list[].aweme_info with its own cursor; its top-level aweme_list was empty. This is the validation step: examine how the query is currently answered, whether the top posts actually match the phrase, and whether the gap fits your product or editorial scope.

Evidence card showing the TikTok related-video response and pagination fields The related-video endpoint turns an abstract search opportunity into posts an editor can review.

Set up one strict API helper

Install Requests and keep the key outside source control:

python -m pip install requests
export SANDBASE_API_KEY="your_key_here"

Every endpoint is called with POST /v1/api/<model>. The helper below accepts only the documented outputs[0].data envelope and fails if a run is incomplete or malformed.

import os, time, requests

BASE = "https://api.sandbase.ai/v1/api"
HEADERS = {
    "Authorization": f"Bearer {os.environ['SANDBASE_API_KEY']}",
    "Content-Type": "application/json",
}

def call(model, payload, retries=2):
    for attempt in range(retries + 1):
        response = requests.post(
            f"{BASE}/{model}", headers=HEADERS, json=payload, timeout=90
        )
        if response.status_code < 500 and response.status_code != 429:
            response.raise_for_status()
            break
        if attempt == retries:
            response.raise_for_status()
        time.sleep(2 ** attempt)
    body = response.json()
    if body.get("status") != "completed":
        raise RuntimeError(body.get("error") or body.get("status"))
    try:
        return body["outputs"][0]["data"]
    except (KeyError, IndexError, TypeError) as exc:
        raise RuntimeError("Missing outputs[0].data") from exc

Discover and page content gaps

def discover(category="Science", language="en", pages=2):
    cursor, rows, seen = 0, [], set()
    for _ in range(pages):
        page = call("tiktok/app-v3/creator-search-insights", {
            "tab": "content_gap",
            "limit": 20,
            "offset": cursor,
            "category_filters": category,
            "language_filters": language,
        })
        for item in page.get("inspiration_list") or []:
            query_id = item.get("query_id_str")
            if not query_id or query_id in seen:
                continue
            seen.add(query_id)
            rows.append({
                "query_id": query_id,
                "query": item.get("query_text"),
                "labels": item.get("query_labels") or [],
                "popularity": item.get("popularity"),
                "video_count": item.get("video_num"),
                "trend": item.get("trending_seq") or [],
            })
        if not page.get("has_more"):
            break
        cursor = page.get("cursor")
        if cursor is None:
            raise RuntimeError("has_more was true but cursor was missing")
    return rows

Keep the explicit content_gap label in the record. A high popularity value is interesting, but it does not independently prove unmet demand. Also deduplicate on query_id_str: live feeds can shift between requests.

Enrich one candidate

def enrich(row):
    detail = call("tiktok/app-v3/creator-search-insights-detail", {
        "query_id_str": row["query_id"],
        "time_range": "past_30_days",
        "dimension_list": "age,country",
    })
    trend = call("tiktok/app-v3/creator-search-insights-trend", {
        "query_id_str": row["query_id"],
        "from_tab_path": "TRENDING,TOPICS",
        "query_analysis_required": True,
    })
    videos = call("tiktok/app-v3/creator-search-insights-videos", {
        "keyword": row["query"], "offset": 0, "count": 20,
    })
    related = [item.get("aweme_info")
               for item in videos.get("search_item_list") or []
               if item.get("aweme_info")]
    return {**row, "detail": detail, "trend_context": trend,
            "videos": related}

Do not rank directly on one undocumented internal field. A safer local score uses only stable, visible inputs and keeps its assumptions obvious:

def editorial_score(row):
    popularity = row.get("popularity") or 0
    supply = max(row.get("video_count") or 1, 1)
    recent = row.get("trend") or []
    momentum = (recent[-1] - recent[0]) if len(recent) > 1 else 0
    return round(popularity / (supply ** 0.5) + max(momentum, 0) * 0.1, 2)

This score is a queueing aid, not a TikTok metric. Save the formula version next to each result so later reports remain reproducible.

Configuration reference

EndpointRequiredUseful optional fieldsPagination
creator-search-insightsnonetab, limit, offset, category and language filtersreturned cursor → next offset
creator-search-insights-detailquery_id_strtime_range, dimension_list, custom datesnone observed
creator-search-insights-trendquery_id_strfrom_tab_path, query_analysis_requirednone observed
creator-search-insights-videoskeywordcount, offsetreturned cursor/has-more fields

Production rules that prevent bad briefs

Cache discovery pages for a short, fixed window and store the capture time. Retry transport errors and 429/5xx responses, but do not retry a 400 with the same payload. Log the run ID without logging the API key. Put a maximum page count on every job. If an endpoint returns a new shape, quarantine that run instead of silently emitting empty rows.

Most importantly, require a human check before a topic becomes a brief. The reviewer should open several related posts, confirm language and intent, note whether results are current, and reject ideas that only look relevant because of an ambiguous phrase. Public data can guide research; it does not grant rights to reuse creators’ media or personal information.

Troubleshooting

  • Detail returns little demographic data: keep it nullable and continue with trend and video validation.
  • The next page repeats ideas: deduplicate by query_id_str.
  • A numeric query ID changes in JavaScript: transport and store query_id_str, never a floating-point number.
  • The list changes between runs: expected for a live recommendation surface; compare dated snapshots.
  • The call completes without outputs[0].data: fail the job and preserve the response for debugging.

FAQ

Is a content-gap label a guarantee that a video will perform?

No. It is an observed platform label for a query, not a prediction for a specific account, creative, or posting time.

Can I monitor only one niche?

Yes. Use category and language filters, then add your own allowlist of terms. The API reference lists the currently accepted category and language values.

Should an LLM choose the final topics?

Use it to summarize clusters after deterministic filtering. Keep the source rows, scores, and related-video links available to an editor.

Where do I inspect the current parameters?

Open the endpoint inside the TikTok API catalog; schemas and availability can change after this dated test.