MCPサーバー

Hook Detector

com.hookdetector/hookdetector

このMCPでできること

Researches effective TikTok and Instagram hooks, provides transcripts and explanations, and supports saving, exporting and embedding selected hooks.

admin_adjust_credits
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}.
入力スキーマ
{'type': 'object', 'title': 'admin_adjust_creditsArguments', 'properties': {'note': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Note', 'default': None, 'description': 'why, for the audit: 1 to 200 characters, required'}, 'delta': {'anyOf': [{'type': 'integer', 'maximum': 1000000, 'minimum': -1000000}, {'type': 'null'}], 'title': 'Delta', 'default': None, 'description': 'credits to add (positive) or remove (negative), -1000000 to 1000000, not zero'}, 'api_key': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Api Key', 'default': None}, 'account_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Account Id', 'default': None, 'description': 'the id from admin_list_accounts'}}}
出力スキーマ
{'type': 'object', 'title': 'admin_adjust_creditsOutput', 'required': ['result'], 'properties': {'result': {'type': 'string', 'title': 'Result'}}}
admin_list_accounts
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.
入力スキーマ
{'type': 'object', 'title': 'admin_list_accountsArguments', 'properties': {'q': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Q', 'default': None, 'description': 'up to 200 characters: part of an email or label (any case), or the start of an id'}, 'limit': {'anyOf': [{'type': 'integer', 'maximum': 200, 'minimum': 1}, {'type': 'null'}], 'title': 'Limit', 'default': None, 'description': 'page size, 1 to 200 (default 50)'}, 'offset': {'anyOf': [{'type': 'integer', 'maximum': 1000000, 'minimum': 0}, {'type': 'null'}], 'title': 'Offset', 'default': None, 'description': 'how many to skip'}, 'api_key': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Api Key', 'default': None}}}
出力スキーマ
{'type': 'object', 'title': 'admin_list_accountsOutput', 'required': ['result'], 'properties': {'result': {'type': 'string', 'title': 'Result'}}}
admin_list_actions
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.
入力スキーマ
{'type': 'object', 'title': 'admin_list_actionsArguments', 'properties': {'limit': {'anyOf': [{'type': 'integer', 'maximum': 200, 'minimum': 1}, {'type': 'null'}], 'title': 'Limit', 'default': None, 'description': 'page size, 1 to 200 (default 50)'}, 'api_key': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Api Key', 'default': None}}}
出力スキーマ
{'type': 'object', 'title': 'admin_list_actionsOutput', 'required': ['result'], 'properties': {'result': {'type': 'string', 'title': 'Result'}}}
admin_list_runs
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.
入力スキーマ
{'type': 'object', 'title': 'admin_list_runsArguments', 'properties': {'limit': {'anyOf': [{'type': 'integer', 'maximum': 200, 'minimum': 1}, {'type': 'null'}], 'title': 'Limit', 'default': None, 'description': 'page size, 1 to 200 (default 50)'}, 'status': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Status', 'default': None, 'description': 'only runs with this status: queued, running, done, failed'}, 'api_key': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Api Key', 'default': None}, 'account_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Account Id', 'default': None, 'description': "only this account's"}}}
出力スキーマ
{'type': 'object', 'title': 'admin_list_runsOutput', 'required': ['result'], 'properties': {'result': {'type': 'string', 'title': 'Result'}}}
admin_overview
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.
入力スキーマ
{'type': 'object', 'title': 'admin_overviewArguments', 'properties': {'api_key': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Api Key', 'default': None}}}
出力スキーマ
{'type': 'object', 'title': 'admin_overviewOutput', 'required': ['result'], 'properties': {'result': {'type': 'string', 'title': 'Result'}}}
approve_access_request
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.
入力スキーマ
{'type': 'object', 'title': 'approve_access_requestArguments', 'properties': {'api_key': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Api Key', 'default': None}, 'request_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Request Id', 'default': None}}}
出力スキーマ
{'type': 'object', 'title': 'approve_access_requestOutput', 'required': ['result'], 'properties': {'result': {'type': 'string', 'title': 'Result'}}}
balance
Your account and remaining credits right now: account_id, credits, key_prefix and key_id of the key making this call, label, created_at, and that key's scopes, credit_limit and expires_at, and user (the person signed in with Google, or null). Same shape as GET /v1/me. A run needs at least 40 credits and reserves up to 200. For spend, credits held by in-flight runs and daily history, call usage. Free.
入力スキーマ
{'type': 'object', 'title': 'balanceArguments', 'properties': {'api_key': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Api Key', 'default': None}}}
出力スキーマ
{'type': 'object', 'title': 'balanceOutput', 'required': ['result'], 'properties': {'result': {'type': 'string', 'title': 'Result'}}}
chat
One conversational turn, for an agent relaying a person's words. It either asks ONE clarifying question (action "clarify"), answers about delivered hooks or the tool (action "answer", run null, no credits), or starts a research run (action "research", with the started run under "run"), exactly like POST /v1/chat. Every response has suggestions: 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 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 from an earlier turn to keep the context; leave it out to start a conversation. Cost: a turn itself is free but is a model call, so turns are limited per account per hour (429 rate_limited); a turn that starts a run reserves credits like find_hooks. Next step: on "research", call get_run with run.run_id and wait_seconds=50. Errors: 503 model_unavailable when the model is down (nothing charged, try again), 402 insufficient_credits. For direct research without the question step, call find_hooks instead. Needs the write permission, and research for a turn that starts a run (403 insufficient_scope, nothing reserved).
入力スキーマ
{'type': 'object', 'title': 'chatArguments', 'required': ['message'], 'properties': {'api_key': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Api Key', 'default': None}, 'country': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Country', 'default': None, 'pattern': '^[A-Za-z]{2}$', 'examples': ['MA'], '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.'}, 'message': {'type': 'string', 'title': 'Message', 'maxLength': 4000, 'minLength': 1}, 'language': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Language', 'default': None, 'examples': ['Moroccan Darija'], 'maxLength': 60, '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.'}, 'seed_hook_ids': {'type': ['array', 'null'], 'items': {'type': 'string', 'format': 'uuid'}, 'title': 'Seed Hook Ids', 'default': None, 'maxItems': 10, '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.', 'uniqueItems': True}, 'conversation_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Conversation Id', 'default': None}, 'idempotency_key': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Idempotency Key', 'default': None, 'description': 'retry safely: a turn that started a run with this key within 24 hours answers that run again, no model call, nothing reserved'}, 'posted_within_days': {'enum': [7, 30, 90, 365, 0, None], 'type': ['integer', 'null'], 'title': 'Posted Within Days', 'default': None, '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."}}}
出力スキーマ
{'type': 'object', 'title': 'chatOutput', 'required': ['result'], 'properties': {'result': {'type': 'string', 'title': 'Result'}}}
clear_decision
Undo keep_hook: the hook goes back to having no verdict, so it leaves list_keeps and its export row carries none. Returns {"hook_id", "verdict": null}. Clearing a hook with no decision is the same success, so a retry is safe. Same as DELETE /v1/hooks/{hook_id}/decision. Free. Errors: 404 hook_not_found, 422 for an id that is not a UUID.
入力スキーマ
{'type': 'object', 'title': 'clear_decisionArguments', 'required': ['hook_id'], 'properties': {'api_key': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Api Key', 'default': None}, 'hook_id': {'type': 'string', 'title': 'Hook Id'}}}
出力スキーマ
{'type': 'object', 'title': 'clear_decisionOutput', 'required': ['result'], 'properties': {'result': {'type': 'string', 'title': 'Result'}}}
create_access_code
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.
入力スキーマ
{'type': 'object', 'title': 'create_access_codeArguments', 'properties': {'note': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Note', 'default': None}, 'email': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Email', 'default': None}, 'api_key': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Api Key', 'default': None}, 'max_uses': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'title': 'Max Uses', 'default': None}, 'expires_in_days': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'title': 'Expires In Days', 'default': None}}}
出力スキーマ
{'type': 'object', 'title': 'create_access_codeOutput', 'required': ['result'], 'properties': {'result': {'type': 'string', 'title': 'Result'}}}
create_account
Create an account and get an API key with free credits. Needs a beta access code (access_code, HD-XXXX-XXXX; case and dashes do not matter): without one the error is 403 access_code_required, and a code that does not work is 403 invalid_access_code. Ask for a code with request_access, or at hookdetector.com/access. No key needed for this call. label is an optional note up to 120 characters. Keep the api_key it returns: it is the only copy. Send it as the header 'Authorization: Bearer hd_...' or pass it as api_key on every other tool. Signups, and wrong codes, are limited per address per hour. Next step: find_hooks.
入力スキーマ
{'type': 'object', 'title': 'create_accountArguments', 'properties': {'label': {'anyOf': [{'type': 'string', 'maxLength': 120}, {'type': 'null'}], 'title': 'Label', 'default': None}, 'access_code': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Access Code', 'default': None}}}
出力スキーマ
{'type': 'object', 'title': 'create_accountOutput', 'required': ['result'], 'properties': {'result': {'type': 'string', 'title': 'Result'}}}
create_key
Create another API key on your account, for example one per agent or machine, so you can revoke one without touching the others. name is up to 60 characters. Give the key only what it needs. scopes: any of "read" (read runs, hooks, keeps, usage), "write" (keep, reject, undo, chat), "research" (start runs, which spend credits), "admin" (manage keys, delete the account); all four when left out. An agent that finds hooks needs ["read", "write", "research"]. credit_limit: the most credits runs started with this key may spend over its life, 1 to 100000 (a run then reserves at most what is left). expires_in_days: 1 to 365; the key then stops working (401 key_expired). The new key is in this result once and never again: store it now. Needs admin. An account holds at most 10 active keys (409 beyond) and may create 20 per hour by default (429 beyond). Free. Next step: use the returned api_key; revoke old ones with revoke_key.
入力スキーマ
{'type': 'object', 'title': 'create_keyArguments', 'properties': {'name': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Name', 'default': None}, 'scopes': {'type': 'array', 'items': {'enum': ['read', 'write', 'research', 'admin'], 'type': 'string'}, 'title': 'Scopes', 'default': None, 'minItems': 1}, 'api_key': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Api Key', 'default': None}, 'credit_limit': {'type': 'integer', 'title': 'Credit Limit', 'default': None, 'minimum': 1}, 'expires_in_days': {'type': 'integer', 'title': 'Expires In Days', 'default': None, 'minimum': 1}}}
出力スキーマ
{'type': 'object', 'title': 'create_keyOutput', 'required': ['result'], 'properties': {'result': {'type': 'string', 'title': 'Result'}}}
delete_account
Erase your account and everything in it, for good: every API key, run, hook, keep or reject decision, conversation and message. Unspent credits go with it. It cannot be undone. confirm must be exactly "delete my account", or nothing happens (422 confirmation_required). Refused with 409 run_in_flight while a run is queued or running: wait for it with get_run first. Returns {"deleted": true, "account_id", "erased": {counts per kind}, "message"}. Every key of the account stops working at once; create_account starts a new one. Same as DELETE /v1/account. Free.
入力スキーマ
{'type': 'object', 'title': 'delete_accountArguments', 'properties': {'api_key': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Api Key', 'default': None}, 'confirm': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Confirm', 'default': None}}}
出力スキーマ
{'type': 'object', 'title': 'delete_accountOutput', 'required': ['result'], 'properties': {'result': {'type': 'string', 'title': 'Result'}}}
embed_hook
Official TikTok or Instagram embed HTML for a hook, so the clip plays inside your own page. kind is "embed", or "fallback" when the creator disabled embedding or the clip is a photo post found before 2026-09-26 (then show thumbnail, which falls back to the hook's still_url, and the transcript). For a plain vertical iframe use the hook's player_url instead. Free.
入力スキーマ
{'type': 'object', 'title': 'embed_hookArguments', 'required': ['hook_id'], 'properties': {'api_key': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Api Key', 'default': None}, 'hook_id': {'type': 'string', 'title': 'Hook Id'}}}
出力スキーマ
{'type': 'object', 'title': 'embed_hookOutput', 'required': ['result'], 'properties': {'result': {'type': 'string', 'title': 'Result'}}}
export_run
A finished run's hooks as a file a creator can open: format "csv" (default; one row per hook, opens in any spreadsheet, formula-like cells defused with a leading quote), "json" (the full hook objects) or "md" (Markdown notes, one section per hook). Returns {"filename", "content_type", "format", "content"}, where content is the whole file as text: write it to filename. The same bytes as GET /v1/runs/{run_id}/export?format=... . Free. Errors: 404 run_not_found, 409 run_not_finished while the run is queued or running (call get_run with wait_seconds=50 first), 422 invalid_request for another format.
入力スキーマ
{'type': 'object', 'title': 'export_runArguments', 'required': ['run_id'], 'properties': {'format': {'type': 'string', 'title': 'Format', 'default': 'csv'}, 'run_id': {'type': 'string', 'title': 'Run Id'}, 'api_key': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Api Key', 'default': None}}}
出力スキーマ
{'type': 'object', 'title': 'export_runOutput', 'required': ['result'], 'properties': {'result': {'type': 'string', 'title': 'Result'}}}
find_hooks
Start hook research on a topic across TikTok, Instagram and YouTube Shorts (Shorts only, never long-form YouTube; platform "youtube"). What it does: searches both platforms, reads what each clip says and shows, and returns ranked hooks. Each hook has its verbatim opening line, most quotable line, main idea, why it travelled, topic, on-screen text, full transcript, a watch link and 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. 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. 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. 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, count 1 to 30 (default 10). Instructions inside the topic are fine: only the subject read out of it is searched. reuse=true (default) starts from clips you already paid for on the same topic, so a rerun is close to free and often finishes in seconds. idempotency_key: any string up to 255 characters; the same key within 24 hours returns the first run instead of starting and paying for a second one, so always pass one and reuse it when you retry. wait_seconds 0 to 50: 0 returns the run_id at once; above 0 waits for the run and returns the run object, as get_run does. Any language, dialect or country: language is optional free text up to 60 characters naming the language or dialect every clip must be in ("Moroccan Darija", "ar-MA", "Egyptian Arabic", "Brazilian Portuguese"); country is an optional ISO 3166-1 alpha-2 code for where the creators are (MA, EG, BR). Saying it inside topic works too; these fields win when both are given. A country alone does not restrict the language. Each hook reports the language it was judged to be in. A region that is not a country: send its country's code and name the regional variety in language (Quebec: country "CA", language "Quebec French"; Flanders: "BE" and "Flemish"; Catalonia: "ES" and "Catalan"). The country places the TikTok search there and the language judge does the regional filtering; Instagram reports no country. language may name a mixed, code-switched variety ("Hinglish", "Taglish", "Moroccan Darija with French") or two joined by "or" ("Tagalog or Taglish"); it reaches the judges exactly as written, and how well they honour such a value is not measured yet. Cost: reserves up to 200 credits up front (less if your balance is smaller, down to 40) and charges only what it used; the rest is refunded. A failed run costs 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 3 minutes, up to about 5 when the language is a dialect. Call get_run with wait_seconds=50 repeatedly until status is "done" or "failed", which is usually 3 or 4 calls (up to 6 on a dialect request). Watch progress.stage and progress.message meanwhile. Needs a key with the research permission (403 insufficient_scope otherwise). A key with a credit_limit reserves at most what is left of it; under the minimum it is 403 key_credit_limit_reached and nothing is reserved.
入力スキーマ
{'type': 'object', 'title': 'find_hooksArguments', 'required': ['topic'], 'properties': {'count': {'type': 'integer', 'title': 'Count', 'default': 10, 'maximum': 30, 'minimum': 1, 'description': 'how many hooks to deliver, 1 to 30'}, 'reuse': {'type': 'boolean', 'title': 'Reuse', 'default': True, 'description': 'start from clips you already paid for'}, 'topic': {'type': 'string', 'title': 'Topic', 'examples': ['short term rental tax strategy'], 'maxLength': 500, 'minLength': 3, 'description': 'what the clips are about, 3 to 500 characters. Instructions inside it are fine: a long topic, or one with a language or country, is searched by the subject read out of it, never verbatim.'}, 'api_key': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Api Key', 'default': None}, 'country': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Country', 'default': None, 'pattern': '^[A-Za-z]{2}$', 'examples': ['MA'], '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.'}, 'language': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Language', 'default': None, 'examples': ['Moroccan Darija'], 'maxLength': 60, '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.'}, 'wait_seconds': {'type': 'integer', 'title': 'Wait Seconds', 'default': 0, 'maximum': 50, 'minimum': 0, 'description': '0 to 50: seconds to wait for the run to finish; 0 returns at once'}, 'seed_hook_ids': {'type': ['array', 'null'], 'items': {'type': 'string', 'format': 'uuid'}, 'title': 'Seed Hook Ids', 'default': None, 'maxItems': 10, '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.', 'uniqueItems': True}, 'conversation_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Conversation Id', 'default': None, 'description': 'attach the run to one of your conversations'}, 'idempotency_key': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Idempotency Key', 'default': None, 'description': '1 to 255 visible ASCII characters; the same key within 24 hours returns the first run instead of paying for a second'}, 'posted_within_days': {'enum': [7, 30, 90, 365, None], 'type': ['integer', 'null'], 'title': 'Posted Within Days', 'default': None, 'examples': [30], '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."}}}
出力スキーマ
{'type': 'object', 'title': 'find_hooksOutput', 'required': ['result'], 'properties': {'result': {'type': 'string', 'title': 'Result'}}}
get_conversation
One chat conversation: every message in order (role, content, run_id, created_at) and every run it holds, with their hooks, plus nullable language and country search picker settings. Same shape as GET /v1/conversations/{conversation_id}. Free. Errors: 404 conversation_not_found, 422 for an id that is not a UUID.
入力スキーマ
{'type': 'object', 'title': 'get_conversationArguments', 'required': ['conversation_id'], 'properties': {'api_key': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Api Key', 'default': None}, 'conversation_id': {'type': 'string', 'title': 'Conversation Id'}}}
出力スキーマ
{'type': 'object', 'title': 'get_conversationOutput', 'required': ['result'], 'properties': {'result': {'type': 'string', 'title': 'Result'}}}
get_hook
One hook with every field: its opening line, 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.
入力スキーマ
{'type': 'object', 'title': 'get_hookArguments', 'required': ['hook_id'], 'properties': {'api_key': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Api Key', 'default': None}, 'hook_id': {'type': 'string', 'title': 'Hook Id'}}}
出力スキーマ
{'type': 'object', 'title': 'get_hookOutput', 'required': ['result'], 'properties': {'result': {'type': 'string', 'title': 'Result'}}}
get_run
A research run: its status, progress, charge and every hook it produced. wait_seconds 0 to 50: wait up to that long for the run to finish, returning the moment it is done or failed. Use wait_seconds=50 and call again while status is "queued" or "running"; a fresh run needs about 3 or 4 such calls. progress has stage, message and updated_at, plus counts once the research reports them, preview (hooks written so far) while a running run holds one, and feed (the live research wall: up to 24 clips the run is working on, newest first, each key, platform, creator, views, state found|reading|talking|bonus|passed, thumb_url, a JPEG any <img> can load with no key, and line, the banner or opening words once the judges passed the clip) while it shows one. Pass since with the last progress.updated_at to return on change or completion. It must be an ISO timestamp with a timezone (422 invalid_request otherwise). `hooks` is the main deck (a person talking); `bonus` holds text-on-screen clips without talking, free, same hook shape, [] on older runs. Every hook has format and section ("main" or "bonus"), and creator_country (TikTok only), place and place_country (null when unknown), banner (the title placed on the video, null when none) and match ("exact" or "close", a close match being a neighbouring subject delivered after the exact ones; null on runs with no 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, other, or null when unknown. conversation_id names the chat conversation the 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.
入力スキーマ
{'type': 'object', 'title': 'get_runArguments', 'required': ['run_id'], 'properties': {'since': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Since', 'default': None}, 'run_id': {'type': 'string', 'title': 'Run Id'}, 'api_key': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Api Key', 'default': None}, 'wait_seconds': {'type': 'integer', 'title': 'Wait Seconds', 'default': 0, 'maximum': 50, 'minimum': 0, 'description': '0 to 50: seconds to wait for the run to finish; 0 returns at once'}}}
出力スキーマ
{'type': 'object', 'title': 'get_runOutput', 'required': ['result'], 'properties': {'result': {'type': 'string', 'title': 'Result'}}}
keep_hook
Keep or reject a hook: verdict is "keep" (default) or "reject", and the last verdict wins. Kept hooks come back from list_keeps, across all runs. Free.
入力スキーマ
{'type': 'object', 'title': 'keep_hookArguments', 'required': ['hook_id'], 'properties': {'api_key': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Api Key', 'default': None}, 'hook_id': {'type': 'string', 'title': 'Hook Id'}, 'verdict': {'type': 'string', 'title': 'Verdict', 'default': 'keep'}}}
出力スキーマ
{'type': 'object', 'title': 'keep_hookOutput', 'required': ['result'], 'properties': {'result': {'type': 'string', 'title': 'Result'}}}
list_access_codes
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.
入力スキーマ
{'type': 'object', 'title': 'list_access_codesArguments', 'properties': {'api_key': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Api Key', 'default': None}}}
出力スキーマ
{'type': 'object', 'title': 'list_access_codesOutput', 'required': ['result'], 'properties': {'result': {'type': 'string', 'title': 'Result'}}}
list_access_requests
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.
入力スキーマ
{'type': 'object', 'title': 'list_access_requestsArguments', 'properties': {'status': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Status', 'default': None}, 'api_key': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Api Key', 'default': None}}}
出力スキーマ
{'type': 'object', 'title': 'list_access_requestsOutput', 'required': ['result'], 'properties': {'result': {'type': 'string', 'title': 'Result'}}}
list_conversations
Your chat conversations, most recently active first, limit 1 to 100 (default 30) per page: conversation_id, title, created_at, updated_at, language, country and messages (the count). Nullable language/country are the saved search picker settings. Returns {"conversations": [...], "next_cursor"}; pass next_cursor back as cursor for more. Same shape as GET /v1/conversations. Next step: get_conversation. Free.
入力スキーマ
{'type': 'object', 'title': 'list_conversationsArguments', 'properties': {'limit': {'type': 'integer', 'title': 'Limit', 'default': 30, 'maximum': 100, 'minimum': 1, 'description': 'page size, 1 to 100'}, 'cursor': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Cursor', 'default': None}, 'api_key': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Api Key', 'default': None}}}
出力スキーマ
{'type': 'object', 'title': 'list_conversationsOutput', 'required': ['result'], 'properties': {'result': {'type': 'string', 'title': 'Result'}}}
list_keeps
The hooks you kept with keep_hook, newest decision first, across all runs or within one run_id, limit 1 to 100 (default 100) per page. Returns {"kept": [...], "count": n on this page, "next_cursor"} with full hook objects; pass next_cursor back as cursor for more. Same shape as GET /v1/keeps. Free. Errors: 422 for a bad run_id or cursor.
入力スキーマ
{'type': 'object', 'title': 'list_keepsArguments', 'properties': {'limit': {'type': 'integer', 'title': 'Limit', 'default': 100, 'maximum': 100, 'minimum': 1, 'description': 'page size, 1 to 100'}, 'cursor': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Cursor', 'default': None}, 'run_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Run Id', 'default': None}, 'api_key': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Api Key', 'default': None}}}
出力スキーマ
{'type': 'object', 'title': 'list_keepsOutput', 'required': ['result'], 'properties': {'result': {'type': 'string', 'title': 'Result'}}}
list_keys
Your API keys and what each may do. keys: key_id, name, prefix, created_at, last_used_at and current (true for the key making this call). access, per key_id: scopes, credit_limit, credits_used, expires_at and expired. scopes: the calling key's. The original signup key has key_id "original". The keys themselves are never shown again. Needs admin. Same shape as GET /v1/keys. Free.
入力スキーマ
{'type': 'object', 'title': 'list_keysArguments', 'properties': {'api_key': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Api Key', 'default': None}}}
出力スキーマ
{'type': 'object', 'title': 'list_keysOutput', 'required': ['result'], 'properties': {'result': {'type': 'string', 'title': 'Result'}}}
list_runs
Your research runs, newest first, without their hooks, limit 1 to 100 (default 20) per page. Returns {"runs": [...], "next_cursor"}: pass next_cursor back as cursor for the next page; it is null on the last. Same shape as GET /v1/runs. Next step: open one with get_run, or download it with export_run. Free. Errors: 422 invalid_cursor for a cursor this API did not issue.
入力スキーマ
{'type': 'object', 'title': 'list_runsArguments', 'properties': {'limit': {'type': 'integer', 'title': 'Limit', 'default': 20, 'maximum': 100, 'minimum': 1, 'description': 'page size, 1 to 100'}, 'cursor': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Cursor', 'default': None}, 'api_key': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Api Key', 'default': None}}}
出力スキーマ
{'type': 'object', 'title': 'list_runsOutput', 'required': ['result'], 'properties': {'result': {'type': 'string', 'title': 'Result'}}}
reject_access_request
Owner only: reject a pending access request. Nothing is emailed. An approved or rejected request is 409 request_already_decided. Free.
入力スキーマ
{'type': 'object', 'title': 'reject_access_requestArguments', 'properties': {'api_key': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Api Key', 'default': None}, 'request_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Request Id', 'default': None}}}
出力スキーマ
{'type': 'object', 'title': 'reject_access_requestOutput', 'required': ['result'], 'properties': {'result': {'type': 'string', 'title': 'Result'}}}
remix_hook
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 clip's hook. They are never added to a deck, a keep or an export. Costs 1 credit per call, taken before the model is asked and refunded when it does not answer (503 model_unavailable). 402 insufficient_credits at a balance of 0; limited per account per hour (429 rate_limited). Needs the research permission. Same as POST /v1/hooks/{hook_id}/remix.
入力スキーマ
{'type': 'object', 'title': 'remix_hookArguments', 'properties': {'n': {'type': 'integer', 'title': 'N', 'default': None, 'maximum': 5, 'minimum': 1, 'description': 'how many lines to write, 1 to 5 (default 3)'}, 'niche': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Niche', 'default': None, 'description': 'your niche, product or audience, as you would say it (1 to 120 characters)'}, 'api_key': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Api Key', 'default': None}, 'hook_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Hook Id', 'default': None, 'description': 'one of your hooks (from get_run, get_hook or list_keeps)'}}}
出力スキーマ
{'type': 'object', 'title': 'remix_hookOutput', 'required': ['result'], 'properties': {'result': {'type': 'string', 'title': 'Result'}}}
request_access
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.
入力スキーマ
{'type': 'object', 'title': 'request_accessArguments', 'required': ['email'], 'properties': {'note': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Note', 'default': None}, 'email': {'type': 'string', 'title': 'Email'}}}
出力スキーマ
{'type': 'object', 'title': 'request_accessOutput', 'required': ['result'], 'properties': {'result': {'type': 'string', 'title': 'Result'}}}
revoke_access_code
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.
入力スキーマ
{'type': 'object', 'title': 'revoke_access_codeArguments', 'properties': {'api_key': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Api Key', 'default': None}, 'code_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Code Id', 'default': None}}}
出力スキーマ
{'type': 'object', 'title': 'revoke_access_codeOutput', 'required': ['result'], 'properties': {'result': {'type': 'string', 'title': 'Result'}}}
revoke_key
Revoke one of your API keys at once: key_id from list_keys, or "original" for the signup key. Any key may revoke itself (the result says so, and that key stops working immediately); revoking another needs admin. Never the last active key on the account (409): create another with create_key first. If a key leaked, pass key_id "others": every key of the account except the one you are calling with is revoked at once, and the result says how many (revoked_count) and which key is kept. Needs admin. Free.
入力スキーマ
{'type': 'object', 'title': 'revoke_keyArguments', 'required': ['key_id'], 'properties': {'key_id': {'type': 'string', 'title': 'Key Id'}, 'api_key': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Api Key', 'default': None}}}
出力スキーマ
{'type': 'object', 'title': 'revoke_keyOutput', 'required': ['result'], 'properties': {'result': {'type': 'string', 'title': 'Result'}}}
showcase
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.
入力スキーマ
{'type': 'object', 'title': 'showcaseArguments', 'properties': {'n': {'type': 'integer', 'title': 'N', 'default': 12, 'description': 'how many; clamped to 1 to 24'}}}
出力スキーマ
{'type': 'object', 'title': 'showcaseOutput', 'required': ['result'], 'properties': {'result': {'type': 'string', 'title': 'Result'}}}
usage
Your credits and what you have spent: credits (balance now), spent_total, reserved_now (credits held by runs still in flight, refunded in part when they finish), runs_total, last_30_days (one entry per UTC day: date, runs, charged) and recent (your 20 newest runs). Same shape as GET /v1/usage. Free.
入力スキーマ
{'type': 'object', 'title': 'usageArguments', 'properties': {'api_key': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Api Key', 'default': None}}}
出力スキーマ
{'type': 'object', 'title': 'usageOutput', 'required': ['result'], 'properties': {'result': {'type': 'string', 'title': 'Result'}}}
変更
chat
2026年10月2日2:41
追加
remix_hook
2026年10月2日2:41
変更
get_run
2026年10月2日2:41
変更
find_hooks
2026年10月2日2:41
追加
showcase
2026年10月2日2:41
変更
get_conversation
2026年9月30日2:40
変更
list_conversations
2026年9月30日2:40
変更
chat
2026年9月30日2:40
変更
get_run
2026年9月30日2:40
変更
find_hooks
2026年9月30日2:40
追加
admin_list_actions
2026年9月28日2:40
追加
admin_list_runs
2026年9月28日2:40
追加
admin_adjust_credits
2026年9月28日2:40
追加
admin_list_accounts
2026年9月28日2:40
追加
admin_overview
2026年9月28日2:40
追加
revoke_access_code
2026年9月28日2:40
追加
create_access_code
2026年9月28日2:40
追加
list_access_codes
2026年9月28日2:40
追加
reject_access_request
2026年9月28日2:40
追加
approve_access_request
2026年9月28日2:40
追加
list_access_requests
2026年9月28日2:40
追加
request_access
2026年9月28日2:40
変更
balance
2026年9月28日2:40
変更
embed_hook
2026年9月28日2:40
変更
get_hook
2026年9月28日2:40
変更
create_account
2026年9月28日2:40
追加
revoke_key
2026年9月26日2:40
追加
create_key
2026年9月26日2:40
追加
list_keys
2026年9月26日2:40
追加
usage
2026年9月26日2:40