Every change Wellknown observed on this MCP server, newest first, with what it was before and what it became. Tool-surface changes carry the definition diff. Nothing here is edited after the fact.
Changed the definition of "find_long_hooks", "find_packaging", "get_run" and 2 more
⟨147 unchanged words⟩ likes_per_1k, posted_at, duration_s, and the opening:hookintro (thelinesfirstthatminutestopasaitviewerwasleaving,said:verbatim)lines of n, t inhook_secondsseconds,introtext)(theandfirstintro_text,minutealwaysaswhole; and inside itwasthesaidhook (verbatim: the lines theofanalysisnmarks as the hook,tendinginon a sentence end, at most 25 seconds and 60 words,textwith hook_words, hook_start_s and hook_end_s, a sponsor read or greeting at the front left out; hook_seconds is hook_end_s). auto_captions says the opening has almostandnointro_text.punctuation. about_subject is yes or partly: a video the model judges not to be about the subject is dropped. The analysis: hook_type (the same list as find_hooks),mechanism (whyit holds, quoting the opening word for word), promise, open_loop,title_link (how thefirst lines pay off the title), beats (the opening in parts: from,to, beat, does), opens_with (cold_open, ⟨83 unchanged words⟩ are built, counted from the finds (hook_seconds, how long the hook runs, and hook_words as medians, beats with how manyopenings use each and thesecond it usually starts_at, and the sequence of beats mostof them share); and report.openers holds up ⟨174 unchanged words⟩ topic 3 to 500 characters; count 1 to2030 (default 8); language and country optional; ⟨126 unchanged words⟩ ). Cost: reserves up to 90 credits for 20 videos or fewer and 5 more for each video above that (140 at 30; less on a smaller balance, down to 25 ⟨73 unchanged words⟩ failed; progress.items holds the finds written so far as cards and progress.scan the videos it is looking at while it runs. Then keep_find, export_run. Same as ⟨10 unchanged words⟩
⟨76 unchanged words⟩ many long-form hooks to deliver, 1 to2030","maximum":2030,"minimum":1,"title":"Count"," ⟨432 unchanged words⟩
⟨158 unchanged words⟩ none), thumbnail_text_role (repeats_title, adds_to_title, contrasts_title,no_textno_text; counted from title_overlap, the share of the thumbnail's words that are also in the title, where that can be), thumbnail_subject, thumbnail_faces,thumbnail_emotion, thumbnail_colors, thumbnail_composition, thumbnail_devices, angle, gap ⟨12 unchanged words⟩ question that leaves), promise, steal_it andaudience.audience,analysedplus cut_on_phone (the title is longer than 60 characters, where a phone roughly cuts it; approximate) and on_subject (yes or partly: whether the look judged the package to be about your topic; a package judged not to be is dropped and the next one takes its slot; null on a link run). analysed is false when a packagecould not be lookedat;at (its look is asked twice, and the next candidate takes the slot if it fails twice); it
⟨123 unchanged words⟩ judges passed the clip) while it showsone.one, and on a Long form or Packaging run scan (the videos it is looking at: up to 60 tiles {v: YouTube video id, s: found|on_topic|off_topic|measured|beat| reading|ready|passed, x: multiple of the channel's usual or null}, oldest first, and lanes with found, on_topic, measured, beat, reading, ready; draw a tile from https://i.ytimg.com/vi/{v}/mqdefault.jpg; counts.measured and counts.beat are the numbers report.landscape ends on). Pass since with the last progress.updated_at to return ⟨326 unchanged words⟩
⟨91 unchanged words⟩ on its title template and its thumbnail's composition. Write my intro: with mode "intro" and video_brief (your own video in a few sentences, 10 to 600 characters; a long find only, a packaging find is 422; niche and n are not used) it writes YOUR intro in the real opening's structure. `written` then holds one object with a beat for each beat of the real opening, in order: beat, does, from_s, to_s, seconds and words_budget (the real opening's own timing, never the model's), say (the words to say, with {placeholders} for what only you know), words, placeholders, suggested_on_screen and suggested_broll (suggestions, never part of what is said); and seconds, timing (measured or estimated), kept_structure, placeholders and numbers_to_placeholders. The answer carries mode "intro" and video_brief, and niche is null. A number you did not write in video_brief becomes {number}. One that copies the real opening or runs over a word budget is 422 remix_not_possible. Returns {"find_id", "kind", "niche" ⟨141 unchanged words⟩
⟨72 unchanged words⟩ idempotency_key_reused","title":"Idempotency Key"},"mode":{"default":null,"description":"optional: \"openings\" (the default) writes openings or packages for a niche; \"intro\" is Write my intro: your own timed intro for the video you describe in video_brief, in this long-form opening's structure. Only a long find has an intro (a packaging find is 422); niche and n are not used in intro mode","enum"
⟨43 unchanged words⟩ get_run, get_hook or list_keeps); 404 hook_not_found otherwise. Leave mode out: "intro" (Write my intro) is remix_find on a long-form find, and is 422 here, before any charge. Returns {"hook_id", "niche", "n ⟨136 unchanged words⟩
⟨71 unchanged words⟩ idempotency_key_reused","title":"Idempotency Key"},"mode":{"default":null,"description":"leave out (or \"openings\"). Write my intro works from a long-form find (POST /v1/finds/{find_id}/remix), so \"intro\" is refused here with 422","enum":["openings",null],"title":"Mode","type":["string","null"]},"n":{"default":null,"description":"how ⟨46 unchanged words⟩
Added "get_remix" and "list_remixes"; changed the definition of "find_hooks", "get_conversation", "get_run" and 2 more (41 tools before, 43 now)
⟨63 unchanged words⟩ player_url (9:16 iframe). Talking headsand bonusonly (since 2026-09-29): `hooks` holds only clips ⟨6 unchanged words⟩ talking (talking head, podcast, interview).TheArunclipobjectwherealsonobodyhastalks`bonus`:isits best clipsdropped;
Added "clear_find_decision", "find_long_hooks", "find_packaging" and 5 more; changed the definition of "chat" and "list_runs" (33 tools before, 41 now)
⟨74 unchanged words⟩ last week") set the run's count andposted_within_days.posted_within_days; the count argument sets the deck size when the message states none. Asked to make one of the conversation's hooks ⟨194 unchanged words⟩
⟨19 unchanged words⟩ ":null,"title":"Conversation Id"},"count":{"anyOf":[{"maximum":30,"minimum":1,"type":"integer"},{"type":"null"}],"default":null,"description":"how many hooks a run this turn starts should deliver, 1 to 30; a size typed into the message (\"15 hooks\") wins over it, and it wins over the turn's own guess; null or omitted lets the turn decide. Fewer hooks is a faster, cheaper run","title":"Count"},"country":{"anyOf":[{"type":"string ⟨552 unchanged words⟩
Changed the definition of "get_run"
⟨230 unchanged words⟩ or country and on older hooks). Since 2026-10-04 the run carries understood (what the request was read to ask: subject, language, country, core, audience, neighbours, styles, style_other, wants; null until read) and, on a run that asked for a kind of opening ("controversial hooks", "storytime"), every hook carries style_match (true when it opens that way; null otherwise). Since 2026-10-01 every hook carries the virality v2 fields: ⟨176 unchanged words⟩
Changed the definition of "find_hooks" and "get_run"
⟨314 unchanged words⟩ no language or country and on older hooks. Analysis (since 2026-10-02): every hook carries hook_type (curiosity_gap, contrarian_claim, specific_number, identity_callout, stakes_threat, promise_payoff, pattern_interrupt, story_open, question, direct_command, other with hook_type_other), mechanism (why the opening stops the scroll, quoting the clip; why_it_travelled holds the same text), visual_hook, audience, template and steal_it; null on older hooks. Virality v2 (since 2026-10-01): the deck is ranked by virality (0 to 1,withinsince 2026-10-02 on one fixed scale, thesame clip scoring the same in every run), built from outlier (views over the ⟨583 unchanged words⟩
Added "remix_hook"; changed the definition of "chat", "find_hooks" and "get_run" (32 tools before, 33 now)
⟨52 unchanged words⟩ 0 to 4 follow-ups, [] for research. A deck size typed into the message ("..., 15 hooks") and a recency phrase ("this month", "last week") set the run's count and posted_within_days. Asked to make one of the conversation's hooks yours for a niche ("make hook 2 mine for dentists"), the turn writes lines from that hook's structure (1 credit, like remix_hook) and answers them labelled, the full result under "remix". language and country select the search locale: omitted ⟨154 unchanged words⟩
⟨358 unchanged words⟩ "title":"Message","type":"string"},"posted_within_days":{"default":null,"description":"since 2026-10-01: optional, how recent the clips must be, as days: 7, 30, 90, 365. The search asks the platforms for that window and the floor for a clip under 7 days old uses views per day (10,000 a day passes); null or left out means any time. A clip's `posted_at` and `age_days` say what the run found. For chat (the recency chip): omitted keeps the conversation's current setting; 7, 30, 90 or 365 sets it for this and later turns and wins over a recency phrase in the message; an explicit null on REST, or 0 on either door, clears it back to any time.","enum":[7,30,90,365,0,null],"title":"Posted Within Days","type":["integer","null"]},"seed_hook_ids":{"default":null,"description":"since 2026-10-01: optional, up to 10 hook ids from your runs (a kept hook from GET /v1/keeps, or any hook you can read): \"find more like these\". The engine reads their lines, topics, creators and languages to steer the search phrases and the creators it expands; a seed is never delivered again. An id that is not one of your hooks is 422.","items":{"format":"uuid","type":"string"},"maxItems":10,"title":"Seed Hook Ids","type":["array","null"],"uniqueItems":true}
Added "showcase"; changed the definition of "find_hooks" and "get_run" (31 tools before, 32 now)
⟨244 unchanged words⟩ never a banner. onscreen_text keeps all text read. Match (since 2026-09-30, runs with a language or country): every hook has match, "exact" when the clip is about the subject itself or "close" when it is about a neighbouring subject with the same intent (a close match, delivered after every exact match; label it as one); null on runs with no language or country and on older hooks. Arguments: topic is 3 to 500 characters, ⟨427 unchanged words⟩
⟨61 unchanged words⟩ updated_at, plus counts once the research reports themand, preview (hooks written so far) while a running run holds
Changed the definition of "find_hooks" and "get_run"
⟨158 unchanged words⟩ fills hooks with clips that do not talk. Where it comes from (since 2026-09-30): every hook has creator_country (ISO alpha-2 of the creator's TikTok account; always null on Instagram, which states none), place (the place the creator tagged, or null) and place_country (that place's country when the platform states it). Never guessed. Banner (since 2026-09-30): every hook has banner, the title-style text the creator placed on the video to hook the viewer, verbatim (at most 200 characters), or null when it has none; subtitles are never a banner. onscreen_text keeps all text read. Arguments: topic is 3 to 500 characters, ⟨427 unchanged words⟩
⟨132 unchanged words⟩ and section ("main" or "bonus"), and creator_country (TikTok only), place and place_country (null when unknown), and banner (the title placed on the video, null when none)
Changed the definition of "find_hooks" and "get_run"
⟨51 unchanged words⟩ a vertical player_url (9:16 iframe). Talking heads and bonus (since 2026-09-29): `hooks` holds only clips where a person on camera is talking (talking head, podcast, interview). The run object also has `bonus`: its best clips without talking (text on screen, voiceover, b-roll with captions), in the same hook shape, up to count and at least half of it when the run read that many. Bonus is free. Every hook carries format (talking_head, podcast, interview, voiceover, text_on_screen, broll, skit, other; null on older hooks) and section ("main" or "bonus"). A run that finds fewer talking heads than count says so in outcome; it never fills hooks with clips that do not talk. Arguments: topic is 3 to 500 characters, ⟨427 unchanged words⟩
⟨99 unchanged words⟩ with a timezone (422 invalid_request otherwise).
Changed the definition of "chat", "get_conversation" and "list_conversations"
⟨52 unchanged words⟩ 0 to 4 follow-ups, [] for research. language and country select the search locale: omitted or null keeps the current conversation setting; a value sets it for this and later turns; an empty string clears it to any language / any country. The picker wins over the message's locale; replies stay in the language the person writes in. Invalid locale: 422 invalid_request. message is 1 to 4000 characters. Pass conversation_id ⟨99 unchanged words⟩
⟨19 unchanged words⟩ ":null,"title":"Conversation Id"},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"description":"optional: only clips from creators in this country, as an assigned ISO 3166-1 alpha-2 code: MA, EG, BR, US (UK is read as GB). For a region that is not a country, send its country's code and name the regional variety in language: Quebec is country CA with language \"Quebec French\", Flanders BE with \"Flemish\", Catalonia ES with \"Catalan\". The country places the TikTok search there; the language judge does the regional filtering, and Instagram reports no country. A country alone does not restrict the language. For chat, omitted or null keeps the conversation's current setting; a value sets it for this and later turns; the empty string clears it back to any language / any country.","examples":["MA"],"pattern":"^[A-Za-z]{2}$","title":"Country"},"idempotency_key":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"description":"retry safely: a turn that started a run with this key within 24 hours answers that run again, no model call, nothing reserved","title":"Idempotency Key"},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"description":"optional, up to 60 characters: only clips spoken or written in this language or dialect, in words or as a tag: \"Moroccan Darija\", \"ar-MA\", \"Egyptian Arabic\", \"Brazilian Portuguese\". Any language works, including a regional variety (\"Quebec French\", \"Flemish\", \"Catalan\") and a mixed, code-switched one (\"Hinglish\", \"Taglish\", \"Moroccan Darija with French\"), or two joined by \"or\" (\"Tagalog or Taglish\"). The value is handed to the judges exactly as written; how well they honour a mixed or either-of value is not measured yet. Saying it inside topic works too; this field wins when both are given. For chat, omitted or null keeps the conversation's current setting; a value sets it for this and later turns; the empty string clears it back to any language / any country.","examples":["Moroccan Darija"],"maxLength":60,"title":"Language"},"
Added "admin_adjust_credits", "admin_list_accounts", "admin_list_actions" and 2 more (26 tools before, 31 now)
Owner only: add or remove an account's credits, as POST /v1/admin/accounts/{account_id}/credits. A change that would take the balance below zero is 422 credits_below_zero and nothing changes; an unknown account is 404 account_not_found. Every change is audited (admin_list_actions). Returns {account_id, credits, delta}.
Owner only: every account, newest first, as GET /v1/admin/accounts: {total, accounts: [{id, email, name, label, credits, created_at, last_seen_at, runs, hooks, charged_total, is_admin, admitted_by, keys_active}]}. Free.
Owner only: the audit of password sign-ins and credit changes, newest first, as GET /v1/admin/actions: {actions: [{id, action, actor_email, target_account_id, detail, created_at}]}. Free.
Owner only: every account's research runs, newest first, as GET /v1/admin/runs: {runs: [{run_id, account_id, email, topic, language, country, status, requested, hooks, reserved, charged, failure_kind, error, created_at, finished_at}]}. Free.
Added "approve_access_request", "create_access_code", "list_access_codes" and 4 more; changed the definition of "balance", "create_account", "embed_hook" and 1 more (19 tools before, 26 now)
⟨19 unchanged words⟩ created_at, and that key's scopes, credit_limit andexpires_at.expires_at, and user (the person signed in with Google, or null). Same shape as GET /v1/me. A run needs ⟨22 unchanged words⟩
Create an account and get an API key with free credits.NoNeedshuman
Certificate recorded, valid to 2026-12-21
Authorization not required
Changed the definition of "find_hooks" and "get_run"
⟨371 unchanged words⟩ the rest is refunded. A failed run costsnothing.nothing, and so does a run that finds no hooks, within a per-account allowance (llms.txt has the numbers; its outcome says which applied). Timing and next step: a fresh run takes about 2 to 3minutes.minutes, up to about 5 when the language is a dialect. Call get_run withwait_seconds=50 repeatedly until status is"done" or "failed", which is usually 3or 4calls.calls (up to 6 on a dialect request). Watch progress.stage and progress.message meanwhile. Needs a key ⟨32 unchanged words⟩
Unknown → Live
First tool surface recorded: 19 tools
https://api.hookdetector.com/mcp (mcp_streamable_http) — from mcp_registry, with the record
Showing the latest 18 events. The API returns up to 500 and filters by kind: ?kind=tool_surface_changed
⟨75 unchanged words⟩ "how many packages to deliver, 1 to2430","maximum":2430,"minimum":1,"title":"Count"," ⟨399 unchanged words⟩
One chat conversation:everyitsmessagenewest messages in order (role, content, run_id,created_at), a page of `limit`, with earlier_cursor for the older ones (null when the page reaches the first message), andeverytherunrunsitthoseholdsmessages hold, with their hooks,plus nullable language and country searchpicker settings. Same shape as GET /v1/conversations/{conversation_id} ⟨5 unchanged words⟩ 422 for an id that is not aUUID.UUID or a cursor this API did not issue.
⟨8 unchanged words⟩ ":null,"title":"Api Key"},"before":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"description":"earlier_cursor from the previous page; leave out for the newest messages","title":"Before"},"conversation_id":{"title":"Conversation Id","type":"string"},"limit":{"default":100,"description":"messages per page, newest page first","maximum":500,"minimum":1,"title":"Limit","type":"integer"}},"required":["conversation_id"],"title ⟨3 unchanged words⟩
⟨97 unchanged words⟩ each key, platform, creator, views, statefound|reading|talking|bonus|passedfound|reading|talking|passed, thumb_url, a JPEG any <img> can load ⟨41 unchanged words⟩ (422 invalid_request otherwise). `hooks` is themaindeck (a person talking); `bonus`holds text-on-screen clips without talking, free, sameishookalwaysshape,[]on older(removedruns.2026-10-08). Every hook has format andsection ("main" oralways "bonusmain"), and creator_country (TikTok only), place ⟨282 unchanged words⟩
⟨171 unchanged words⟩ when it does not answer (503 model_unavailable) or when nothing it wrote keeps the rules or it declines the content (422 remix_not_possible, the reason in the message; another try or another niche may work). 402 insufficient_credits at a balance of 0; ⟨6 unchanged words⟩ hour (429 rate_limited). Needs the research permission. The answer carries remix_id: list_remixes and get_remix read it back, free. Same as POST /v1/finds/{find_id}/remix.
⟨30 unchanged words⟩ list_kept_finds)","title":"Find Id"},"idempotency_key":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"description":"1 to 255 visible ASCII characters; the same key and request within 24 hours answer the saved remix and charge nothing; the same key with another request is 422 idempotency_key_reused","title":"Idempotency Key"},"n":{"default":null,"description":"how ⟨45 unchanged words⟩
⟨120 unchanged words⟩ when it does not answer (503 model_unavailable) or when nothing it wrote keeps the rules or it declines the content (422 remix_not_possible, the reason in the message; another try or another niche may work). 402 insufficient_credits at a balance of 0; limited per account per hour (429 rate_limited). Needs the research permission. The answer carries remix_id: list_remixes and get_remix read it back, free. Same as POST /v1/hooks/{hook_id}/remix.
⟨29 unchanged words⟩ list_keeps)","title":"Hook Id"},"idempotency_key":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"description":"1 to 255 visible ASCII characters; the same key and request within 24 hours answer the saved remix and charge nothing; the same key with another request is 422 idempotency_key_reused","title":"Idempotency Key"},"n":{"default":null,"description":"how ⟨46 unchanged words⟩
One remix you paid for (from remix_hook, remix_find or a chat turn), as its answer held it. Free. 404 remix_not_found for one that is not yours. Same as GET /v1/remixes/{remix_id}.
The remixes you paid for from one hook (hook_id) or one find (find_id), newest first, each as its answer held it, with remix_id and created_at. Returns {"remixes": [...]}. Free. 404 hook_not_found or find_not_found for one that is not yours. Same as GET /v1/hooks/{hook_id}/remixes and GET /v1/finds/{find_id}/remixes.
Your research runs, newest first, without theirhooksresults, limit 1 to 100 (default 20) per page. Each run says its kind: "short" (find_hooks), "long" (find_long_hooks) or "packaging" (find_packaging); pass kind to list only one. Returns {"runs": [...],"next_cursor"}: pass next_cursor back as cursor forthe next page; it is null on thelast. Same shape as GET /v1/runs. Next step: open onewith get_run, or download it withexport_run. Free. Errors: 422 invalid_cursor for acursor this API did notissue.issue, 422 for a kind that is not one of the three.
⟨18 unchanged words⟩ "default":null,"title":"Cursor"},"kind":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"description":"only runs of this kind: short, long or packaging; leave out for every kind","title":"Kind"},"limit":{"default":20,"description":"page ⟨16 unchanged words⟩
Undo keep_find: the video goes back to having no verdict, on every find of it, and leaves list_kept_finds. Returns {"find_id", "verdict": null}. Clearing a find with no decision is the same success, so a retry is safe. Same as DELETE /v1/finds/{find_id}/decision. Free.
Find long-form hooks: how the YouTube videos that beat their own channel open, on a topic. Long-form YouTube only, never Shorts, TikTok or Instagram (for those: find_hooks). What it does: searches YouTube for long-form videos on the topic, keeps only the outliers (a video whose views are at least min_outlier times the median views of its own channel's recent uploads; 2 by default) posted in 2023 or later (nothing older is ever delivered), reads the first intro_seconds (60 by default) of each one's transcript, and says why that opening holds a viewer. What comes b…
Find packaging: the titles and thumbnails that earned the click on a topic, from long-form YouTube videos that beat their own channel. What it does: runs the same outlier search as find_long_hooks (long-form YouTube, 2023 or later, views at least min_outlier times the channel's usual), then looks at each video's thumbnail beside its title and says what the pair is doing. What comes back: the run's `finds`, ranked. Each is a real package, as YouTube shows it: title (verbatim) and thumbnail_url (the size YouTube really served, the picture the analysis looked at; th…
One result of a long or packaging run with every field: the video, its numbers and the opening or the package analysis. find_id comes from a run's finds (get_run) or from list_kept_finds. Same shape as GET /v1/finds/{find_id}. Free. Errors: 404 find_not_found, 422 for an id that is not a UUID.
Keep or reject a find (a long-form hook or a package): verdict is "keep" (default) or "reject", and the last verdict wins. The verdict is the video's on that tab: every find of the same video you have there carries it, so a later run that finds the video again shows it already kept. Kept videos come back from list_kept_finds, across all runs. Returns {"find_id", "verdict"}. Same as POST /v1/finds/{find_id}/decision. Free. Errors: 404 find_not_found.
The index: every video your long and packaging runs found, across all runs, one entry a video on each tab (its newest read, with the keep or reject made on the video). Each is a full find object: the title and thumbnail_url as YouTube shows them, views, channel_median_views, outlier (views over the channel's usual) and outlier_score (0 to 100 on one fixed scale, so entries from different runs rank together), plus the analysis its run wrote. q (at most 120 characters) looks in the title, the channel, the hook as it was said, and what the thumbnail prints and shows, …
The videos you kept with keep_find (long-form hooks and packages), newest decision first: one entry a video on each tab across all runs, or the kept finds of one run_id, limit 1 to 100 (default 100) per page. Returns {"kept": [...], "count": n on this page, "next_cursor"} with full find objects. Same shape as GET /v1/finds/keeps. Free. Errors: 422 for a bad kind, run_id or cursor.
Make it mine, for a long-form opening or a package: write up to n (1 to 5, default 3) new ones for your niche that keep one real find's structure. From a long find, `written` holds openings: hook (what the creator says first), then (what the next half minute has to deliver) and kept_structure, built on the find's template and its order of beats. From a packaging find it holds packages: title, thumbnail (what to show and how to frame it), thumbnail_text (the words to print, or null) and kept_structure, built on its title template and its thumbnail's composition. Re…
⟨240 unchanged words⟩ hook carries the virality v2 fields: virality (the score that0rankstothe1deck,on0onetofixed1scalewithinsincethe run2026-10-02),virality_why, outlier, outlier_basis, creator_median_views, posted_at, age_days ⟨4 unchanged words⟩ , own_sound, hashtags (null on older hooks);, and since 2026-10-02 the analysis: hook_type, hook_type_other, mechanism, visual_hook, audience, template, steal_it; the run echoes posted_within_days andseed_hook_ids, and once done carries patterns: up ⟨9 unchanged words⟩ name, template, why, hook_ids and count, grouped by hook_type and computedafterbeforedeliverythe run is done, in thedeck's language (nulluntilonthe callolderlandsruns andonwhenoldernothingrunsis shared). Hooks include transcript_kind: speech, music, ⟨82 unchanged words⟩
Start hook research on a topic across TikTok, Instagram andInstagram.YouTube Shorts (Shorts only, never long-form YouTube; platform "youtube"). What it does: searches both platforms, reads ⟨287 unchanged words⟩ no language or country and on older hooks. Virality v2 (since 2026-10-01): the deck is ranked by virality (0 to 1 within the run), built from outlier (views over the creator's usual views per video, creator_median_views, with outlier_basis saying how it was measured), freshness (posted_at, age_days, views_per_day), travel (shares and saves per 1,000 views), own_sound and sound_reuse, and a paid penalty; virality_why names the strongest signal in one line. Every hook also carries duration_s and hashtags. Null on older hooks. posted_within_days (7, 30, 90 or 365) keeps the search to recent clips (a clip under 7 days old passes the floor at 10,000 views a day). seed_hook_ids (up to 10 of your hooks, for example kept ones) means "find more like these": their lines, topics, creators and languages steer the search, and a seed is never delivered again. A done run carries patterns: up to 5 hook structures winning in the deck (name, template, why, hook_ids, count), computed after delivery; null until then. remix_hook writes lines from one hook's structure for your niche (1 credit). Arguments: topic is 3 to 500 characters, ⟨427 unchanged words⟩
⟨315 unchanged words⟩ "maxLength":60,"title":"Language"},"posted_within_days":{"default":null,"description":"since 2026-10-01: optional, how recent the clips must be, as days: 7, 30, 90, 365. The search asks the platforms for that window and the floor for a clip under 7 days old uses views per day (10,000 a day passes); null or left out means any time. A clip's `posted_at` and `age_days` say what the run found.","enum":[7,30,90,365,null],"examples":[30],"title":"Posted Within Days","type":["integer","null"]},"reuse":{"default":true,"description":"start ⟨6 unchanged words⟩ ,"title":"Reuse","type":"boolean"},"seed_hook_ids":{"default":null,"description":"since 2026-10-01: optional, up to 10 hook ids from your runs (a kept hook from GET /v1/keeps, or any hook you can read): \"find more like these\". The engine reads their lines, topics, creators and languages to steer the search phrases and the creators it expands; a seed is never delivered again. An id that is not one of your hooks is 422.","items":{"format":"uuid","type":"string"},"maxItems":10,"title":"Seed Hook Ids","type":["array","null"],"uniqueItems":true},"topic":{"description":"what the ⟨81 unchanged words⟩
⟨229 unchanged words⟩ language or country and on older hooks). Since 2026-10-01 every hook carries the virality v2 fields: virality (the score that ranks the deck, 0 to 1 within the run), virality_why, outlier, outlier_basis, creator_median_views, posted_at, age_days, views_per_day, duration_s, paid, sound_reuse, own_sound, hashtags (null on older hooks); the run echoes posted_within_days and seed_hook_ids, and once done carries patterns: up to 5 hook structures winning in this deck, each name, template, why, hook_ids and count, computed after delivery in the deck's language (null until the call lands and on older runs). Hooks include transcript_kind: speech, music, none, ⟨81 unchanged words⟩
Make it mine: write up to n new opening lines (1 to 5, default 3) for your niche that keep a real hook's structure, the device that made it travel, with the subject swapped for yours. hook_id is one of your hooks (from get_run, get_hook or list_keeps); 404 hook_not_found otherwise. Returns {"hook_id", "niche", "n", "written": [{"line", "kept_structure"}], "label", "credits_charged": 1, "credits"}. label is always "Written by Hook Detector from a real hook's structure": these are written lines, never found hooks; show the label with them, never present one as a…
Real hooks this service has delivered, for a wait screen: talking heads only, each hook_id, still_url (a JPEG, no key needed), platform, creator, views and line (the banner on the video, else its opening line). The most viewed first, one per creator, only what is already public on TikTok or Instagram: never a topic, an account or a run. No key needed. Cached 10 minutes. Free. REST: GET /v1/showcase?n=12.
⟨11 unchanged words⟩ and every run it holds, with theirhooks.hooks, plus nullable language and country search picker settings. Same shape as GET /v1/conversations/{conversation_id}. Free. ⟨12 unchanged words⟩
⟨13 unchanged words⟩ per page: conversation_id, title, created_at, updated_at,andlanguage, country and messages (the count). Nullable language/country are the saved search picker settings. Returns {"conversations": [...], "next_cursor ⟨17 unchanged words⟩
Owner only: the whole service at a glance, as GET /v1/admin/overview. Counts of accounts and people; runs (total, last_24h, last_7d, failed_24h, in_flight); hooks; credits (charged_24h, charged_7d, charged_total, balance_total); access (pending_requests, active_codes, redemptions); search (credits_left, low, accepting_runs; null when not read yet); and the running commit. Needs the admin scope and an admin account (403 admin_required otherwise). Free.
{"properties":{"access_code":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Access Code"},"label":{"anyOf":[{"maxLength":120, ⟨12 unchanged words⟩
⟨26 unchanged words⟩ embedding or the clip is a photo post found before 2026-09-26 (then show thumbnail, which falls back tothe hook's still_url,and the transcript). For a plain vertical ⟨7 unchanged words⟩
⟨8 unchanged words⟩ transcript, watch_url and the vertical player_url (null only for an unusable id or a photo post found before 2026-09-26; runs deliver single videos only). hook_id comes from get_run. Free.
Owner only: approve an access request. Makes a one-use code, marks the request approved and emails the code. The code is in this result once, with emailed true or false; when false, send it yourself or approve again (the unused code is revoked and a new one sent). 409 request_already_decided once its code was used. Free.
Owner only: make an access code to hand out. max_uses 1 to 100000 (default 1), expires_in_days 1 to 365 (optional), note your own. email binds it to one person: then only Google sign-in with that verified email redeems it, never the API or MCP; leave it out for a code that works on every door. The code is in this result only. Free.
Owner only: every access code, newest first, with code_id, code_prefix, email, note, source, max_uses, uses, expires_at, revoked_at, created_at and usable. Never the code itself. Free.
Owner only: every access request, newest first, optionally only one status (pending, approved, rejected). Each has request_id, email, note, status, code_id, code_prefix, created_at and decided_at. Never a code. Needs the admin scope and an admin account (403 admin_required otherwise). Free.
Owner only: reject a pending access request. Nothing is emailed. An approved or rejected request is 409 request_already_decided. Free.
Ask for the beta access code a new account needs (create_account's access_code). email is where the code is sent; note is optional (who you are, up to 500 characters). The owner approves the request and the code is emailed at once; it works once, in any agent or on the web. Returns status pending (waiting for approval) or emailed (sent now). No key needed. Limited to 5 per address per hour. Free.
Owner only: revoke an access code (code_id from list_access_codes). It admits nobody from now on; accounts it already made keep working. 404 access_code_not_found for an unknown id. Free.
⟨123 unchanged words⟩ run belongs to (null outside one). A done run with fewer hooks than asked for, or none, has outcome: why in one plain paragraph, what to try next, and whether it was free (stats.rejections holds the counts); a full run's outcome is null, or one sentence when some hooks are under the view floor because the country filter set clips aside. Free: reading a run costs no credits.