API and MCP

Everything the app does, your agent can do. MCP and REST run on one backend and return the same hook.

Base URL https://api.hookdetector.com
Download docs (.md)
Sections
  1. Quick start
  2. Connect your agent
  3. Routes
  4. Runs
  5. Long form and packaging
  6. Hooks, keeps and chat
  7. Keys and usage
  8. Errors and limits

Quick start

Three calls, no card. New accounts need a beta access code: Ask for a code.

  1. Get a key.

    The key comes back once, with 500 free credits. Store it.

    curl -X POST https://api.hookdetector.com/v1/accounts -H 'content-type: application/json' -d '{"access_code": "HD-XXXX-XXXX"}'
  2. Start a run.

    A fresh run takes two to three minutes.

    curl -X POST https://api.hookdetector.com/v1/research \
      -H "authorization: Bearer hd_YOUR_KEY" \
      -H "content-type: application/json" \
      -H "Idempotency-Key: $(uuidgen)" \
      -d '{"topic":"short term rental tax strategy","count":10}'
  3. Wait for it.

    Repeat until status is done or failed. Set your client's read timeout above the wait, for example 70 seconds.

    curl "https://api.hookdetector.com/v1/runs/$RUN_ID?wait=55" -H "authorization: Bearer hd_YOUR_KEY"

Signed in already? Your keys are on your account page.

Connect your agent

The MCP server is https://api.hookdetector.com/mcp over streamable HTTP. Replace hd_YOUR_KEY with your key, or reveal it above and copy the block as it is.

One command in your terminal. Checked against the live server on 2026-09-22.

claude mcp add --transport http hookdetector https://api.hookdetector.com/mcp \
  --header "Authorization: Bearer hd_YOUR_KEY"
URL-only connectors and the 30 tools

A connector that only takes a URL: add https://api.hookdetector.com/mcp with no auth, call create_account once, then pass the key it returns as api_key on every tool.

  • create_account
  • find_hooks
  • find_long_hooks
  • find_packaging
  • get_run
  • list_runs
  • export_run
  • get_hook
  • embed_hook
  • remix_hook
  • keep_hook
  • clear_decision
  • list_keeps
  • list_finds
  • get_find
  • remix_find
  • keep_find
  • clear_find_decision
  • list_kept_finds
  • chat
  • list_conversations
  • get_conversation
  • balance
  • usage
  • list_keys
  • create_key
  • revoke_key
  • delete_account
  • showcase
  • request_access

Each tool returns the same shape as its REST route. find_hooks, find_long_hooks, find_packaging and get_run take wait_seconds from 0 to 50. A failed tool call carries {"error": {"code": <status>, "message", "error_code"}}.

Routes

All of them take Authorization: Bearer hd_... except signup.

Accounts and keys
RouteWhat it does
POST /v1/accountsSign up. No auth. Returns a key and 500 free credits.
GET /v1/meYour account, balance and the key you called with.
GET /v1/keysYour active keys, never the keys themselves.
POST /v1/keysMake another key with its own permissions, spend cap and expiry. Shown once.
DELETE /v1/keys/{key_id}Revoke a key. The signup key is "original"; "others" revokes every key except the one you call with, for a leak.
DELETE /v1/accountErase the account and everything in it.
Short form: research and runs
RouteWhat it does
POST /v1/researchStart a hook run on TikTok, Instagram and YouTube Shorts. Answers 202 at once. Honours Idempotency-Key.
GET /v1/runs/{run_id}One run of any kind and its results. ?wait=0 to 55 holds until it finishes.
GET /v1/runsYour runs, newest first. ?kind=short, long or packaging narrows it.
GET /v1/runs/{run_id}/exportA finished run of any kind as csv, json or md.
Long form and packaging (long-form YouTube)
RouteWhat it does
POST /v1/longformStart a long-form run: how the YouTube outliers on a topic open. The topic may be a channel's or a video's link. 202 at once; results are the run's finds.
POST /v1/packagingStart a packaging run: the titles and thumbnails of those outliers, what they share, and written ideas.
GET /v1/findsThe index: every video your long and packaging runs found, once each, by outlier score. ?q= searches it; ?kind=, ?tier=, ?min_score=, ?device= and ?sort= narrow and order it.
GET /v1/finds/{find_id}One result of a long or packaging run, every field.
POST /v1/finds/{find_id}/remixWrite openings (from a long find) or packages (from a packaging find) for your niche, or with mode intro and a video_brief, your own timed intro in a long find's structure. 1 credit. Always labelled as written.
POST /v1/finds/{find_id}/decisionKeep or reject a find.
DELETE /v1/finds/{find_id}/decisionClear a verdict on a find.
GET /v1/finds/keepsThe finds you kept. ?kind= and ?run_id= narrow it.
Hooks, embeds and players
RouteWhat it does
GET /v1/hooks/{hook_id}One hook, every field, including player_url.
GET /v1/hooks/{hook_id}/embedThe platform's official embed markup.
POST /v1/hooks/{hook_id}/remixWrite lines from a hook's structure for your niche. 1 credit. Always labelled as written.
Decisions and keeps
RouteWhat it does
POST /v1/hooks/{hook_id}/decisionKeep or reject a hook.
DELETE /v1/hooks/{hook_id}/decisionClear a verdict, the way an undo does.
GET /v1/keepsThe hooks you kept. ?run_id= narrows it to one run.
Chat and conversations
RouteWhat it does
POST /v1/chatOne chat turn: an answer, a clarifying question, or a run it started.
GET /v1/conversationsYour conversations.
GET /v1/conversations/{conversation_id}One conversation, its messages and runs.
Usage
RouteWhat it does
GET /v1/usageBalance, spend, what runs in flight hold, the last 30 days.

Runs

POST /v1/research answers 202 with a run_id and credits_reserved. Any language or dialect, from any country.

POST /v1/research
FieldAcceptsDefault
topic3 to 500 characters; only its subject is searchedrequired
count1 to 30default 10
reusetrue starts from clips you already paid for on the same topicdefault true
conversation_ida conversation's idoptional
languagefree text up to 60 characters naming the language or dialect every clip must be in, such as "Moroccan Darija", "ar-MA" or "Brazilian Portuguese"optional
countryan ISO 3166-1 alpha-2 code for where the creators are, such as MA or BR; any other value is 422optional

Saying the language or country in the topic works too; the fields win when both are given. Runs echo both, and each hook carries the language it was judged to be in. MCP find_hooks takes the same two arguments.

Wait

?wait= from 0 to 55 holds the answer until the run finishes or the time runs out. One account may hold ten waiting requests at once.

Progress

A run in flight carries progress: a stage, a plain message you can show a person, and updated_at. The stage is one of queued, starting, reusing, searching_instagram, searching_tiktok, searching_youtube, judging, transcribing, writing, saving, done, failed, other; treat other as still working. counts and, once a hook is written, preview hold the hooks confirmed so far (line, why, watch_url, player_url, thumb_url). Add ?since= with the last updated_at and a waiting call answers as soon as the progress moves.

"progress": {
  "stage": "searching_tiktok",
  "message": "searching TikTok, round 1",
  "updated_at": "2026-09-22T12:31:40Z"
}

Retry safely

Send an Idempotency-Key header, one fresh value per run. The same key on the same account within 24 hours returns the first run with 200 and Idempotent-Replayed: true, and reserves nothing. The same key with a different body is 422 idempotency_key_reused.

Lists and cursors

/v1/runs, /v1/keeps and /v1/conversations take limit and return next_cursor; pass it back as ?cursor=. It is null on the last page.

Long form and packaging

Two more kinds of run, both on long-form YouTube only (never Shorts), both outliers only, nothing posted before 2023. An outlier is a video whose views are at least min_outlier times the median of its own channel's recent uploads. Both answer 202 with a run_id and are read with GET /v1/runs/{run_id}?wait=55. Their results are the run's finds; progress.items holds those written so far. report.landscape counts how many videos the search returned, how many beat their channel, and how many of those came from small channels. A channel's address (https://www.youtube.com/@name or @name) returns that channel's own outliers; a video's (https://www.youtube.com/watch?v=...) returns that one video, whatever its multiple. A Short's address is refused.

Both kinds
FieldAcceptsDefault
topica subject, or a YouTube channel's or video's addressrequired
min_outlier1.5 to 100: views at least this many times the channel's recent mediandefault 2
posted_within_days30, 90, 365 or 730default since 2023
channel_sizesmall, medium or large, by what the channel's uploads usually getoptional
language, countryas for a hook runoptional
reusetrue starts from the videos your last run on this topic bought (within a week); a video already read is not read againdefault true

Long form

POST /v1/longform
FieldAcceptsDefault
count1 to 30default 8
intro_seconds20 to 120default 60
openers0 to 6 openings written for your topicdefault 3

Reads the opening of each video from its own captions. Each find carries hook and intro (timed lines, verbatim), hook_type, mechanism, promise, open_loop, beats, template and steal_it. The hook is a run of words of the opening: hook_start_s, hook_end_s and hook_words say where it runs and how long it is, auto_captions is true when the captions carry almost no punctuation (names in them may be misheard), and about_subject is yes or partly (null on a link run). Beside title, views, channel_median_views, outlier and outlier_score (0 to 100 on one fixed scale, as of the run that found it). report.patterns names the openings several share and why each holds, report.shape counts how they are built, and report.openers holds the written openings, labelled by openers_label, never counted as finds. It reserves up to 90 credits (more for a count above 20, up to 140 at 30) and charges only the provider calls it made.

Packaging

POST /v1/packaging
FieldAcceptsDefault
count1 to 30default 12
ideas0 to 10 titles and thumbnail concepts written for your topicdefault 6

Looks at each thumbnail beside its title. Each find carries title (YouTube's own, word for word), thumbnail_url (the image that was looked at), title_device, title_template, thumbnail_text, thumbnail_subject, thumbnail_text_role, thumbnail_composition, thumbnail_devices, gap and outlier_score, and says on_subject (yes or partly), title_overlap (the share of the thumbnail's words that the title repeats) and cut_on_phone (the title is longer than about 60 characters, where a phone roughly cuts it; approximate). report holds title_patterns, thumbnail_patterns, numbers (title length, how many carry a number or a question, how many thumbnails print words) and ideas, labelled by ideas_label, never counted as finds. It reserves up to 70 credits (up to 88 at a count of 30) and charges only the provider calls it made; looking at a thumbnail costs none.

curl -X POST https://api.hookdetector.com/v1/longform \
  -H "authorization: Bearer hd_YOUR_KEY" -H "content-type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"topic":"home espresso","count":8,"min_outlier":3,"openers":3}'

curl -X POST https://api.hookdetector.com/v1/packaging \
  -H "authorization: Bearer hd_YOUR_KEY" -H "content-type: application/json" \
  -d '{"topic":"home espresso","count":12,"ideas":6}'

The finds index. Every video your runs found is in one index, once each: GET /v1/finds?kind=packaging&sort=score. q searches titles, channels, hooks, the words on thumbnails and angles; tier (outlier, strong, breakout), min_score and device narrow it; sort is score, outlier, views or newest. Keep or reject with POST /v1/finds/{find_id}/decision; kept ones come back from GET /v1/finds/keeps. A verdict belongs to the video, in every run that found it.

Remix. POST /v1/finds/{find_id}/remix with a niche writes up to n (1 to 5, default 3) openings (from a long find) or packages (from a packaging find) on that find's structure, for 1 credit. They come back in written under a label that says they were written; they are never finds. Send mode intro and a video_brief (your video in a few sentences, 10 to 600 characters) to a long find instead and the answer is your own intro in that opening's structure: each beat with its seconds, a word budget, the words to say with {placeholders} for what only you know, and on-screen and B-roll suggestions. Also 1 credit.

Hooks, keeps and chat

Each hook carries its opening line (hook), its best line, what it argues, why it spread, its topic, the text on screen, the full transcript, its language, views, likes, the creator and a watch_url. A clip with no words has a null hook: we will not invent one from a caption.

To play a clip, put player_url in an iframe sized to aspect (9:16): TikTok's official player or Instagram's embed. TikTok photo posts have a null player_url; read their slides from onscreen_text. The embed route returns the platform's own markup, or kind: "fallback" when the creator turned embedding off.

curl https://api.hookdetector.com/v1/hooks/$HOOK_ID -H "authorization: Bearer hd_YOUR_KEY"
curl https://api.hookdetector.com/v1/hooks/$HOOK_ID/embed -H "authorization: Bearer hd_YOUR_KEY"

Keep or reject

curl -X POST https://api.hookdetector.com/v1/hooks/$HOOK_ID/decision \
  -H "authorization: Bearer hd_YOUR_KEY" -H "content-type: application/json" \
  -d '{"verdict":"keep"}'

curl "https://api.hookdetector.com/v1/keeps?run_id=$RUN_ID" -H "authorization: Bearer hd_YOUR_KEY"

The verdict is keep or reject. A DELETE on the same path clears it.

Chat

POST /v1/chat takes message, an optional conversation_id and an optional count. It answers a question about delivered hooks (action: "answer"), asks one clarifying question ("clarify") or starts a run ("research", with the run in run). Turns are limited per account per hour. The Short form tab uses it; an agent usually calls research directly.

Keys and usage

Up to ten active keys per account, so each agent gets its own. A new key is shown once. The account always keeps at least one active key. scopes picks what a key may do; with none it gets read, write and research, never admin. credit_limit (1 to 100,000) caps what it may ever spend, and expires_in_days (1 to 365) ends it. A missing permission is 403 insufficient_scope; past its cap, 403 key_limit_reached; expired, 401 key_expired. If a key leaks, DELETE /v1/keys/others stops every key but the one you call with.

Scopes
ScopeAllows
readevery GET
writekeep, pass, chat
researchstart runs, which spends credits
adminmanage keys, delete the account
Show the calls
curl https://api.hookdetector.com/v1/keys -H "authorization: Bearer hd_YOUR_KEY"

curl -X POST https://api.hookdetector.com/v1/keys -H "authorization: Bearer hd_YOUR_KEY" \
  -H "content-type: application/json" \
  -d '{"name":"claude-code on laptop","scopes":["research","read","write"],"credit_limit":500,"expires_in_days":90}'

curl -X DELETE https://api.hookdetector.com/v1/keys/$KEY_ID -H "authorization: Bearer hd_YOUR_KEY"

curl -X DELETE https://api.hookdetector.com/v1/keys/others -H "authorization: Bearer hd_YOUR_KEY"

Credits

One credit is one call to the data provider. A run holds up to 200, charges only what it used and gives the rest back. A failed run of any kind costs nothing, and so does a run that finds nothing (a few a day per account); past that it is charged what it used, and its outcome says why it came back short. Asking again about a topic you already paid for reuses those clips, so it costs far less.

curl https://api.hookdetector.com/v1/usage -H "authorization: Bearer hd_YOUR_KEY"

It returns credits, spent_total, reserved_now, runs_total, last_30_days and your 20 most recent runs.

Export

A finished run as csv (the default), json or md. Cells a spreadsheet would run as a formula are neutralised. A run still working is 409 run_not_finished. Over MCP it is export_run. Add &bom=true for a csv a person opens in Excel, so curly quotes and emoji read right.

curl -o hooks.csv "https://api.hookdetector.com/v1/runs/$RUN_ID/export?format=csv&bom=true" -H "authorization: Bearer hd_YOUR_KEY"

Delete your account

Erases the account and every row that belongs to it: all its keys, runs, hooks, decisions, conversations and messages. Every key stops working at once. It needs the exact confirmation below, and it is refused with 409 run_in_flight while a run is still working. Over MCP it is delete_account with the same confirm. In the app: Account, then More.

curl -X DELETE https://api.hookdetector.com/v1/account -H "authorization: Bearer hd_YOUR_KEY" \
  -H "content-type: application/json" \
  -d '{"confirm":"delete my account"}'

Errors and limits

Every error is JSON: {"detail": "...", "code": "..."}. Show detail to a person; branch on code, and on the HTTP status for a code you do not know. Every response carries X-Request-Id; send your own to trace a call.

StatusCodesMeaning
401invalid_api_key, key_expiredThe key is missing, unknown, revoked or past its expiry.
402insufficient_creditsNot enough credits for a run. The detail states your balance.
403insufficient_scope, key_limit_reachedThe key may not do that (detail names the permission it lacks), or it reached its spend cap.
404run_not_found, hook_not_found, find_not_found, conversation_not_found, key_not_found, not_foundNothing with that id on this account, or no such route.
405method_not_allowedThe route exists; that method does not.
409key_limit_reached, last_active_key, run_in_flight, run_not_finishedThe request conflicts with the account's state: ten keys already, the last key, a run still working.
413payload_too_largeThe body is over 64 KB.
422invalid_request, invalid_cursor, idempotency_key_reused, confirmation_required, remix_not_possibleInvalid input: detail names the field; errors lists every problem. remix_not_possible: every line the model wrote broke a rule, or it declined; nothing was charged, try again or another niche.
429rate_limited, too_many_runsA limit was hit, or the account already has the most research runs in flight. Wait Retry-After seconds.
500internal_errorOur fault. detail names the request id. Nothing was charged.
503model_unavailable, unavailable, search_unavailable, at_capacity, restartingThe language model, the video search provider or the service cannot answer, today's research capacity is used up, or the server is restarting. Nothing was charged; wait Retry-After seconds.

Rate limits

Signups are limited per address, and chat turns and new keys per account, each per hour. Those responses carry X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (seconds until the oldest counted request leaves the hour). A 429 carries Retry-After in seconds. Waiting requests are capped at ten per account.

Agent files: llms.txt · docs.md · openapi.json · interactive reference · mcp.json · server.json · status.

API and MCP | Hook Detector