Home > Blog > TikTok Keyword Research with the Search Suggestions API
Keyword Research

How to Do TikTok Keyword Research with an API

By PrimeApi·August 15, 2026·9 min read·Updated August 15, 2026

Keyword research on TikTok has no Keyword Planner, no volume column and no public index you can query. What it does have is the list that drops down while someone types in the search box, and that list is built from queries people actually run. The searched-suggest endpoint exposes it as a one-parameter HTTP call, which turns it into a keyword expansion tool: seed a term, read the suggestions back, re-seed with each of them, and a map of how your topic is phrased on TikTok assembles itself. This guide covers the response shape, what group_id is good for, how to run the expansion without burning credits, and where the method stops being reliable.

TikTok search is not Google search

The two behave differently enough that keyword lists do not transfer between them. On Google, a query is typed to reach a page, and years of tooling have trained people to type short, noun-heavy terms. On TikTok, a query is usually typed to reach a video, and often typed immediately after seeing something - so the phrasing stays conversational and runs long. "cat sounds to attract cats" is a normal TikTok query. As a Google keyword it would look like an outlier.

The second difference is what the surface is made of. A web search engine indexes titles, headings and body copy. TikTok has none of those to work with, so the text it can match on is the caption, the hashtags in it, the sound title and whatever is written on screen. That makes keyword work here a question of how you word captions and which tags you attach, not of page structure.

The third difference is timing. Suggestion data is live. It reflects what is being typed now rather than a rolling monthly average, so a phrase that started spreading last week can show up in the suggestions before any keyword database has heard of it. The flip side is that you cannot treat a snapshot as permanent - if the phrasing of a topic matters to you, re-run the expansion on a schedule and diff the results.

The call

The base URL is https://api.primeapi.co/ and authentication is a single header, X-PrimeAPI-Key. searched-suggest takes one required parameter, keyword, and nothing else. There is no cursor, no count and no paging - one call, one list.

curl

curl -s "https://api.primeapi.co/searched-suggest?keyword=cat" \
  -H "X-PrimeAPI-Key: YOUR_API_KEY"

The response is small and flat:

{
  "data": [
    { "word": "cat sounds to attract cats", "group_id": "978840492744295435" }
  ],
  "status_code": 0,
  "status_msg": "",
  "log_id": "2026081509234904C595AAD6EEDB75BD8C"
}

Node.js (Axios)

const axios = require("axios");

async function suggest(keyword) {
  const res = await axios.get("https://api.primeapi.co/searched-suggest", {
    params: { keyword },
    headers: { "X-PrimeAPI-Key": "YOUR_API_KEY" }
  });
  console.log("Remaining credits:", res.headers["x-primeapi-balance"]);
  return (res.data.data || []).map((s, i) => ({
    rank: i,
    word: s.word,
    groupId: s.group_id
  }));
}

suggest("cat")
  .then(rows => rows.forEach(r => console.log(r.rank, r.word)))
  .catch(err => console.error(err.response ? err.response.data : err.message));

Python (Requests)

import requests

HEADERS = {"X-PrimeAPI-Key": "YOUR_API_KEY"}

def suggest(keyword):
    r = requests.get(
        "https://api.primeapi.co/searched-suggest",
        params={"keyword": keyword},
        headers=HEADERS, timeout=15,
    )
    data = r.json()
    if data.get("status_msg"):
        print("warning:", data["status_msg"])
    return [(i, s["word"], s["group_id"])
            for i, s in enumerate(data.get("data", []))]

for rank, word, group in suggest("cat"):
    print(rank, word, group)

What comes back

There are only six fields worth knowing, which is what makes this endpoint cheap to work with.

FieldHolds
dataThe suggestion list. Around nine entries for a typical seed. Its order is meaningful - store the index, because it is the only ranking signal you get.
data[].wordThe suggested query, as text, exactly as it would appear under the search box. This is your keyword.
data[].group_idThe group the suggestion belongs to, as a long numeric string. Opaque - use it as a key, never parse it.
status_codeInteger status of the call itself, separate from the HTTP status.
status_msgEmpty on a normal response. Treat anything non-empty as a reason to log the call rather than trust the list.
log_idPer-request identifier. Worth storing with the raw payload if you ever need to ask about a specific call.

Note what is not here: no counts, no scores, no timestamps. If you want to see the raw payload against your own key before writing any parsing code, the interactive playground runs the endpoint live and prints the JSON.

Handling group_id correctly

Every suggestion carries a group_id, and it arrives as a string for a good reason: the values are long enough that pushing one through a JavaScript number, or a 32-bit integer column, quietly corrupts it. Keep the string type end to end - in your parser, your database column and your CSV export.

Its practical use appears once you are merging results from many seeds. The same word can be returned for several different seeds, and the group ID gives you a second axis to group and de-duplicate on beyond the text itself. When suggestions for one seed come back carrying more than one group value, that is a hint the term is ambiguous and its audience splits into distinct clusters - worth checking before you build a content plan around a single interpretation. Do not assume the value is stable over months, and do not use it alone as a primary key. Pair it with the word.

Building a keyword tree by re-seeding

One call gives you nine phrases. The technique that makes the endpoint useful is treating each returned word as a new seed and running the call again, breadth-first, until you hit a depth cap. Two levels usually cover a topic; three starts producing noise and repetition.

Do the arithmetic before you start the loop. One seed is 1 credit. Expanding its nine children is 9 more, so a two-level tree costs about 10 credits and yields somewhere under a hundred unique phrases after de-duplication. Three levels is roughly 90 requests. That is still small money, but it is worth a visited set rather than a blind recursion, because children frequently suggest their own parent back and an uncapped crawl will loop.

import time, requests

HEADERS = {"X-PrimeAPI-Key": "YOUR_API_KEY"}

def suggest_words(keyword):
    r = requests.get(
        "https://api.primeapi.co/searched-suggest",
        params={"keyword": keyword},
        headers=HEADERS, timeout=15,
    )
    return [s["word"] for s in r.json().get("data", [])]

def keyword_tree(seed, max_depth=2, max_calls=60):
    seen = {seed: {"depth": 0, "parent": None}}
    queue = [(seed, 0)]
    calls = 0
    while queue and calls < max_calls:
        term, depth = queue.pop(0)
        if depth >= max_depth:
            continue
        calls += 1
        for word in suggest_words(term):
            if word in seen:
                continue
            seen[word] = {"depth": depth + 1, "parent": term}
            queue.append((word, depth + 1))
        time.sleep(0.7)   # stay well inside 100 requests per minute
    return seen, calls

tree, used = keyword_tree("cat food")
print(f"{len(tree)} phrases from {used} credits")
for word, meta in sorted(tree.items(), key=lambda kv: kv[1]["depth"]):
    print(" " * meta["depth"] * 2, word)

Forcing the tree wider with prefixes

Breadth-first expansion follows whatever TikTok considers the most likely completions, which means it tends to run down one interpretation of your topic. To break out of that, append a character to the seed and call again: "cat food a", "cat food b", and so on. Each variant pushes the completion engine into a different branch and surfaces phrases the plain seed never returns. Twenty-six extra calls per seed is 26 credits, so use it on the two or three seeds that matter rather than on the whole tree. The same trick works with a leading word - prefixing with "how to", "best" or "why" pulls the intent of the results in a particular direction, which is usually more useful than the alphabet sweep.

What to store

Store one row per phrase with the seed it came from, its depth, its rank inside the response, its group ID and the timestamp of the run. Rank and depth together are your rough priority score: a phrase that appears at position 0 for several different seeds is a hub term for the topic. Re-running the same tree weekly and diffing on phrase text turns the whole thing into a trend monitor - new rows are phrasings that were not being typed before.

From phrases to something you can measure

Suggestions tell you how a topic is worded. They do not tell you how much attention it gets, and that gap has to be closed with other endpoints. The practical bridge is the hashtag: pick the phrase you care about, find one video already using it, take the numeric post ID from that video's URL and call post-detail. The challenges array in that response gives you the tag IDs on the post, and feeding one into challenge-posts returns the video feed behind it - creators, captions, upload times, so you can judge whether the phrase has real activity behind it or just a nice ring to it. The hashtag videos guide walks through that resolution step in detail.

Two other signals are worth wiring in. If a phrase turns out to be attached to an audio trend, the sound is the thing that is actually moving and it can be tracked directly, as the guide to tracking trending sounds explains. And if your seeds are geographic - a city, a neighbourhood, a venue type - the phrasing work pairs naturally with location data, covered in the local business research guide, because a place record tells you how many videos were filmed somewhere while suggestions tell you how people ask about it.

What this data will not do

Be clear about the limits before you present a keyword list to anyone. There is no volume figure, so any number in your report is one you calculated downstream, not one the API gave you. There is no difficulty metric and nothing resembling competition scoring. The list is short by design - nine or so entries - so coverage comes from running many seeds, not from asking for a bigger page. And because the data is real-time with no caching, two runs an hour apart can differ; that is a feature for trend work and a nuisance if you expected a stable reference table, so always store the run timestamp.

One more practical note: a seed that means nothing on TikTok returns an empty data array and still costs a credit. Check the array length before indexing into it, and count empty responses in your expansion run - a branch that keeps returning nothing is telling you the topic ends there.

Credits, limits and errors

Every request costs 1 credit, the default rate limit is 100 requests per minute, and responses average around a second because nothing is cached. Read your remaining balance from the X-PrimeAPI-Balance header rather than polling anything. New accounts get 50 free credits, which is enough for a full two-level tree plus a prefix sweep, and the paid tiers run from $9.90 for 2,500 credits to $279.00 for 500,000 - the table is on the pricing page.

Four failures come back as a JSON message and deserve their own branches in an expansion loop, since a long run should not die silently halfway through. "Please sign up to primeapi.co" means the key header was missing. "PrimeAPI-Key is not available" means the key is wrong or the account was never activated. The per-minute message means you crossed the rate limit, which in a tight expansion loop is the one you will actually hit - add the sleep shown above or back off and retry. "Your balance has been exhausted..." means you are out of credits. The full reference sits in the API documentation.

Putting it together

The workflow is short. Pick two or three seeds that describe your topic in the language your audience uses. Expand each one breadth-first to depth two with a visited set and a call cap, then widen the best seeds with prefix variants. Store every phrase with its seed, depth, rank, group ID and run timestamp, and sort by how often a phrase surfaces across different seeds rather than by any single response order. Then take the handful of phrases that survive and measure them through hashtag and sound data before you commit them to captions. Fifty free credits on a new account cover that entire loop end to end. PrimeApi is an independent service and is not affiliated with or endorsed by TikTok or ByteDance.

Frequently asked questions

What does searched-suggest actually return?

A data array of suggestion objects, each with a word (the suggested query text) and a group_id (an opaque numeric string delivered as a string). Alongside it you get status_code, status_msg and a log_id for the request. A typical seed returns around nine suggestions in one call, in the order TikTok would show them.

Do the suggestions come with search volume numbers?

No. There is no volume, CPC or difficulty column anywhere in the response, and inventing one from the payload is not possible. What you get instead is ordinal: the position of a word inside data is a ranking signal, and whether a phrase appears at all tells you people type it. If you need a magnitude, you have to measure it downstream - for example by pulling the video feed behind the hashtag that matches the phrase.

What is group_id for?

It is the identifier of the group a suggestion was drawn from. Treat it as an opaque key: useful for grouping and de-duplicating when you merge results from dozens of seeds, useless if you try to parse meaning out of the digits. Keep it as a string - the values are long enough that converting one to a JavaScript number silently loses precision.

Can I ask for suggestions in a specific country or language?

The endpoint takes one parameter, keyword, and there is no country, language or region argument. The control you have is the seed itself: write the seed in the language you want back, then check the first response before you build an expansion run on top of it. Because the call carries no user session, results are not personalised to whoever is typing, which is what makes them usable as research rather than as a mirror of one account.

How many credits does a keyword expansion run cost?

One credit per request, including a request that returns an empty list. One seed is 1 credit, expanding each of its roughly nine suggestions is about 10 credits for a two-level tree, and a three-level tree lands near 90 before de-duplication trims it. The default rate limit is 100 requests per minute, new accounts start with 50 free credits, and every response returns what is left in the X-PrimeAPI-Balance header.

Can I use this to power an autocomplete box in my own app?

Yes, and the small payload makes it well suited to it, but bill for it properly. Each keystroke that reaches the API is a credit, so debounce input by about 250-300 ms, do not fire below two or three characters, and cache responses per normalised prefix for a few minutes. Without those three rules a single user typing a twenty-character phrase can burn twenty credits.

Start building with the PrimeApi TikTok API
50 free credits when you sign up. No card required - you only pay when you need more.

← Back to the PrimeApi Blog