---
title: "Find long-form hooks: how the YouTube outliers on a topic open."
method: POST
path: "/v1/longform"
tags: ["long form"]
---

# Find long-form hooks: how the YouTube outliers on a topic open.

`POST /v1/longform`

## Request

- Base URL: `https://api.hookdetector.com` (the document declares no server; this is the origin it was published from)
- URL: `https://api.hookdetector.com/v1/longform`
- Auth: none declared

Start a long-form run and return its run_id at once. It searches YouTube for
long-form videos on the topic (never Shorts), keeps only the outliers (videos whose
views are at least min_outlier times the median of their own channel's recent uploads,
2 by default) posted in 2023 or later, reads the first intro_seconds (60 by default) of
each one's transcript and says why that opening holds a viewer. Then GET
/v1/runs/{run_id}?wait=55 until status is done or failed: the results are the run's
`finds`, each with hook and intro (verbatim, timed lines), hook_type, mechanism,
promise, open_loop, beats, template and steal_it; `report.patterns` says which opening
types several of them share, `report.shape` how the openings are built (how long the
hook runs, which beats they use and when, counted from the finds) and `report.openers`
holds up to `openers` (3 by default) openings written for your topic from those
structures, always labelled as written, never counted as a find. While it runs,
`progress.items` holds the finds written so far, as cards, and `progress.scan` the videos it is looking at. It reserves up to 90 credits for 20 videos or fewer and 5 more for each video above that (140 for the largest count, 30; less on a smaller balance, down to 25) and charges
only the provider calls it made (the searches, one call per channel measured, one per
transcript read); a failed run costs nothing, and so does one that finds nothing, inside
the same allowance a hook run has. How long it takes has not been measured yet: poll,
never assume a time. A second long or packaging run on the same topic within a week starts from the
videos the first one bought (reuse), and a video you already had read is delivered from
that read (`analysis_from_earlier_read`).

`topic` may be a YouTube address instead of a subject: a channel's
(https://www.youtube.com/@name, or @name) returns that channel's own outliers among
its recent long-form uploads (one provider call, no search), and a long-form video's
returns that one video whatever its multiple (`requested` is 1). The run's
`options.link` says which ("channel" or "video"); no openings are written for a link.
`channel_size` (small, medium, large) keeps only channels of that size, by what their
uploads usually get. `report.landscape` is the subject at a glance, counted. Starts are
limited per account per hour (429). Needs research. MCP: find_long_hooks.

## Headers

- `Idempotency-Key` string, nullable: retry safely: the same key within 24 hours returns the first run and reserves nothing
- `authorization` string, nullable: Bearer hd_... from POST /v1/accounts or POST /v1/keys

## Request body

- LongformIn
  - `topic` string, required: the subject to research, 3 to 500 characters, in any language. Or a YouTube address instead of a subject: a channel's (https://www.youtube.com/@name, or just @name) returns that channel's own outliers, its recent long-form uploads that beat its usual; a long-form video's (https://www.youtube.com/watch?v=...) returns that one video, whatever its multiple. A Short's address is refused (422)
  - `count` integer: how many long-form hooks to deliver, 1 to 30
  - `language` string, nullable: optional, up to 60 characters: the language the videos should be spoken in, in words or as a tag ("Spanish", "pt-BR"). Left out, it is read from the topic's own language
  - `country` string, nullable: optional, an assigned ISO 3166-1 alpha-2 code (US, GB, MA): searches YouTube as seen from this country. It steers the search; it does not filter the channels
  - `min_outlier` number, nullable: optional, 1.5 to 100 (default 2): how many times its own channel's usual views a video must have to count as an outlier. Only outliers are ever delivered
  - `posted_within_days` 30 | 90 | 365 | 730 | null, nullable: optional, how recent the videos must be, as days: 30, 90, 365, 730. Null or left out means everything since 2023-01-01; nothing older is ever delivered
  - `channel_size` 'small' | 'medium' | 'large' | 'null', nullable: optional: only videos from channels of this size, by what the channel's uploads usually get (its median views, the figure the outlier is measured against): small (under 20,000), medium (up to 200,000) or large. Null or left out: any size
  - `intro_seconds` integer, nullable: optional, 20 to 120 (default 60): how many seconds of each video's opening to read and return
  - `openers` integer, nullable: optional, 0 to 6 (default 3): how many openings to write for the subject from the structure of the real ones; 0 for none
  - `reuse` boolean: start from the videos your last long or packaging run on this topic already bought (within a week), so the second tab on a topic costs little

## Response `200`

a replay of an earlier start with the same Idempotency-Key; nothing was reserved

- RunStartedOut
  - `run_id` string, required
  - `status` string, required
  - `kind` string: what the run researches: "short" (hooks from TikTok, Instagram and YouTube Shorts, POST /v1/research), "long" (how long-form YouTube outliers open, POST /v1/longform) or "packaging" (their titles and thumbnails, POST /v1/packaging). Every run from before 2026-10-07 is short
  - `topic` string, required
  - `requested` integer, required
  - `language` string, nullable: the language or dialect asked for, as sent, or null for any language
  - `country` string, nullable: the country asked for, as an ISO 3166-1 alpha-2 code (upper-cased, UK read as GB), or null for any country
  - `posted_within_days` integer, nullable: the recency window asked for (7, 30, 90 or 365 days), or null
  - `seed_hook_ids` string[], nullable: the seed hooks asked for ("find more like these"), or null
  - `platforms` string[], nullable: the platforms this Short form run searches and delivers from ("tiktok", "instagram", "youtube"): the ones asked for, or all three; null on a long or packaging run
  - `credits_reserved` integer, required
  - `created_at` string, required

## Other responses

- `202`: Successful Response
- `401`: missing, invalid, revoked or expired API key (code key_expired)
- `402`: not enough credits
- `403`: the key lacks the scope this call needs (insufficient_scope), or its credit_limit is spent (key_credit_limit_reached)
- `413`: request body over the size cap
- `422`: invalid input
- `429`: too_many_runs: the account already has the most runs in flight it may; see Retry-After; nothing was reserved
- `500`: our fault; names the request id
- `503`: nothing was reserved; see Retry-After. search_unavailable, at_capacity, restarting or unavailable, as on POST /v1/research

---

[API](https://skmtc.dev/hookdetector/apis/hook-detector.md) · [All operations](https://skmtc.dev/hookdetector/apis/hook-detector/llms.txt) · [OpenAPI document](https://skmtc.dev/hookdetector/apis/hook-detector/revisions/b136a2804d2b?raw)
