# searchtok > tiktok and instagram reels research for agents and scripts: the outliers, hooks and creators in any niche, every number from real posts. searchtok finds the tiktoks and instagram reels worth copying. give it a niche, an app, a website or a handle and it finds the outliers (posts with far more views than the account usually gets), the hooks behind them and the creators making them. every endpoint is a searchtok tool, the same ones our own agent uses. views, outlier scores and baselines are worked out in code from real posts, never guessed by a model, and every result links back to the searchtok dashboard. ## ways in - rest api: https://searchtok.app/api/v1 (live. for any agent or script that can send http: n8n, make, manus, your own code.) - mcp server: https://searchtok.app/mcp (live. streamable http, for claude, chatgpt, claude code, cursor, codex, openclaw and any agent that takes a remote mcp server. add the url and sign in (oauth), or send your api key as `Authorization: Bearer st_live_...`.) - cli: npx searchtok - agent skill: npx skills add Mitchell2398/searchtok-agent in claude code: ```bash claude mcp add --transport http searchtok https://searchtok.app/mcp --header "Authorization: Bearer $SEARCHTOK_API_KEY" ``` ## quick start 1. sign in and make an api key at https://searchtok.app/app/settings/keys 2. keep it in an environment variable: `export SEARCHTOK_API_KEY=st_live_...` 3. check it works. this one is free: ```sh curl https://searchtok.app/api/v1/usage \ -H "Authorization: Bearer $SEARCHTOK_API_KEY" ``` - keys: send your key on every request as `Authorization: Bearer st_live_...`. make and revoke keys at https://searchtok.app/app/settings/keys. every endpoint needs a searchtok plan, except usage and profile, which always work so an agent can tell you where you stand. - credits: each call costs credits from one pool, shared by every way in. every signed-in response says what it used and what's left in two headers: `credits-used` and `credits-left`. a call that fails is refunded. send the same `Idempotency-Key` header to retry a call without paying twice. credit costs are provisional and may change before launch. - rate limit: up to 60 requests a minute per key. past that you get a 429 with a `Retry-After` header. - errors: every error has the same shape. `hint` says what to try next. `{"error": {"code", "message", "hint"}}` ## endpoints requests and responses are json. an endpoint's input is its tool's input, and its response is the tool's output. credit costs are provisional and may change before launch. ### `search_content` · POST /api/v1/search · credits: 20 per query Find the TikToks and Instagram Reels that did best for a topic or niche, ranked by outlier score (a post's views ÷ its account's usual views). searchtok's main tool. Use it for: what's working in a niche, viral examples of a format, hooks and ideas to copy. Don't use it for: a known account (get_account), a known post (analyze_post), posts like a given post (find_similar), finding creators (find_accounts). Each query runs several searches plus searchtok's index, removes repeats and scores each post. Several angles or niches? Pass them together as queries (up to 5): they run at the same time, much faster than a call each. Returns compact rows (id, url, handle, views, outlier_score with its explanation, caption, hook, format), what was searched and a dashboard link. Top rows carry their hook word for word, so analyze_post is rarely needed just to quote one. Paid posts are flagged paid. input: - `query` (string): What to look for, in the words people would search on TikTok: a niche, topic or format ("habit tracker app", "smile transformation"). Short beats long. - `queries` (string[]): Up to 5 searches in one call, run at the same time (several angles on a niche, or several niches). Faster than one call per query; each query costs what a single search costs. Rows say which queries found them. - `platforms` (("tiktok" | "instagram")[]): Default: both - `timeframe` ("7d" | "30d" | "90d" | "1y"): Only posts from this long ago or newer. Default: any time - `language` (string): Two-letter language, e.g. "en". Posts in other languages are dropped; posts whose language is unknown are kept - `region` (string): Two-letter country that biases TikTok results (it doesn't filter them) - `limit` (integer): Rows to return. Default 15 (more for several queries). Results are cut to about 2k tokens (plus about 1k per extra query); the rest are at `link` returns: - `search_id` (string, required): This search's id in searchtok - `rows` (object[], required): Best first: id, platform, url, handle, posted_at, views, outlier_score, outlier_explained, caption, hook, format, why_matched - `total_found` (number, required): Distinct posts found before the limit - `searched` (object[], required): Each search that ran, and how many posts it returned - `missed` (string[]): Calls that failed or were too slow. Results may be thinner than usual - `left_out` (number): Weaker rows (under 1x their account's usual views, or no score) left out. They're at `link` - `truncated` (number): Rows left out to keep this result short. They're at `link` - `note` (string): Said when there's nothing to show or no scores, with what to try - `link` (string, required): This search in the searchtok dashboard ```sh curl -X POST https://searchtok.app/api/v1/search \ -H "Authorization: Bearer $SEARCHTOK_API_KEY" \ -H "Content-Type: application/json" \ -d '{"query":"habit tracker app"}' ``` example response (real output, lists cut short): ```json { "search_id": "00000000-0000-4000-8000-000000000001", "rows": [ { "id": "tiktok:7377548155597950251", "platform": "tiktok", "url": "https://www.tiktok.com/@spaceyflower/video/7377548155597950251", "handle": "spaceyflower", "posted_at": "2024-06-07", "views": 565678, "outlier_score": 1457.9, "outlier_explained": "1457.9x this account's usual views (median 388 over its last 20 posts)", "caption": "#habittracker #habittracker2024 #BulletJournal #bulletjournaling #yearinpixels…", "hook": null, "format": "video", "cover_url": "https://searchtok.app/api/c/tiktok:7377548155597950251", "why_matched": [ "#habittracker" ] }, "... 14 more" ], "total_found": 111, "searched": [ { "platform": "instagram", "search": "reels search \"habit tracker app\"", "found": 9 }, "... 5 more" ], "link": "https://searchtok.app/app/searches/00000000-0000-4000-8000-000000000001" } ``` ### `find_accounts` · POST /api/v1/accounts/find · 15 credits Find TikTok or Instagram creators and brands in a niche, with how well their posts do: followers, usual (median) views and outlier rate (how often a post gets 3x+ their usual views). Use it for: creators to learn from or partner with, competitors in a niche, accounts that reliably go viral. Don't use it for: one account you already know (get_account), a brand's website or name (analyze_brand), posts (search_content). Returns up to 8 accounts (6 on Instagram), most reliable outlier-makers first, each with id, handle, url, followers, median views, outlier rate and posts per week. Never returns or infers gender, ethnicity or age. input: - `query` (string, required): A niche or topic ("habit tracker app"), or a name to look up - `platform` ("tiktok" | "instagram"): Default: tiktok returns: - `rows` (object[], required) - `missed` (string[]) - `truncated` (number) - `note` (string) ```sh curl -X POST https://searchtok.app/api/v1/accounts/find \ -H "Authorization: Bearer $SEARCHTOK_API_KEY" \ -H "Content-Type: application/json" \ -d '{"query":"habit tracker app"}' ``` example response (real output, lists cut short): ```json { "rows": [ { "id": "tiktok:7597922150545507350", "platform": "tiktok", "handle": "completerapp", "display_name": "Completer - Habit Tracker", "url": "https://www.tiktok.com/@completerapp", "followers": 667, "verified": null, "bio": null, "median_views": 674, "posts_sampled": 30, "baseline_explained": "median 674 views over its last 30 posts (2+ days old)", "outlier_rate": 0.17, "posts_per_week": 1.1 }, "... 7 more" ] } ``` ### `get_account` · GET /api/v1/accounts/{platform}/{handle} · 3 credits Look up one TikTok or Instagram account: profile, its usual (median) views, outlier rate, posting rate, format mix and its best recent posts by outlier score. Use it for: checking a creator or competitor you already know by handle or url, and following up on an account from another searchtok result. Don't use it for: finding accounts (find_accounts), a brand known only by website or name, or comparing brands (analyze_brand). Returns the profile, numbers (all worked out from its latest 30 posts) and up to 5 top recent posts with outlier scores and ids for analyze_post. input: - `platform` ("tiktok" | "instagram", required, in the path): tiktok or instagram - `handle` (string, required, in the path): The account's handle, with or without "@" returns: - `account` (object, required) - `numbers` (object, required) - `top_outliers` (object[], required): Its best recent posts by outlier score - `link` (string, required) ```sh curl https://searchtok.app/api/v1/accounts/tiktok/completerapp \ -H "Authorization: Bearer $SEARCHTOK_API_KEY" ``` example response (real output, lists cut short): ```json { "account": { "id": "tiktok:7597922150545507350", "platform": "tiktok", "handle": "completerapp", "display_name": "Completer - Habit Tracker", "url": "https://www.tiktok.com/@completerapp", "followers": 667, "following": 493, "posts": 79, "likes_total": 13861, "verified": false, "bio": "Build better habits with a simple habit tracker. Download \"COMPLETER\" on the App Store. 📲 getcompleter.app" }, "numbers": { "median_views": 674, "posts_sampled": 30, "baseline_explained": "median 674 views over its last 30 posts (2+ days old)", "outlier_rate": 0.17, "posts_per_week": 1.1, "format_mix": { "carousel": 0.87, "video": 0.13 } }, "top_outliers": [ { "id": "tiktok:7616340301648563488", "platform": "tiktok", "url": "https://www.tiktok.com/@completerapp/photo/7616340301648563488", "handle": "completerapp", "posted_at": "2026-03-12", "views": 216733, "outlier_score": 344, "outlier_explained": "344x this account's usual views (median 630 over its last 29 posts)", "caption": "Discover my favorite iPhone apps for 2026 — handy tools to stay productive, org…", "hook": null, "format": "carousel", "cover_url": "https://searchtok.app/api/c/tiktok:7616340301648563488" }, "... 4 more" ], "link": "https://searchtok.app/app/accounts/tiktok%3A7597922150545507350" } ``` ### `analyze_brand` · POST /api/v1/brands/analyze · credits: 5 per brand (the brand plus each one compared) Analyse a brand's or creator's organic TikTok and Instagram from its website, handle or name, optionally side by side with up to 3 others (compare). Use it for: competitor research, 'how is X doing on TikTok', benchmarking your brand against others, a brand known only by its website. Don't use it for: one handle when you only need its profile and top posts (get_account, cheaper), finding brands in a niche (find_accounts), ads (organic posts only). A website is read for its TikTok and Instagram links; a name is looked up with TikTok account search (resolved_from says how, check it). Returns per account: followers, usual (median) views, outlier rate, posts per week, format mix, last-30-days vs previous-30-days posting and views, top outlier posts and quoted hooks of analysed posts. Every number is worked out in code; interpret them yourself. input: - `website` (string): The brand's site, e.g. "acme.com". Its TikTok and Instagram links are read from the page - `handle` (string): A TikTok or Instagram handle, e.g. "@acme" - `platform` ("tiktok" | "instagram"): The handle's platform. Default: tiktok - `name` (string): The brand's name, looked up with TikTok account search - `compare` (object[]): Up to 3 other brands, same fields, analysed side by side returns: - `brands` (object[], required): The brand first, then each one compared - `missed` (string[]) - `link` (string, required) ```sh curl -X POST https://searchtok.app/api/v1/brands/analyze \ -H "Authorization: Bearer $SEARCHTOK_API_KEY" \ -H "Content-Type: application/json" \ -d '{"handle":"@completerapp","compare":[{"handle":"@thehabittracker"}]}' ``` example response (real output, lists cut short): ```json { "brands": [ { "brand": "@completerapp", "resolved_from": "handle on tiktok", "accounts": [ { "id": "tiktok:7597922150545507350", "platform": "tiktok", "handle": "completerapp", "url": "https://www.tiktok.com/@completerapp", "followers": 667, "median_views": 674, "posts_sampled": 30, "outlier_rate": 0.17, "posts_per_week": 1.1, "format_mix": { "carousel": 0.87, "video": 0.13 }, "change_30d": { "posts_last_30d": 2, "posts_prev_30d": 0, "median_views_last_30d": 1165, "median_views_prev_30d": null }, "top_outliers": [ { "id": "tiktok:7616340301648563488", "platform": "tiktok", "url": "https://www.tiktok.com/@completerapp/photo/7616340301648563488", "handle": "completerapp", "posted_at": "2026-03-12", "views": 216733, "outlier_score": 344, "outlier_explained": "344x this account's usual views (median 630 over its last 29 posts)", "caption": "Discover my favorite iPhone apps for 2026 — handy tools to stay productive, org…", "hook": null, "format": "carousel" }, "... 2 more" ], "hooks": [] } ] }, "... 1 more" ], "link": "https://searchtok.app/app/brief?accounts=tiktok%3A7597922150545507350,tiktok%3A7285627514402653186" } ``` ### `analyze_post` · POST /api/v1/posts/analyze · credits: 6 per post Everything about a TikTok or Instagram post, by url or searchtok id. For several posts, pass them together as ids or urls (up to 8): they're analysed at the same time, much faster than one call each. Use it for: breaking down why a post worked, quoting its hook or script, checking a post from another searchtok result or a link the user pasted. Don't use it for: finding posts (search_content), posts like this one (find_similar), an account's overall numbers (get_account). Returns fresh views, likes, comments, shares and saves, the account's usual views, the outlier score with its explanation, the transcript and, once analysed, on-screen text, spoken and on-screen hooks, format and structure (analysis_status says whether that has run). Hooks are quoted word for word from the post, never written by a model. With ids or urls, a list (posts), one per post, in order. input: - `url` (string): The post's TikTok or Instagram url - `id` (string): A searchtok post id from another result, e.g. "tiktok:7301234567890" - `urls` (string[]): Several posts by url (up to 8 in all, with ids), analysed at the same time - `ids` (string[]): Several posts by searchtok id (up to 8 in all, with urls), analysed at the same time returns: ```sh curl -X POST https://searchtok.app/api/v1/posts/analyze \ -H "Authorization: Bearer $SEARCHTOK_API_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://www.tiktok.com/@spaceyflower/video/7377548155597950251"}' ``` example response (real output, lists cut short): ```json { "post": { "id": "tiktok:7377548155597950251", "platform": "tiktok", "url": "https://www.tiktok.com/@spaceyflower/video/7377548155597950251", "handle": "spaceyflower", "posted_at": "2024-06-07", "caption": "#habittracker #habittracker2024 #BulletJournal #bulletjournaling #yearinpixels #ratemyday", "hashtags": [ "habittracker", "... 5 more" ], "duration_s": 30.3, "format": "video", "sound": "original sound - neil.chapman1", "cover_url": "https://searchtok.app/api/c/tiktok:7377548155597950251" }, "metrics": { "views": 565678, "likes": 8281, "comments": 66, "shares": 729, "saves": 2545, "views_per_day": 658, "engagement_rate": 0.016 }, "baseline": { "median_views": 388, "posts_sampled": 20, "confidence": "ok" }, "outlier_score": 1457.9, "outlier_explained": "1457.9x this account's usual views (median 388 over its last 20 posts)", "transcript": { "status": "available", "text": "the reason why I wake up at 5:00am and I run every morning is it because I'm a Marathon runner or because I'm some kind of triathlete it's because I fucking hate running man I hate getting up early even more so I'm starting the day by building a non negotiable standard for myself which I've come to realize is the only way we can ever grow and improve when people say no one's coming to save you they're not talking about how no one's coming to help you reach your goals talking about how no one's coming to save you from yourself because without standards man we always lose direction and we always fall back to our lowest automatic systems binging procrastinating clinging to comfort", "cut": false }, "on_screen_text": null, "spoken_hook": null, "on_screen_hook": null, "format_label": null, "second_format_label": null, "structure": null, "seen": null, "is_ad": null, "country": "US", "analysis_status": "not_available", "link": "https://searchtok.app/app/posts/tiktok%3A7377548155597950251" } ``` ### `find_similar` · POST /api/v1/similar · 12 credits Find posts like a given TikTok or Instagram post (same topic or format), ranked by outlier score. Use it for: 'more like this', finding other creators doing the same format, checking whether a format works beyond one account. Don't use it for: a topic in words (search_content), one post's details (analyze_post). Searches the post's own hashtags and caption words on its platform, plus searchtok's index by meaning. Returns the same compact rows as search_content (the source post left out), with why_matched saying which seed found each. Paid posts are flagged paid. input: - `url` (string): The post's TikTok or Instagram url - `id` (string): A searchtok post id, e.g. "tiktok:7301234567890" - `limit` (integer): Default 10 returns: - `source` (object, required) - `rows` (object[], required) - `total_found` (number, required) - `searched` (string[], required) - `missed` (string[]) - `truncated` (number) - `note` (string) ```sh curl -X POST https://searchtok.app/api/v1/similar \ -H "Authorization: Bearer $SEARCHTOK_API_KEY" \ -H "Content-Type: application/json" \ -d '{"id":"tiktok:7377548155597950251","limit":10}' ``` example response (real output, lists cut short): ```json { "source": { "id": "tiktok:7377548155597950251", "url": "https://www.tiktok.com/@spaceyflower/video/7377548155597950251", "handle": "spaceyflower", "views": 565678 }, "rows": [ { "id": "tiktok:7400516824174988590", "platform": "tiktok", "url": "https://www.tiktok.com/@spaceyflower/video/7400516824174988590", "handle": "spaceyflower", "posted_at": "2024-08-07", "views": 1360488, "outlier_score": 2846.2, "outlier_explained": "2846.2x this account's usual views (median 478 over its last 27 posts)", "caption": "#habittracker2024 #habittracker #BulletJournal #bulletjournaling #yearinpixels…", "hook": null, "format": "video", "cover_url": "https://searchtok.app/api/c/tiktok:7400516824174988590", "why_matched": [ "#habittracker2024" ] }, "... 9 more" ], "total_found": 37, "searched": [ "tiktok #habittracker (20)", "... 1 more" ] } ``` ### `research` · POST /api/v1/research · credits: 50 quick, 150 deep Hand a whole question to searchtok's own research agent, which runs many searches and writes a checked report, every claim citing real posts. Use it when the user asks for a full research report, or wants a big question run in the background (depth deep, then get_research for the answer). Don't use it for: most questions. Calling search_content, find_accounts and analyze_post yourself is faster, cheaper and shows each step. It costs the most credits. Returns a summary, the posts it's based on (ids, outlier scores, quoted hooks) and a dashboard link. quick waits about 15s; deep returns at once with its search_id and link, and the answer is ready within about a minute (get_research). input: - `question` (string, required): The research question, in the user's words - `depth` ("quick" | "deep"): quick: under 15s, only already-analysed posts. deep: up to a minute, analyses new posts. Default quick returns: - `search_id` (string, required): This run's id in searchtok - `status` ("done" | "partial" | "running", required): partial: some data couldn't be fetched, see answer.gaps. running: a deep run, still working - `answer` (any): The checked answer: summary, patterns (each with claims citing post ids, with their numbers and quoted hooks) and example posts, what was searched, gaps. Not set while running - `posts` (any[]): More results, best first: id, url, handle, views, outlier_score, caption - `events_url` (string): Live progress and the answer as server-sent events (deep runs) - `link` (string, required): This run in the searchtok dashboard - `outage` (string): Set when the run has no answer because searchtok's side failed: its credits were given back - `note` (string) ```sh curl -X POST https://searchtok.app/api/v1/research \ -H "Authorization: Bearer $SEARCHTOK_API_KEY" \ -H "Content-Type: application/json" \ -d '{"question":"smile transformation"}' ``` example response (real output, lists cut short): ```json { "search_id": "00000000-0000-4000-8000-000000000001", "status": "done", "answer": { "summary": "Smile transformation videos work best as a clear before and after reveal, or as an emotional story about a person who hid their smile. Dentists also get reach from a rating question or a warning about bad veneers. No hooks were analysed, so none are quoted.", "groups": [ { "pattern": "Rate-this-smile reveal", "why": "A direct question invites comments and replays, and a clinic can film it with any patient case.", "claims": [ { "text": "A dentist's rate-this-smile veneers video reached 10.99M views, 226.7x the account's usual.", "post_ids": [ "tiktok:7598707763442748703" ], "numbers": [ { "post_id": "tiktok:7598707763442748703", "metric": "views", "value": 10989752 }, "... 1 more" ], "hooks": [] }, "... 1 more" ], "examples": [ "tiktok:7598707763442748703", "... 1 more" ] }, "... 3 more" ], "searched": [ "smile transformation", "... 1 more" ], "gaps": [ "TikTok only; Instagram not searched.", "... 3 more" ] }, "posts": [ { "id": "tiktok:7002262291064360197", "url": "https://www.tiktok.com/@austincosmeticdentistry/video/7002262291064360197", "handle": "austincosmeticdentistry", "views": 84080, "outlier_score": 81.8, "caption": "#beforeandafter #beforeafter #smile #smiledekedekho #makeover #transformation #…" }, "... 18 more" ], "link": "https://searchtok.app/app/searches/00000000-0000-4000-8000-000000000001" } ``` ### `get_research` · GET /api/v1/research/{search_id} · free Fetch a research run's answer by its search_id: a deep run that research started in the background, or any earlier run. Use it when research returned status running, or the user asks about an earlier research run. Don't use it for: search_content results (they come back in full straight away). Returns the status and, once it's done, the same answer research returns, with a dashboard link. Free. works without a plan. input: - `search_id` (string, required, in the path): The run's search_id, from POST /research returns: - `search_id` (string, required) - `question` (string, required) - `depth` ("quick" | "deep", required) - `status` ("queued" | "running" | "done" | "partial" | "failed" | "cancelled", required): running: not finished yet, ask again in 20 to 30 seconds. partial: some data couldn't be fetched, see answer.gaps - `answer` (any): The checked answer, the same shape research returns. Set once the run is done or partial - `posts` (any[]): More results, best first - `error` (string) - `link` (string, required): This run in the searchtok dashboard - `events_url` (string): Live progress as server-sent events, while it runs - `created_at` (string, required) - `finished_at` (string | null, required) - `truncated` (number) - `note` (string) ```sh curl https://searchtok.app/api/v1/research/$SEARCH_ID \ -H "Authorization: Bearer $SEARCHTOK_API_KEY" ``` example response (real output, lists cut short): ```json { "search_id": "00000000-0000-4000-8000-000000000001", "question": "smile transformation", "depth": "deep", "status": "done", "answer": { "summary": "Smile transformation videos work best as a clear before and after reveal, or as an emotional story about a person who hid their smile. Dentists also get reach from a rating question or a warning about bad veneers. No hooks were analysed, so none are quoted.", "groups": [ { "pattern": "Rate-this-smile reveal", "why": "A direct question invites comments and replays, and a clinic can film it with any patient case.", "claims": [ { "text": "A dentist's rate-this-smile veneers video reached 10.99M views, 226.7x the account's usual.", "post_ids": [ "tiktok:7598707763442748703" ], "numbers": [ { "post_id": "tiktok:7598707763442748703", "metric": "views", "value": 10989752 }, "... 1 more" ], "hooks": [] }, "... 1 more" ], "examples": [ "tiktok:7598707763442748703", "... 1 more" ] }, "... 3 more" ], "searched": [ "smile transformation", "... 1 more" ], "gaps": [ "TikTok only; Instagram not searched.", "... 3 more" ] }, "posts": [ { "id": "tiktok:7002262291064360197", "url": "https://www.tiktok.com/@austincosmeticdentistry/video/7002262291064360197", "handle": "austincosmeticdentistry", "views": 84080, "outlier_score": 81.8, "caption": "#beforeandafter #beforeafter #smile #smiledekedekho #makeover #transformation #…" }, "... 15 more" ], "link": "https://searchtok.app/app/searches/00000000-0000-4000-8000-000000000001", "created_at": "2026-10-16T16:11:50.841Z", "finished_at": "2026-10-16T16:11:50.844Z", "truncated": 3 } ``` ### `content_ideas` · POST /api/v1/ideas · 150 credits Content ideas for the user's profile, each built on one video in their niche that beat its account's usual views 2x+ and fits the user (plus up to 2 more posts in its format), with hooks quoted word for word and the beats to film. Each call finds new videos: a video shown in an earlier set for the profile is never shown again. Use it for: 'what should I post', a content plan, ideas in a given format ('talking head ideas') or from an account's posts. Don't use it for: finding posts to study (search_content). Ideas never invent hooks or formats: fewer new videos means fewer ideas, said in `note`. Takes about a minute. Returns the ideas with their post ids, outlier scores and quoted hooks, plus a dashboard link. input: - `profile_id` (string): Which saved profile (get_profile). Default: the first - `count` (integer): How many ideas, 1 to 8. Default 8 - `focus` (string): Narrow the ideas: a theme ("leg day"), a format ("talking head", "talking head fitness") or an account ("@acme": ideas from its posts) returns: - `set_id` (string, required) - `profile_id` (string | null, required) - `status` ("running" | "done" | "thin" | "empty", required): thin: fewer new videos than ideas asked for; empty: none (the credits go back). running: started in the background (the dashboard) - `focus` (string | null, required) - `ideas` (object[], required) - `note` (string | null, required): Said when there are fewer ideas than asked for, and why - `formats` (number, required): Formats among the fitting posts read - `scanned` (number, required): Posts checked - `searched` (string[], required) - `dropped` (number, required): Ideas the checks removed - `link` (string, required): These ideas in the searchtok dashboard ```sh curl -X POST https://searchtok.app/api/v1/ideas \ -H "Authorization: Bearer $SEARCHTOK_API_KEY" \ -H "Content-Type: application/json" \ -d '{}' ``` example response (real output, lists cut short): ```json { "set_id": "00000000-0000-4000-8000-000000000001", "profile_id": "00000000-0000-4000-8000-000000000001", "status": "thin", "focus": null, "ideas": [ { "id": "idea-1", "title": "Home tour with a personal story", "format": "day in the life / vlog", "format_id": "day-in-the-life", "fits": { "text": "A house tour told with a real story gives home buyers and sellers in your area a reason to trust you, which suits a residential realtor.", "phrase": "a residential realtor", "brief_field": "what" }, "why": [ "It opens by saying plainly what it is: a house tour, and the same words appear on screen.", "... 3 more" ], "posts": [ { "id": "tiktok:7649541016927735054", "platform": "tiktok", "url": "https://www.tiktok.com/@meredithboydd/video/7649541016927735054", "handle": "meredithboydd", "views": 3518505, "outlier_score": 2398.4, "outlier_explained": "2398.4x this account's usual views (median 1.5k over its last 20 posts)", "on_screen_hook": "let's do a house tour", "own": false }, "... 2 more" ], "proof": 7, "beats": [ { "text": "Open outside the home and say you are doing a house tour, with the same words on screen.", "post_id": "tiktok:7649541016927735054" }, "... 4 more" ], "hooks": [ { "text": "let's do a house tour", "post_id": "tiktok:7649541016927735054", "field": "on_screen" }, "... 4 more" ] } ], "note": "only 1 proven format in your niche right now. more niche words in your brief find more.", "formats": 2, "scanned": 207, "searched": [ "Instagram reels search \"realtor\" (10)", "... 13 more" ], "dropped": 1, "link": "https://searchtok.app/app/ideas?set=00000000-0000-4000-8000-000000000001" } ``` ### `build_profile` · POST /api/v1/profile/build · 10 credits Turn one link into the user's searchtok profile: an App Store or Play link, a website, a TikTok or Instagram handle, or a sentence about the business. Use it when the user has no profile yet (get_profile is empty) or adds a new app, business or client. Agencies keep one profile per client. Don't use it to research someone else's account (get_account, analyze_brand). It reads the listing, site or account. Returns (and saves) the brief: type (app, service, creator, ecommerce or agency), what it is, who it's for, 5 to 10 niche phrases in the words people use on TikTok, content goal, language, region, its own accounts, and 3 to 5 lookalike TikTok accounts that account search returned (never made up). Building the same link again updates that profile. input: - `input` (string, required): What the profile is for: an App Store or Google Play link, a website ("acme.com"), a TikTok or Instagram handle or profile url ("@acme", "instagram @acme"), or a sentence ("i run a dog grooming van in leeds") - `save` (boolean): Save it as one of the user's profiles. Default true. Building the same link again updates that profile returns: - `profile_id` (string | null, required): Pass it to content_ideas as profile_id. Null when save was false - `saved` ("created" | "updated" | "not_saved", required) - `source` (object, required) - `brief` (object, required) - `missed` (string[]): Pages, accounts or searches that couldn't be read - `link` (string, required) ```sh curl -X POST https://searchtok.app/api/v1/profile/build \ -H "Authorization: Bearer $SEARCHTOK_API_KEY" \ -H "Content-Type: application/json" \ -d '{"input":"opal.so"}' ``` example response (real output, lists cut short): ```json { "profile_id": "00000000-0000-4000-8000-000000000001", "saved": "created", "source": { "kind": "website", "url": "https://opal.so" }, "brief": { "type": "app", "name": "Opal", "what": "Opal is a screen time and focus app for iPhone, iPad, Mac and Android. It blocks apps you choose, runs a focus timer and gives a daily score for how your tech use fits your wellbeing.", "who_for": "People who spend too much time on their phone and want to scroll less and focus more.", "niche": [ "screen time app", "... 9 more" ], "content_goal": "downloads", "language": "en", "region": null, "own_accounts": [ { "platform": "tiktok", "handle": "opal", "url": "https://www.tiktok.com/@opal", "followers": 10931, "median_views": 4433, "posts_per_week": 0.3, "found": "linked from opal.so" }, "... 1 more" ], "lookalikes": [ { "platform": "tiktok", "handle": "bepresent.app", "display_name": "BePresent | Screen Time App", "url": "https://www.tiktok.com/@bepresent.app", "followers": 10124, "verified": null, "bio": null, "why": "a screen time app, by its name", "found_with": "screen time app" }, "... 4 more" ], "gaps": [ "The material doesn't say which country they mainly serve. Posts mention the US, UK, Japan and Australia.", "... 2 more" ] }, "link": "https://searchtok.app/app/profiles" } ``` ### `get_profile` · GET /api/v1/profile · free The user's saved searchtok profile(s): what they make content for (an app, a business, a creator...), their niche, audience and language. Use it first when a question depends on who the user is ('ideas for me', 'what should I post'), so searches match their niche and language. Don't use it to look up other accounts (get_account). Free. Returns each profile's id, type, name, link, language, region and brief. works without a plan. input: no input. returns: - `profiles` (object[], required) - `note` (string) - `link` (string, required) ```sh curl https://searchtok.app/api/v1/profile \ -H "Authorization: Bearer $SEARCHTOK_API_KEY" ``` example response (real output, lists cut short): ```json { "profiles": [ { "id": "00000000-0000-4000-8000-000000000001", "type": "app", "name": "example habit app (test data)", "source_url": "https://app.example", "language": "en", "region": null, "brief": { "note": "test data for this example", "niche": "habit tracker app" } } ], "link": "https://searchtok.app/app/profiles" } ``` ### `get_usage` · GET /api/v1/usage · free The user's searchtok plan, credits left, when they reset, and what each tool costs. Use it before a long run of calls (or after a no_credits error) so you can warn the user before they run out. Free and always works, even without a plan. Returns plan, plan_status, can_use_tools, credits_left, credits_used, resets_at, credit_costs and upgrade_url. works without a plan. input: no input. returns: - `plan` (string, required) - `plan_status` (string, required) - `can_use_tools` (boolean, required): False without an active plan or with no credits left - `credits_left` (number, required) - `credits_used` (number, required): Used this credit month; refunded calls don't count - `monthly_allowance` (number, required): The plan's monthly credits. 0 without a plan - `period_start` (string | null, required): When this credit month started (ISO date) - `resets_at` (string | null, required): When the plan renews and credits are topped up (ISO date). Null without a plan - `by_surface` (object[], required): This month's credits per door: dashboard, mcp, cli, api - `by_tool` (object[], required): This month's credits per tool, most first - `credit_costs` (object, required): What each tool costs - `upgrade_url` (string, required): Pick a plan or buy more credits - `usage_url` (string, required): The same numbers on the dashboard ```sh curl https://searchtok.app/api/v1/usage \ -H "Authorization: Bearer $SEARCHTOK_API_KEY" ``` example response (real output, lists cut short): ```json { "plan": "creator", "plan_status": "active", "can_use_tools": true, "credits_left": 1000, "credits_used": 0, "monthly_allowance": 1250, "period_start": "2026-10-16", "resets_at": "2026-11-03", "by_surface": [ { "surface": "dashboard", "credits": 0, "calls": 0 }, "... 3 more" ], "by_tool": [], "credit_costs": { "search_content": 20, "find_accounts": 15, "get_account": 3, "analyze_brand": "5 per brand", "analyze_post": 6, "find_similar": 12, "research": "50 quick, 150 deep", "content_ideas": 150, "build_profile": 10, "get_profile": 0, "get_usage": 0 }, "upgrade_url": "https://searchtok.app/app/billing", "usage_url": "https://searchtok.app/app/usage" } ``` ## errors - `bad_input` (400): the request doesn't match the input schema; message says what's wrong - `unsupported` (400): that platform or kind of link isn't supported by this endpoint - `unauthorized` (401): no key, or the key isn't valid or was revoked - `no_plan` (402): the account has no searchtok plan - `no_credits` (402): not enough credits left for this call - `not_allowed` (403): this key can't do that - `not_found` (404): the post, account, run or endpoint doesn't exist (or is private) - `unknown_tool` (404): no such endpoint - `budget_exceeded` (422): the call needed more data than one call may use; narrow it down - `rate_limited` (429): more than 60 requests in a minute with this key - `internal` (500): something broke on our side; credits are refunded - `not_available` (503): switched off for now (research has a kill switch and a daily cap) - `unavailable` (503): tiktok or instagram data couldn't be fetched right now; credits are refunded ## more - [api docs](https://searchtok.app/docs/api) - [openapi spec](https://searchtok.app/api/v1/openapi.json) - [llms.txt](https://searchtok.app/llms.txt)