{"openapi":"3.1.0","info":{"title":"Hook Detector","description":"Find short-form hooks that already work.\n\nSign up with a beta access code (ask at https://hookdetector.com/access): one POST to\n`/v1/accounts` with `access_code` gives an API key and free credits. The same operations are available as MCP tools at `/mcp`.\n\nFull guide, always current, at [`/llms.txt`](/llms.txt).\n","contact":{"name":"Hook Detector","url":"https://hookdetector.com/","email":"contact@hookdetector.com"},"version":"2.4.0"},"paths":{"/v1/accounts":{"post":{"tags":["accounts"],"summary":"Sign up with a beta access code. No auth, no form, no human step.","description":"Create an account and get an API key with free credits. Needs a beta access code as\n`access_code` (403 access_code_required without one, 403 invalid_access_code for one\nthat does not work; ask at hookdetector.com/access). The key in this response\nis the only copy that will ever exist: store it, then send it as\n`Authorization: Bearer hd_...` on every other call. Signups are limited per address per\nhour (an IPv6 caller per /64), and so are wrong codes; the X-RateLimit headers say\nwhere you stand. Free. MCP: create_account.","operationId":"signup_v1_accounts_post","requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/SignupIn"},{"type":"null"}],"title":"Body"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignupOut"}}}},"413":{"description":"request body over the size cap","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"422":{"description":"invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"429":{"description":"rate limited; see Retry-After","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"500":{"description":"our fault; names the request id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"403":{"description":"no access code (access_code_required), or one that does not work (invalid_access_code): mistyped, used up, expired or revoked, one answer for all","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}}}}},"/v1/me":{"get":{"tags":["accounts"],"summary":"Who am I, what is my balance, what may this key do","description":"The account behind the key: account_id, credits (the balance right now),\nkey_prefix and key_id of the key making this call, label and created_at, and that\nkey's scopes, credit_limit and expires_at (null: no cap, never expires), and `user`:\nthe person signed in with Google who uses the account (null for a key-only account).\nNeeds read. Free. MCP: balance.","operationId":"me_v1_me_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys","title":"Authorization"},"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MeOut"}}}},"401":{"description":"missing, invalid, revoked or expired API key (code key_expired)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"403":{"description":"the key lacks the scope this call needs (insufficient_scope), or its credit_limit is spent (key_credit_limit_reached)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"413":{"description":"request body over the size cap","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"422":{"description":"invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"500":{"description":"our fault; names the request id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}}}}},"/v1/account":{"delete":{"tags":["accounts"],"summary":"Erase your account and everything in it","description":"Erase the account and every row it owns: all its API keys, runs, hooks, keep or\nreject decisions, conversations and messages. It cannot be undone. The body must be\nexactly `{\"confirm\": \"delete my account\"}` (422 confirmation_required otherwise), and\nit is refused with 409 run_in_flight while a run is still queued or running. Any\nunspent credits go with the account. Every key stops working at once. Needs admin.\nMCP: delete_account.","operationId":"delete_account_v1_account_delete","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys","title":"Authorization"},"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys"}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/DeleteAccountIn"},{"type":"null"}],"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountDeletedOut"}}}},"401":{"description":"missing, invalid, revoked or expired API key (code key_expired)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"403":{"description":"the key lacks the scope this call needs (insufficient_scope), or its credit_limit is spent (key_credit_limit_reached)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"409":{"description":"conflict with the account's state","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"413":{"description":"request body over the size cap","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"422":{"description":"invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"500":{"description":"our fault; names the request id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}}}}},"/v1/usage":{"get":{"tags":["accounts"],"summary":"Credits, spend, what in-flight runs hold, and the last 30 days","description":"credits (the balance now), spent_total (charged by finished runs), reserved_now\n(held by runs still in flight), runs_total, last_30_days (one entry per UTC day,\noldest first) and recent (the 20 newest runs). credits + spent_total + reserved_now is\neverything the account was given. Needs read. Free. MCP: usage.","operationId":"usage_v1_usage_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys","title":"Authorization"},"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsageOut"}}}},"401":{"description":"missing, invalid, revoked or expired API key (code key_expired)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"403":{"description":"the key lacks the scope this call needs (insufficient_scope), or its credit_limit is spent (key_credit_limit_reached)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"413":{"description":"request body over the size cap","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"422":{"description":"invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"500":{"description":"our fault; names the request id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}}}}},"/v1/keys":{"get":{"tags":["keys"],"summary":"Your API keys and what each may do. Never the keys themselves.","description":"Every key not revoked: in keys, key_id, name, prefix, created_at, last_used_at\n(updated at most every 5 minutes) and current (true for the key making this call); in\naccess, per key_id, its scopes, credit_limit, credits_used (what runs started with it\nspent), expires_at and expired. scopes is the calling key's. The signup key is key_id\n\"original\". A key itself is never shown again. Needs admin. MCP: list_keys.","operationId":"list_keys_v1_keys_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys","title":"Authorization"},"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KeyList"}}}},"401":{"description":"missing, invalid, revoked or expired API key (code key_expired)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"403":{"description":"the key lacks the scope this call needs (insufficient_scope), or its credit_limit is spent (key_credit_limit_reached)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"413":{"description":"request body over the size cap","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"422":{"description":"invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"500":{"description":"our fault; names the request id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}}}},"post":{"tags":["keys"],"summary":"Create another API key. The key is in this response only.","description":"A new key on the same account, for example one per agent or machine, so one can be\nrevoked without touching the others. Give it only what it needs: scopes (read, write,\nresearch, admin; all four when left out), credit_limit (the most its runs may spend\nover its life, 1 to 100000) and expires_in_days (1 to 365). The full key is in this\nresponse and nowhere else, ever. Needs admin. 409 key_limit_reached at 10 active\nkeys; creation is limited per account per hour (429). MCP: create_key.","operationId":"create_key_v1_keys_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys","title":"Authorization"},"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys"}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/KeyIn"},{"type":"null"}],"title":"Body"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KeyCreatedOut"}}}},"401":{"description":"missing, invalid, revoked or expired API key (code key_expired)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"403":{"description":"the key lacks the scope this call needs (insufficient_scope), or its credit_limit is spent (key_credit_limit_reached)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"409":{"description":"conflict with the account's state","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"413":{"description":"request body over the size cap","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"422":{"description":"invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"429":{"description":"rate limited; see Retry-After","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"500":{"description":"our fault; names the request id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}}}}},"/v1/keys/{key_id}":{"delete":{"tags":["keys"],"summary":"Revoke a key (\"original\", a key_id, or \"others\" for all but yours)","description":"Revoke one key at once. You may revoke the key you are calling with, whatever its\nscopes (the response says so in current); revoking another needs admin. Never the last\nusable key (409 last_active_key: create another first). An unknown or already revoked\nkey is 404 key_not_found. key_id \"others\" revokes every key of the account except the\none calling, for a key that leaked, and answers revoked_count and kept. MCP:\nrevoke_key.","operationId":"revoke_key_v1_keys__key_id__delete","parameters":[{"name":"key_id","in":"path","required":true,"schema":{"type":"string","title":"Key Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys","title":"Authorization"},"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KeyRevokedOut"}}}},"401":{"description":"missing, invalid, revoked or expired API key (code key_expired)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"403":{"description":"the key lacks the scope this call needs (insufficient_scope), or its credit_limit is spent (key_credit_limit_reached)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"404":{"description":"not found on this account","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"409":{"description":"conflict with the account's state","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"413":{"description":"request body over the size cap","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"422":{"description":"invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"500":{"description":"our fault; names the request id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}}}}},"/v1/research":{"post":{"tags":["research"],"summary":"Find hooks on a topic. Returns immediately; wait on the run.","description":"Start a research run across TikTok and Instagram and return its run_id at once.\nIt reserves up to 200 credits (less on a smaller balance, down to 40) and charges only\nwhat it used; a failed run costs nothing, and so does a run that finds no hooks, within\na per-account allowance (llms.txt has the numbers). A fresh run takes about 2 to 3 minutes (up to about 5 on a dialect request): then\nGET /v1/runs/{run_id}?wait=55 until status is done or failed. Needs research. A key\nwith a credit_limit reserves at most what is left of it (403 key_credit_limit_reached\nunder the minimum). Any language, dialect or country: set language (\"Moroccan\nDarija\", \"ar-MA\") and country (\"MA\"), or say it in the topic. Since 2026-10-01:\nposted_within_days (7, 30, 90 or 365) keeps the search to recent clips, and\nseed_hook_ids (up to 10 of your hooks) means \"find more like these\". Hooks carry\nvirality (the v2 score), outlier (views over the creator's usual), posted_at and\nthe other signals; a done run carries patterns, the structures winning in its deck.\nSince 2026-10-10: platforms (any of \"tiktok\", \"instagram\", \"youtube\"; all three when\nleft out) searches and delivers only those platforms.\nMCP: find_hooks.","operationId":"research_v1_research_post","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"retry safely: the same key within 24 hours returns the first run and reserves nothing","title":"Idempotency-Key"},"description":"retry safely: the same key within 24 hours returns the first run and reserves nothing"},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys","title":"Authorization"},"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResearchIn"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RunStartedOut"}}}},"200":{"description":"a replay of an earlier start with the same Idempotency-Key; nothing was reserved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RunStartedOut"}}}},"401":{"description":"missing, invalid, revoked or expired API key (code key_expired)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"402":{"description":"not enough credits","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"403":{"description":"the key lacks the scope this call needs (insufficient_scope), or its credit_limit is spent (key_credit_limit_reached)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"413":{"description":"request body over the size cap","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"422":{"description":"invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"500":{"description":"our fault; names the request id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"429":{"description":"too_many_runs: the account already has the most runs in flight it may; see Retry-After; nothing was reserved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"503":{"description":"nothing was reserved; see Retry-After. search_unavailable: the video search provider is refusing our requests right now. at_capacity: today's research capacity is used up. restarting: this server is restarting for an update. unavailable: every database connection stayed busy","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}}}}},"/v1/runs/{run_id}":{"get":{"tags":["research"],"summary":"A run and its hooks","description":"One run: status, progress, reserved, charged, error (ending with the request id\nthat started it, on a failed run) and every hook it produced. ?wait=N holds the\nrequest until the run is done or failed, or N seconds pass. With since set to the\nlast progress.updated_at, it also returns on progress change. A done run with fewer\nhooks than asked for, or none, has outcome: why, in plain words; a full run has none,\nor one sentence on hooks under the view floor. `hooks` holds only clips where a person\non camera is talking; `bonus` is always [] (removed 2026-10-08). Every hook has format\nand section, and since\n2026-10-01 virality, virality_why, outlier, posted_at, age_days, views_per_day and the\nother virality v2 fields (null on older hooks). A done run also carries patterns: up\nto 3 hook structures winning in this deck with the hook_ids using each, computed\nafter delivery (null until then, and on older runs). Free. MCP: get_run.","operationId":"get_run_v1_runs__run_id__get","parameters":[{"name":"run_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Run Id"}},{"name":"wait","in":"query","required":false,"schema":{"type":"integer","maximum":55,"minimum":0,"description":"seconds to wait for the run to finish, 0 to 55. Returns as soon as it is done or failed.","default":0,"title":"Wait"},"description":"seconds to wait for the run to finish, 0 to 55. Returns as soon as it is done or failed."},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Last progress.updated_at; return early when it changes.","title":"Since"},"description":"Last progress.updated_at; return early when it changes."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys","title":"Authorization"},"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RunOut"}}}},"401":{"description":"missing, invalid, revoked or expired API key (code key_expired)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"403":{"description":"the key lacks the scope this call needs (insufficient_scope), or its credit_limit is spent (key_credit_limit_reached)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"404":{"description":"not found on this account","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"413":{"description":"request body over the size cap","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"422":{"description":"invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"429":{"description":"rate limited; see Retry-After","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"500":{"description":"our fault; names the request id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}}}}},"/v1/runs/{run_id}/export":{"get":{"tags":["research"],"summary":"Download a run's hooks as CSV, JSON or Markdown","description":"The hooks of a finished run as a file, one row or section per hook, ready to open\nin a spreadsheet or paste into notes. CSV cells that a spreadsheet would run as a\nformula are prefixed with a quote. With bom=true a csv starts with the UTF-8\nbyte-order mark for Excel. 409 run_not_finished while the run is in flight.\nFree. MCP: export_run.","operationId":"export_run_v1_runs__run_id__export_get","parameters":[{"name":"run_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Run Id"}},{"name":"format","in":"query","required":false,"schema":{"enum":["csv","json","md"],"type":"string","description":"csv (opens in any spreadsheet), json or md","default":"csv","title":"Format"},"description":"csv (opens in any spreadsheet), json or md"},{"name":"bom","in":"query","required":false,"schema":{"type":"boolean","description":"csv only: start the file with the UTF-8 byte-order mark, so Excel opens it as UTF-8 (curly quotes, emoji, accents). Leave it off when a program reads the file","default":false,"title":"Bom"},"description":"csv only: start the file with the UTF-8 byte-order mark, so Excel opens it as UTF-8 (curly quotes, emoji, accents). Leave it off when a program reads the file"},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys","title":"Authorization"},"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys"}],"responses":{"200":{"description":"the file, as an attachment","content":{"text/csv":{"schema":{"type":"string"}},"application/json":{"schema":{"type":"string"}},"text/markdown":{"schema":{"type":"string"}}}},"401":{"description":"missing, invalid, revoked or expired API key (code key_expired)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"403":{"description":"the key lacks the scope this call needs (insufficient_scope), or its credit_limit is spent (key_credit_limit_reached)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"404":{"description":"not found on this account","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"409":{"description":"conflict with the account's state","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"413":{"description":"request body over the size cap","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"422":{"description":"invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"500":{"description":"our fault; names the request id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}}}}},"/v1/runs":{"get":{"tags":["research"],"summary":"Your runs, newest first","description":"Your runs without their results, newest first, a page at a time: pass next_cursor\nback as ?cursor= for the next page (null on the last). Each says its kind (short, long\nor packaging); ?kind= narrows the list to one. Open one with GET /v1/runs/{run_id}.\nFree. MCP: list_runs.","operationId":"list_runs_v1_runs_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"page size, 1 to 100","default":20,"title":"Limit"},"description":"page size, 1 to 100"},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"next_cursor from the previous page; leave out for the first","title":"Cursor"},"description":"next_cursor from the previous page; leave out for the first"},{"name":"kind","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"only runs of this kind: short, long or packaging; leave out for every kind","title":"Kind"},"description":"only runs of this kind: short, long or packaging; leave out for every kind"},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys","title":"Authorization"},"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RunList"}}}},"401":{"description":"missing, invalid, revoked or expired API key (code key_expired)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"403":{"description":"the key lacks the scope this call needs (insufficient_scope), or its credit_limit is spent (key_credit_limit_reached)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"413":{"description":"request body over the size cap","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"422":{"description":"invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"500":{"description":"our fault; names the request id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}}}}},"/v1/longform":{"post":{"tags":["long form"],"summary":"Find long-form hooks: how the YouTube outliers on a topic open.","description":"Start a long-form run and return its run_id at once. It searches YouTube for\nlong-form videos on the topic (never Shorts), keeps only the outliers (videos whose\nviews are at least min_outlier times the median of their own channel's recent uploads,\n2 by default) posted in 2023 or later, reads the first intro_seconds (60 by default) of\neach one's transcript and says why that opening holds a viewer. Then GET\n/v1/runs/{run_id}?wait=55 until status is done or failed: the results are the run's\n`finds`, each with hook and intro (verbatim, timed lines), hook_type, mechanism,\npromise, open_loop, beats, template and steal_it; `report.patterns` says which opening\ntypes several of them share, `report.shape` how the openings are built (how long the\nhook runs, which beats they use and when, counted from the finds) and `report.openers`\nholds up to `openers` (3 by default) openings written for your topic from those\nstructures, always labelled as written, never counted as a find. While it runs,\n`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\nonly the provider calls it made (the searches, one call per channel measured, one per\ntranscript read); a failed run costs nothing, and so does one that finds nothing, inside\nthe same allowance a hook run has. How long it takes has not been measured yet: poll,\nnever assume a time. A second long or packaging run on the same topic within a week starts from the\nvideos the first one bought (reuse), and a video you already had read is delivered from\nthat read (`analysis_from_earlier_read`).\n\n`topic` may be a YouTube address instead of a subject: a channel's\n(https://www.youtube.com/@name, or @name) returns that channel's own outliers among\nits recent long-form uploads (one provider call, no search), and a long-form video's\nreturns that one video whatever its multiple (`requested` is 1). The run's\n`options.link` says which (\"channel\" or \"video\"); no openings are written for a link.\n`channel_size` (small, medium, large) keeps only channels of that size, by what their\nuploads usually get. `report.landscape` is the subject at a glance, counted. Starts are\nlimited per account per hour (429). Needs research. MCP: find_long_hooks.","operationId":"longform_v1_longform_post","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"retry safely: the same key within 24 hours returns the first run and reserves nothing","title":"Idempotency-Key"},"description":"retry safely: the same key within 24 hours returns the first run and reserves nothing"},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys","title":"Authorization"},"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LongformIn"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RunStartedOut"}}}},"200":{"description":"a replay of an earlier start with the same Idempotency-Key; nothing was reserved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RunStartedOut"}}}},"401":{"description":"missing, invalid, revoked or expired API key (code key_expired)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"402":{"description":"not enough credits","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"403":{"description":"the key lacks the scope this call needs (insufficient_scope), or its credit_limit is spent (key_credit_limit_reached)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"413":{"description":"request body over the size cap","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"422":{"description":"invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"500":{"description":"our fault; names the request id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"429":{"description":"too_many_runs: the account already has the most runs in flight it may; see Retry-After; nothing was reserved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"503":{"description":"nothing was reserved; see Retry-After. search_unavailable, at_capacity, restarting or unavailable, as on POST /v1/research","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}}}}},"/v1/packaging":{"post":{"tags":["packaging"],"summary":"Find packaging: the titles and thumbnails that earned the click on a topic.","description":"Start a packaging run and return its run_id at once. It finds the long-form YouTube\nvideos on the topic that beat their own channel (the same outlier search as POST\n/v1/longform, 2023 or later), looks at each one's thumbnail beside its title, and\nreturns the pairs as the run's `finds`: title and thumbnail_url (YouTube's own), views\nand outlier, and what the package does: title_device, title_template, thumbnail_text,\nthumbnail_subject, thumbnail_composition, thumbnail_devices and gap (what the picture\nshows that the title does not say). `report` then holds title_patterns and\nthumbnail_patterns (what at least two of them share) and ideas: up to `ideas` (6 by\ndefault) titles and thumbnail concepts written for your topic from those patterns,\nalways labelled as written, never counted as a find, and `numbers`: the packages\ncounted (title length, how many carry a number or a question, how many thumbnails print\nwords, which devices repeat). GET /v1/runs/{run_id}?wait=55 until done; while it runs,\n`progress.items` holds the packages looked at so far, as cards, and `progress.scan` the\nvideos it is looking at. It reserves up to 70 credits for 24 packages or fewer and 3 more for each package above that (88 for the largest count, 30; down to 25) and charges only the provider\ncalls it made (the searches, one call per channel measured, and one for each video whose\ndate had to be read exactly): looking at a thumbnail costs none. A failed run costs\nnothing, and so does one that finds nothing, inside the allowance. How long it takes\nhas not been measured yet: poll, never assume a time. `topic` may be a channel's or a\nvideo's YouTube address, and `channel_size` narrows to channels of one size, exactly as\non POST /v1/longform; no ideas are written for a link. Starts are limited per account\nper hour (429). Needs research. MCP: find_packaging.","operationId":"packaging_v1_packaging_post","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"retry safely: the same key within 24 hours returns the first run and reserves nothing","title":"Idempotency-Key"},"description":"retry safely: the same key within 24 hours returns the first run and reserves nothing"},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys","title":"Authorization"},"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PackagingIn"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RunStartedOut"}}}},"200":{"description":"a replay of an earlier start with the same Idempotency-Key; nothing was reserved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RunStartedOut"}}}},"401":{"description":"missing, invalid, revoked or expired API key (code key_expired)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"402":{"description":"not enough credits","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"403":{"description":"the key lacks the scope this call needs (insufficient_scope), or its credit_limit is spent (key_credit_limit_reached)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"413":{"description":"request body over the size cap","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"422":{"description":"invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"500":{"description":"our fault; names the request id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"429":{"description":"too_many_runs: the account already has the most runs in flight it may; see Retry-After; nothing was reserved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"503":{"description":"nothing was reserved; see Retry-After. search_unavailable, at_capacity, restarting or unavailable, as on POST /v1/research","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}}}}},"/v1/finds":{"get":{"tags":["long form","packaging"],"summary":"The index: every video your long and packaging runs found","description":"Everything your long and packaging runs found, as one index: one entry a video on\neach tab (its newest read, with the keep or reject you made on the video), each a full\nfind object with its title and thumbnail as YouTube shows them, its `outlier` (views\nover its channel's usual) and its `outlier_score` (0 to 100 on one fixed scale, so\nentries from different runs rank together). `q` looks in the title, the channel, the\nhook as it was said, and what the thumbnail prints and shows, so a package can be\nfound by its thumbnail (\"red arrow\", \"before and after\"). `total` is how many entries\nmatch, on any page; page with next_cursor. Free. MCP: list_finds.","operationId":"find_index_v1_finds_get","parameters":[{"name":"kind","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"only long or only packaging; leave out for both","title":"Kind"},"description":"only long or only packaging; leave out for both"},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}],"description":"words a title, channel, hook or thumbnail must hold","title":"Q"},"description":"words a title, channel, hook or thumbnail must hold"},{"name":"sort","in":"query","required":false,"schema":{"type":"string","description":"score (the default: the highest outlier score first), outlier (views over the channel's usual), views or newest","default":"score","title":"Sort"},"description":"score (the default: the highest outlier score first), outlier (views over the channel's usual), views or newest"},{"name":"tier","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"only breakout (10 times the channel's usual or more), strong (5 to 10) or outlier (under 5)","title":"Tier"},"description":"only breakout (10 times the channel's usual or more), strong (5 to 10) or outlier (under 5)"},{"name":"min_score","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"only entries with an outlier_score of at least this, 0 to 100","title":"Min Score"},"description":"only entries with an outlier_score of at least this, 0 to 100"},{"name":"device","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"only entries filed under this device: a hook_type, a title_device or one of the thumbnail_devices (\"curiosity_gap\", \"how_to\", \"face_closeup\")","title":"Device"},"description":"only entries filed under this device: a hook_type, a title_device or one of the thumbnail_devices (\"curiosity_gap\", \"how_to\", \"face_closeup\")"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"page size, 1 to 100","default":100,"title":"Limit"},"description":"page size, 1 to 100"},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"next_cursor from the previous page; leave out for the first","title":"Cursor"},"description":"next_cursor from the previous page; leave out for the first"},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys","title":"Authorization"},"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FindIndexOut"}}}},"401":{"description":"missing, invalid, revoked or expired API key (code key_expired)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"403":{"description":"the key lacks the scope this call needs (insufficient_scope), or its credit_limit is spent (key_credit_limit_reached)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"413":{"description":"request body over the size cap","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"422":{"description":"invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"500":{"description":"our fault; names the request id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}}}}},"/v1/finds/keeps":{"get":{"tags":["long form","packaging"],"summary":"The long-form hooks and packages you kept","description":"The videos you kept, newest decision first: one entry a video on each tab (its\nnewest find), or the kept finds of one run when run_id is given; full find objects, a\npage at a time. count is the number on this page. Free. MCP: list_kept_finds.","operationId":"kept_finds_v1_finds_keeps_get","parameters":[{"name":"kind","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"only long or only packaging; leave out for both","title":"Kind"},"description":"only long or only packaging; leave out for both"},{"name":"run_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Run Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"page size, 1 to 100","default":100,"title":"Limit"},"description":"page size, 1 to 100"},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"next_cursor from the previous page; leave out for the first","title":"Cursor"},"description":"next_cursor from the previous page; leave out for the first"},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys","title":"Authorization"},"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KeptFindsOut"}}}},"401":{"description":"missing, invalid, revoked or expired API key (code key_expired)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"403":{"description":"the key lacks the scope this call needs (insufficient_scope), or its credit_limit is spent (key_credit_limit_reached)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"413":{"description":"request body over the size cap","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"422":{"description":"invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"500":{"description":"our fault; names the request id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}}}}},"/v1/finds/{find_id}":{"get":{"tags":["long form","packaging"],"summary":"One find, every field","description":"One result of a long or packaging run with every field. find_id comes from a run's\n`finds`. Free. MCP: get_find.","operationId":"get_find_v1_finds__find_id__get","parameters":[{"name":"find_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Find Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys","title":"Authorization"},"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FindOut"}}}},"401":{"description":"missing, invalid, revoked or expired API key (code key_expired)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"403":{"description":"the key lacks the scope this call needs (insufficient_scope), or its credit_limit is spent (key_credit_limit_reached)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"404":{"description":"not found on this account","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"413":{"description":"request body over the size cap","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"422":{"description":"invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"500":{"description":"our fault; names the request id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}}}}},"/v1/finds/{find_id}/decision":{"post":{"tags":["long form","packaging"],"summary":"Keep or reject a find","description":"Record keep or reject on one find; the last verdict wins. The verdict is the video's\non that tab: every find of the same video you have there (an earlier run's, a later\none's) carries it. Kept videos come back from GET /v1/finds/keeps. Free. MCP: keep_find.","operationId":"decide_find_v1_finds__find_id__decision_post","parameters":[{"name":"find_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Find Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys","title":"Authorization"},"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DecisionIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FindDecisionOut"}}}},"401":{"description":"missing, invalid, revoked or expired API key (code key_expired)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"403":{"description":"the key lacks the scope this call needs (insufficient_scope), or its credit_limit is spent (key_credit_limit_reached)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"404":{"description":"not found on this account","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"413":{"description":"request body over the size cap","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"422":{"description":"invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"500":{"description":"our fault; names the request id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}}}},"delete":{"tags":["long form","packaging"],"summary":"Clear a keep or reject on a find","description":"Undo a verdict: the video goes back to having none, on every find of it, and leaves\nGET /v1/finds/keeps. Answers {\"find_id\", \"verdict\": null}. Clearing a find with no\ndecision is the same 200. Free. MCP: clear_find_decision.","operationId":"clear_find_decision_v1_finds__find_id__decision_delete","parameters":[{"name":"find_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Find Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys","title":"Authorization"},"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FindDecisionOut"}}}},"401":{"description":"missing, invalid, revoked or expired API key (code key_expired)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"403":{"description":"the key lacks the scope this call needs (insufficient_scope), or its credit_limit is spent (key_credit_limit_reached)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"404":{"description":"not found on this account","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"413":{"description":"request body over the size cap","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"422":{"description":"invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"500":{"description":"our fault; names the request id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}}}}},"/v1/finds/{find_id}/remix":{"post":{"tags":["long form","packaging"],"summary":"Make it mine, or Write my intro: written from this find","description":"Write my intro: body {\"mode\": \"intro\", \"video_brief\": \"<your video in a few\nsentences, 10 to 600 characters>\"} (a long find only; niche and n are not used) writes\nyour own intro in this real opening's structure: one entry in `written` holding a\n`beat` for each beat of the real opening, in order, each with its seconds (from_s,\nto_s) and words_budget taken from the real opening's own timing, the words to `say`\nwith {placeholders} for what only you know, and suggested_on_screen and suggested_broll\n(suggestions, never part of what is said). A number you did not write becomes {number}\n(numbers_to_placeholders counts them). Same price and rules as below: 1 credit, labelled\nas written, 422 remix_not_possible when the model copied the real opening or broke a\nbudget.\n\nOtherwise write up to n (1 to 5, default 3) new openings or packages for your niche\nthat keep this one real find's structure. From a long find: `written` holds openings (hook, then,\nkept_structure) built on its template and its order of beats. From a packaging find:\npackages (title, thumbnail, thumbnail_text, kept_structure) built on its title\ntemplate and its thumbnail's composition. Body: {\"niche\": \"teeth whitening\", \"n\": 2}.\nEvery answer carries `label`: these are written, never found; one that is the real\nfind's own words is dropped; they are never added to a run, a keep or an export. Costs\n1 credit per call, taken before the model is asked and refunded if it does not answer\n(503). 404 for a find that is not yours. Limited per account per hour (429). Needs\nresearch. The answer carries remix_id; GET /v1/finds/{find_id}/remixes and GET\n/v1/remixes/{remix_id} read it back, free. MCP: remix_find.","operationId":"remix_find_v1_finds__find_id__remix_post","parameters":[{"name":"find_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Find Id"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"retry safely: the same key and body within 24 hours answer the saved remix (Idempotent-Replayed: true) and charge nothing; the same key with another body is 422 idempotency_key_reused","title":"Idempotency-Key"},"description":"retry safely: the same key and body within 24 hours answer the saved remix (Idempotent-Replayed: true) and charge nothing; the same key with another body is 422 idempotency_key_reused"},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys","title":"Authorization"},"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys"}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/RemixFindIn"},{"type":"null"}],"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RemixFindOut"}}}},"401":{"description":"missing, invalid, revoked or expired API key (code key_expired)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"402":{"description":"not enough credits","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"403":{"description":"the key lacks the scope this call needs (insufficient_scope), or its credit_limit is spent (key_credit_limit_reached)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"404":{"description":"not found on this account","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"413":{"description":"request body over the size cap","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"422":{"description":"invalid input (invalid_request, idempotency_key_reused), or remix_not_possible: the model answered but nothing it wrote kept the rules, or it declined the content; detail says why and the credit was refunded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"429":{"description":"rate limited; see Retry-After","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"500":{"description":"our fault; names the request id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"503":{"description":"model_unavailable: the language model did not answer; the credit was refunded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}}}}},"/v1/finds/{find_id}/remixes":{"get":{"tags":["long form","packaging"],"summary":"Your saved remixes of this find","description":"The openings or packages you paid to have written from this find, newest first,\neach as its answer held it, with remix_id and created_at. Free. 404 for a find that is\nnot yours. MCP: list_remixes.","operationId":"find_remixes_v1_finds__find_id__remixes_get","parameters":[{"name":"find_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Find Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys","title":"Authorization"},"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RemixList"}}}},"401":{"description":"missing, invalid, revoked or expired API key (code key_expired)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"403":{"description":"the key lacks the scope this call needs (insufficient_scope), or its credit_limit is spent (key_credit_limit_reached)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"404":{"description":"not found on this account","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"413":{"description":"request body over the size cap","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"422":{"description":"invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"500":{"description":"our fault; names the request id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}}}}},"/v1/remixes/{remix_id}":{"get":{"tags":["hooks"],"summary":"One saved remix","description":"One remix you paid for (a hook's or a find's), as its answer held it. Free. 404\nremix_not_found for one that is not yours. MCP: get_remix.","operationId":"get_remix_v1_remixes__remix_id__get","parameters":[{"name":"remix_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Remix Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys","title":"Authorization"},"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SavedRemixOut"}}}},"401":{"description":"missing, invalid, revoked or expired API key (code key_expired)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"403":{"description":"the key lacks the scope this call needs (insufficient_scope), or its credit_limit is spent (key_credit_limit_reached)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"404":{"description":"not found on this account","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"413":{"description":"request body over the size cap","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"422":{"description":"invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"500":{"description":"our fault; names the request id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}}}}},"/v1/hooks/{hook_id}":{"get":{"tags":["hooks"],"summary":"One hook, every field","description":"One hook with every field: the verbatim opening line, the most quotable line, main\nidea, why it travelled, on-screen text, transcript, watch_url and the 9:16\nplayer_url. hook_id comes from a run. Free. MCP: get_hook.","operationId":"get_hook_v1_hooks__hook_id__get","parameters":[{"name":"hook_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Hook Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys","title":"Authorization"},"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HookOut"}}}},"401":{"description":"missing, invalid, revoked or expired API key (code key_expired)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"403":{"description":"the key lacks the scope this call needs (insufficient_scope), or its credit_limit is spent (key_credit_limit_reached)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"404":{"description":"not found on this account","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"413":{"description":"request body over the size cap","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"422":{"description":"invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"500":{"description":"our fault; names the request id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}}}}},"/v1/hooks/{hook_id}/embed":{"get":{"tags":["hooks"],"summary":"Playable embed so the clip runs in your page","description":"Official TikTok or Instagram embed HTML. kind is \"embed\", or \"fallback\" when the\ncreator disabled embedding or the clip is a photo post found before 2026-09-26 (then\nshow thumbnail, which\nfalls back to the hook's still_url, and the transcript; a fallback is remembered for\nan hour). For a plain vertical player use the hook's player_url.\nFree. MCP: embed_hook.","operationId":"get_embed_v1_hooks__hook_id__embed_get","parameters":[{"name":"hook_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Hook Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys","title":"Authorization"},"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmbedOut"}}}},"401":{"description":"missing, invalid, revoked or expired API key (code key_expired)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"403":{"description":"the key lacks the scope this call needs (insufficient_scope), or its credit_limit is spent (key_credit_limit_reached)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"404":{"description":"not found on this account","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"413":{"description":"request body over the size cap","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"422":{"description":"invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"500":{"description":"our fault; names the request id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}}}}},"/v1/hooks/{hook_id}/still":{"get":{"tags":["hooks"],"summary":"The hook's still as a JPEG, for an <img> tag","description":"The card image for a hook, fetched and re-encoded by this API, so it loads on any\npage (platform image hosts refuse to be shown on other sites, and their links expire).\nNo key needed: the id comes from a run and the image is the platform's public still.\nCached for a day. Free. On MCP the same URL is the hook's still_url.","operationId":"get_still_v1_hooks__hook_id__still_get","parameters":[{"name":"hook_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Hook Id"}}],"responses":{"200":{"description":"the still as a JPEG","content":{"image/jpeg":{"schema":{"type":"string","format":"binary"}}}},"404":{"description":"not found on this account","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"422":{"description":"invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"429":{"description":"rate limited; see Retry-After","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"500":{"description":"our fault; names the request id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}}}}},"/v1/hooks/{hook_id}/media":{"get":{"tags":["hooks"],"summary":"The clip's original video file, for a <video> tag","description":"Play a TikTok or Instagram clip in your own player: put this URL (the hook's\nmedia_url) in a <video src>. It answers a 302 to the clip's original file on the\nplatform's CDN, which plays with seeking; no video byte is stored or proxied. No key needed.\n404 media_unavailable for a clip with no file (a YouTube Short, a photo post, a deleted\npost) and 503 media_busy when it cannot be looked up now: show the official embed\ninstead. After a playback error, ask once with ?retry=1 for a fresh address. With\n?format=json it answers 200 {url, expires_in} instead of redirecting. Free.","operationId":"get_media_v1_hooks__hook_id__media_get","parameters":[{"name":"hook_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Hook Id"}},{"name":"retry","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Retry"}},{"name":"format","in":"query","required":false,"schema":{"enum":["redirect","json"],"type":"string","default":"redirect","title":"Format"}}],"responses":{"302":{"description":"a redirect to the clip's current file on the platform's own video CDN (TikTok or Instagram)"},"200":{"description":"with ?format=json: the file address as JSON","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MediaOut"}}}},"404":{"description":"not found on this account","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"422":{"description":"invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"429":{"description":"rate limited; see Retry-After","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"503":{"description":"the language model is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}}}}},"/v1/runs/{run_id}/thumbs/{key}":{"get":{"tags":["runs"],"summary":"A clip on a run's live research wall, as a JPEG for an <img> tag","description":"The cover of one clip a run in flight is working on: the thumb_url of an entry in\nprogress.feed. No key needed (an <img> cannot send one): the URL is the secret, an HMAC\nof the run and the clip under the server's secret, so it cannot be guessed. Served from\nthe cover the run already holds, re-encoded; 404 once the run no longer shows it. Cached\nfor a day. Free. On MCP the same URL is in get_run's progress.feed.","operationId":"get_thumb_v1_runs__run_id__thumbs__key__get","parameters":[{"name":"run_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Run Id"}},{"name":"key","in":"path","required":true,"schema":{"type":"string","title":"Key"}}],"responses":{"200":{"description":"the clip's cover as a JPEG, at most 400 KB","content":{"image/jpeg":{"schema":{"type":"string","format":"binary"}}}},"404":{"description":"not found on this account","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"422":{"description":"invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"429":{"description":"rate limited; see Retry-After","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"500":{"description":"our fault; names the request id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}}}}},"/v1/showcase":{"get":{"tags":["hooks"],"summary":"Real delivered talking-head hooks, for a wait screen","description":"Hooks this service has delivered, for the first seconds of a wait before a run's own\nfeed fills: talking heads only, a banner or opening line, a still (still_url, no key),\nthe most viewed first and one per creator. Only what is already public on TikTok or\nInstagram: never a topic, an account or a run. No key needed. Cached 10 minutes. Free.\nMCP: showcase.","operationId":"get_showcase_v1_showcase_get","parameters":[{"name":"n","in":"query","required":false,"schema":{"type":"integer","description":"how many; clamped to 1 to 24","default":12,"title":"N"},"description":"how many; clamped to 1 to 24"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ShowcaseHook"},"title":"Response Get Showcase V1 Showcase Get"}}}},"422":{"description":"invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"429":{"description":"rate limited; see Retry-After","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"500":{"description":"our fault; names the request id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}}}}},"/v1/hooks/{hook_id}/decision":{"post":{"tags":["hooks"],"summary":"Keep or reject a hook","description":"Record keep or reject on one hook; the last verdict wins. Kept hooks come back\nfrom GET /v1/keeps across every run. Free. MCP: keep_hook.","operationId":"decide_v1_hooks__hook_id__decision_post","parameters":[{"name":"hook_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Hook Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys","title":"Authorization"},"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DecisionIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DecisionOut"}}}},"401":{"description":"missing, invalid, revoked or expired API key (code key_expired)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"403":{"description":"the key lacks the scope this call needs (insufficient_scope), or its credit_limit is spent (key_credit_limit_reached)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"404":{"description":"not found on this account","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"413":{"description":"request body over the size cap","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"422":{"description":"invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"500":{"description":"our fault; names the request id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}}}},"delete":{"tags":["hooks"],"summary":"Clear a keep or reject (undo)","description":"Undo a verdict: the hook goes back to having none, so it leaves GET /v1/keeps and\nits export row carries no verdict. Answers {\"hook_id\", \"verdict\": null}. Clearing a\nhook that has no decision is the same 200, so a retried undo is safe. Free. MCP:\nclear_decision.","operationId":"clear_decision_v1_hooks__hook_id__decision_delete","parameters":[{"name":"hook_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Hook Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys","title":"Authorization"},"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DecisionClearedOut"}}}},"401":{"description":"missing, invalid, revoked or expired API key (code key_expired)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"403":{"description":"the key lacks the scope this call needs (insufficient_scope), or its credit_limit is spent (key_credit_limit_reached)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"404":{"description":"not found on this account","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"413":{"description":"request body over the size cap","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"422":{"description":"invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"500":{"description":"our fault; names the request id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}}}}},"/v1/hooks/{hook_id}/remix":{"post":{"tags":["hooks"],"summary":"Make it mine: lines written from this hook's structure for your niche","description":"Write up to n new opening lines (1 to 5, default 3) for your niche that keep this\nreal hook's structure: the device that made it travel, with the subject swapped for\nyours. Body: {\"niche\": \"teeth whitening\", \"n\": 2}. Every answer carries the label\n\"Written by Hook Detector from a real hook's structure\", and each line names the\nstructure it kept: these are written lines, never found hooks; they are never mixed\ninto a deck, a keep or an export. Costs 1 credit per call (a cheap model), taken\nbefore the model is asked and refunded if it does not answer (503). 404 for a hook\nthat is not yours. Limited per account per hour (429). Needs research. The answer\ncarries remix_id; GET /v1/hooks/{hook_id}/remixes and GET /v1/remixes/{remix_id} read\nit back, free. MCP: remix_hook.","operationId":"remix_hook_v1_hooks__hook_id__remix_post","parameters":[{"name":"hook_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Hook Id"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"retry safely: the same key and body within 24 hours answer the saved remix (Idempotent-Replayed: true) and charge nothing; the same key with another body is 422 idempotency_key_reused","title":"Idempotency-Key"},"description":"retry safely: the same key and body within 24 hours answer the saved remix (Idempotent-Replayed: true) and charge nothing; the same key with another body is 422 idempotency_key_reused"},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys","title":"Authorization"},"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys"}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/RemixHookIn"},{"type":"null"}],"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RemixOut"}}}},"401":{"description":"missing, invalid, revoked or expired API key (code key_expired)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"402":{"description":"not enough credits","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"403":{"description":"the key lacks the scope this call needs (insufficient_scope), or its credit_limit is spent (key_credit_limit_reached)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"404":{"description":"not found on this account","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"413":{"description":"request body over the size cap","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"422":{"description":"invalid input (invalid_request, idempotency_key_reused), or remix_not_possible: the model answered but nothing it wrote kept the rules, or it declined the content; detail says why and the credit was refunded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"429":{"description":"rate limited; see Retry-After","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"500":{"description":"our fault; names the request id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"503":{"description":"model_unavailable: the language model did not answer; the credit was refunded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}}}}},"/v1/hooks/{hook_id}/remixes":{"get":{"tags":["hooks"],"summary":"Your saved remixes of this hook","description":"The lines you paid to have written from this hook, newest first, each as its answer\nheld it, with remix_id and created_at. Free. 404 for a hook that is not yours. MCP:\nlist_remixes.","operationId":"hook_remixes_v1_hooks__hook_id__remixes_get","parameters":[{"name":"hook_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Hook Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys","title":"Authorization"},"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RemixList"}}}},"401":{"description":"missing, invalid, revoked or expired API key (code key_expired)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"403":{"description":"the key lacks the scope this call needs (insufficient_scope), or its credit_limit is spent (key_credit_limit_reached)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"404":{"description":"not found on this account","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"413":{"description":"request body over the size cap","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"422":{"description":"invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"500":{"description":"our fault; names the request id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}}}}},"/v1/keeps":{"get":{"tags":["hooks"],"summary":"The hooks you kept","description":"The hooks you kept, newest decision first, across all runs or within one run_id,\nas full hook objects, a page at a time (100 per page by default, and at most).\ncount is the number on this page. Free. MCP: list_keeps.","operationId":"keeps_v1_keeps_get","parameters":[{"name":"run_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Run Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"page size, 1 to 100","default":100,"title":"Limit"},"description":"page size, 1 to 100"},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"next_cursor from the previous page; leave out for the first","title":"Cursor"},"description":"next_cursor from the previous page; leave out for the first"},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys","title":"Authorization"},"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KeepsOut"}}}},"401":{"description":"missing, invalid, revoked or expired API key (code key_expired)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"403":{"description":"the key lacks the scope this call needs (insufficient_scope), or its credit_limit is spent (key_credit_limit_reached)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"413":{"description":"request body over the size cap","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"422":{"description":"invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"500":{"description":"our fault; names the request id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}}}}},"/v1/chat":{"post":{"tags":["chat"],"summary":"One conversational turn. The chat surface uses this.","description":"One turn: an answer about hooks or the tool (action \"answer\", no credits),\none clarifying question (action \"clarify\"), or a started run\n(action \"research\", with the run in `run`, reserving credits like POST /v1/research).\nA deck size typed into the message (\"..., 15 hooks\") and a recency phrase (\"this\nmonth\", \"last week\") set the run's count and posted_within_days; the body's\nposted_within_days (the recency chip) wins over the phrase and is kept on the\nconversation for later turns (an explicit null clears it); seed_hook_ids applies to\nthis turn's research (\"find more like these\"); count sets how many hooks a run this\nturn starts delivers (a size typed into the message wins). Asked to make one\nof the conversation's hooks yours for a niche (\"make hook 2 mine for dentists\"), the\nturn writes lines from that hook's structure (1 credit, like POST\n/v1/hooks/{hook_id}/remix) and answers with them labelled, the result in `remix`.\nEvery response includes suggestions (0 to 4 follow-ups, [] for research).\nEach turn is a model call, so turns are limited per account per hour. 503\nmodel_unavailable when the model is down; nothing is charged then. Needs write, and\nresearch for a turn that starts a run (403 insufficient_scope before anything is\nreserved). MCP: chat.","operationId":"chat_turn_v1_chat_post","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"retry safely: the same key and body within 24 hours answer the first turn again (its conversation, its answer, its run or its remix): no model call, no new message, nothing charged. The same key with another body is 422 idempotency_key_reused","title":"Idempotency-Key"},"description":"retry safely: the same key and body within 24 hours answer the first turn again (its conversation, its answer, its run or its remix): no model call, no new message, nothing charged. The same key with another body is 422 idempotency_key_reused"},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys","title":"Authorization"},"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatOut"}}}},"401":{"description":"missing, invalid, revoked or expired API key (code key_expired)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"402":{"description":"not enough credits","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"403":{"description":"the key lacks the scope this call needs (insufficient_scope), or its credit_limit is spent (key_credit_limit_reached)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"404":{"description":"not found on this account","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"413":{"description":"request body over the size cap","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"422":{"description":"invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"429":{"description":"rate limited; see Retry-After","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"500":{"description":"our fault; names the request id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"503":{"description":"the language model is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}}}}},"/v1/conversations":{"get":{"tags":["chat"],"summary":"Your conversations","description":"Your chat conversations, most recently active first, a page at a time, each with\nits title and message count. Free. MCP: list_conversations.","operationId":"conversations_v1_conversations_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"page size, 1 to 100","default":30,"title":"Limit"},"description":"page size, 1 to 100"},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"next_cursor from the previous page; leave out for the first","title":"Cursor"},"description":"next_cursor from the previous page; leave out for the first"},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys","title":"Authorization"},"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversationList"}}}},"401":{"description":"missing, invalid, revoked or expired API key (code key_expired)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"403":{"description":"the key lacks the scope this call needs (insufficient_scope), or its credit_limit is spent (key_credit_limit_reached)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"413":{"description":"request body over the size cap","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"422":{"description":"invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"500":{"description":"our fault; names the request id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}}}}},"/v1/conversations/{conversation_id}":{"get":{"tags":["chat"],"summary":"One conversation with its messages and runs","description":"One conversation: its newest messages in order (a page of `limit`; earlier_cursor\npages back through older ones), and the runs those messages hold (started by the chat,\nor attached with conversation_id in the same stretch of time) with their hooks. Free.\nMCP: get_conversation.","operationId":"conversation_v1_conversations__conversation_id__get","parameters":[{"name":"conversation_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Conversation Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"description":"messages per page, newest page first","default":100,"title":"Limit"},"description":"messages per page, newest page first"},{"name":"before","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"earlier_cursor from the previous page; leave out for the newest messages","title":"Before"},"description":"earlier_cursor from the previous page; leave out for the newest messages"},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys","title":"Authorization"},"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversationOut"}}}},"401":{"description":"missing, invalid, revoked or expired API key (code key_expired)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"403":{"description":"the key lacks the scope this call needs (insufficient_scope), or its credit_limit is spent (key_credit_limit_reached)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"404":{"description":"not found on this account","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"413":{"description":"request body over the size cap","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"422":{"description":"invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"500":{"description":"our fault; names the request id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}}}}},"/health":{"get":{"tags":["meta"],"summary":"Liveness: is the process up and can it reach the database","description":"A cheap check for load balancers: always 200. status is ok, or degraded when the\ndatabase does not answer. database is \"up\", \"busy\" (every pooled connection is in use\nright now: the service is loaded, not broken, and status stays ok) or \"down\". commit\nis the git commit this image was built from, \"unknown\" outside a deploy.\nruns_in_flight counts research runs queued or running across the service (null when\nthe database did not answer): a restart waits for 0, because Railway's in-place\nrestart kills the process about 10 seconds after SIGTERM. It waits at most about 2\nseconds for a connection and never calls a provider.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthOut"}}}}}}},"/health/deep":{"get":{"tags":["meta"],"summary":"Readiness: database, migrations, provider keys, provider status","description":"For monitoring and for the web: a timed database round trip, whether every\nmigration this code needs is applied, whether each provider key is configured (never\na value), and provider_status: whether the language model or the search provider is\nrefusing right now, read from how recent runs ended (cached 30 seconds, never a paid\nprobe), and for the search provider our own credits_left (read in the background at\nmost every 10 minutes from its free balance endpoint, never on this request), low\n(under HD_SEARCH_CREDITS_LOW) and accepting_runs (false while it refuses us: new runs\nare then 503 search_unavailable). A refusing provider does not make this 503; it is a\nwarning for new runs. fallback_providers says whether the optional anthropic model\nfallback is configured (never a value); it never makes this degraded.\ncommit is the git commit the image was built from (\"unknown\" outside a deploy), and\ncredits_today is how much of the global daily research ceiling is reserved (UTC day).\ndatabase.state is \"up\", \"busy\" or \"down\". Busy means every pooled connection is in\nuse: it answers 200 with status ok, the schema as last read, and no provider_status.","operationId":"health_deep_health_deep_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthDeepOut"}}}},"503":{"description":"degraded: database down, a migration missing or a provider key not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthDeepOut"}}}}}}},"/v1/access/requests":{"post":{"tags":["access"],"summary":"Ask for a beta access code by email. No auth.","description":"Ask for the beta access code a new account needs. Send your email and, if you like,\na short note. The owner approves the request and the code is emailed to you at once;\nit works once, on the web or as access_code on POST /v1/accounts. The answer is 202\nwith status pending (waiting for approval) or emailed (sent now). Asking again while a\nrequest is pending changes nothing. Limited to 5 requests per address per hour (429).\nNo key needed. Free. MCP: request_access.","operationId":"request_access_v1_access_requests_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccessRequestIn"}}},"required":true},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccessRequestOut"}}}},"413":{"description":"request body over the size cap","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"422":{"description":"invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"429":{"description":"rate limited; see Retry-After","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"500":{"description":"our fault; names the request id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}}}}},"/v1/admin/requests":{"get":{"tags":["admin"],"summary":"Access requests, newest first. Owner only.","description":"Every access request, newest first (at most 500): request_id, email, note, status\n(pending, approved, rejected), the code_id and code_prefix of the code made when it was\napproved, created_at and decided_at. Never the code itself. Needs the admin scope and\nan admin account (403 admin_required). MCP: list_access_requests.","operationId":"list_requests_v1_admin_requests_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"enum":["pending","approved","rejected","other"],"type":"string"},{"type":"null"}],"description":"only requests with this status; all when left out","title":"Status"},"description":"only requests with this status; all when left out"},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys","title":"Authorization"},"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccessRequestList"}}}},"401":{"description":"missing, invalid, revoked or expired API key (code key_expired)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"413":{"description":"request body over the size cap","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"422":{"description":"invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"500":{"description":"our fault; names the request id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"403":{"description":"the key lacks the admin scope (insufficient_scope), or its account is not the owner's (admin_required)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}}}}},"/v1/admin/requests/{request_id}/approve":{"post":{"tags":["admin"],"summary":"Approve a request: make a one-use code and email it. Owner only.","description":"Make a one-use access code for the request, mark it approved and email the code to\nthe person. The code is in this response once, with emailed true or false: when the\nemail did not go out, send the code yourself or approve again, which revokes the\nunused code and sends a new one. A request whose code was already used is 409\nrequest_already_decided. Needs the admin scope and an admin account. MCP:\napprove_access_request.","operationId":"approve_request_v1_admin_requests__request_id__approve_post","parameters":[{"name":"request_id","in":"path","required":true,"schema":{"type":"string","title":"Request Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys","title":"Authorization"},"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccessApprovedOut"}}}},"401":{"description":"missing, invalid, revoked or expired API key (code key_expired)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"404":{"description":"not found on this account","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"409":{"description":"conflict with the account's state","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"413":{"description":"request body over the size cap","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"422":{"description":"invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"500":{"description":"our fault; names the request id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"403":{"description":"the key lacks the admin scope (insufficient_scope), or its account is not the owner's (admin_required)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}}}}},"/v1/admin/requests/{request_id}/reject":{"post":{"tags":["admin"],"summary":"Reject a pending request. Owner only.","description":"Mark a pending access request rejected. Nothing is emailed. A request already\napproved or rejected is 409 request_already_decided (revoke an approved request's code\nwith DELETE /v1/admin/codes/{code_id} instead). Needs the admin scope and an admin\naccount. MCP: reject_access_request.","operationId":"reject_request_v1_admin_requests__request_id__reject_post","parameters":[{"name":"request_id","in":"path","required":true,"schema":{"type":"string","title":"Request Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys","title":"Authorization"},"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccessRejectedOut"}}}},"401":{"description":"missing, invalid, revoked or expired API key (code key_expired)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"404":{"description":"not found on this account","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"409":{"description":"conflict with the account's state","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"413":{"description":"request body over the size cap","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"422":{"description":"invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"500":{"description":"our fault; names the request id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"403":{"description":"the key lacks the admin scope (insufficient_scope), or its account is not the owner's (admin_required)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}}}}},"/v1/admin/codes":{"get":{"tags":["admin"],"summary":"Every access code, never the code itself. Owner only.","description":"Every access code, newest first (at most 500): code_id, code_prefix (the first 7\ncharacters), email (when bound), note, source (owner, request, proof), max_uses, uses,\nexpires_at, revoked_at, created_at and usable. The full code is never shown again\nafter it was made. Needs the admin scope and an admin account. MCP:\nlist_access_codes.","operationId":"list_codes_v1_admin_codes_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys","title":"Authorization"},"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccessCodeList"}}}},"401":{"description":"missing, invalid, revoked or expired API key (code key_expired)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"413":{"description":"request body over the size cap","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"422":{"description":"invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"500":{"description":"our fault; names the request id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"403":{"description":"the key lacks the admin scope (insufficient_scope), or its account is not the owner's (admin_required)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}}}},"post":{"tags":["admin"],"summary":"Make an access code to hand out. The code is in this response only.","description":"Make a beta access code to send however you like. max_uses (1 to 100000, default 1)\nis how many accounts it may create; expires_in_days (1 to 365) is optional; note is\nyour own. email binds it to one person: then only Google sign-in with that verified\nemail redeems it, never REST or MCP; leave it out for a code that works on every door.\nThe full code is in this response and nowhere else, ever. Needs the admin scope and an\nadmin account. MCP: create_access_code.","operationId":"create_code_v1_admin_codes_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys","title":"Authorization"},"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys"}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/AccessCodeIn"},{"type":"null"}],"title":"Body"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccessCodeCreatedOut"}}}},"401":{"description":"missing, invalid, revoked or expired API key (code key_expired)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"413":{"description":"request body over the size cap","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"422":{"description":"invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"500":{"description":"our fault; names the request id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"403":{"description":"the key lacks the admin scope (insufficient_scope), or its account is not the owner's (admin_required)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}}}}},"/v1/admin/codes/{code_id}":{"delete":{"tags":["admin"],"summary":"Revoke an access code. Owner only.","description":"The code admits nobody from now on. Accounts it already created keep working.\nRevoking a revoked code answers the same. 404 access_code_not_found for an unknown id.\nNeeds the admin scope and an admin account. MCP: revoke_access_code.","operationId":"revoke_code_v1_admin_codes__code_id__delete","parameters":[{"name":"code_id","in":"path","required":true,"schema":{"type":"string","title":"Code Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys","title":"Authorization"},"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccessCodeRevokedOut"}}}},"401":{"description":"missing, invalid, revoked or expired API key (code key_expired)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"404":{"description":"not found on this account","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"409":{"description":"conflict with the account's state","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"413":{"description":"request body over the size cap","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"422":{"description":"invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"500":{"description":"our fault; names the request id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"403":{"description":"the key lacks the admin scope (insufficient_scope), or its account is not the owner's (admin_required)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}}}}},"/v1/auth/exchange":{"post":{"tags":["sign-in"],"summary":"Trade a one-time sign-in token for a web API key","description":"The web calls this with the token from /signin/callback#t=... after Google sign-in.\nThe token works once, for 60 seconds. The answer is a new API key named \"web sign-in\"\nwith every scope and a 30-day expiry, the person (`user`) and where to go next\n(`return_to`). An account keeps at most 3 web keys: the oldest is revoked. 409\nkey_limit_reached when the account already holds the most keys it can and none of\nthem is a web key. Free. Called from the web's own origin, the key is also set in the\nsession cookie, which the web then uses instead (FAILURE_MODES.md WS1).","operationId":"auth_exchange_v1_auth_exchange_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExchangeIn"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExchangeOut"}}}},"409":{"description":"conflict with the account's state","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"413":{"description":"request body over the size cap","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"422":{"description":"invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"500":{"description":"our fault; names the request id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"401":{"description":"the token is unknown, used or older than 60 seconds (invalid_exchange_token)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}}}}},"/v1/auth/signout":{"post":{"tags":["sign-in"],"summary":"Sign out: revoke the key making this call","description":"Revokes the calling key and nothing else. A web sign-in key is always revoked;\nany other key keeps the rule that an account's last active key cannot be revoked (409\nlast_active_key). Any key may call it, whatever its scopes. Free. Called from the web's\nown origin, it also clears the session cookie, whatever it answers (FAILURE_MODES.md\nWS8), so a browser can always sign out.","operationId":"auth_signout_v1_auth_signout_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys","title":"Authorization"},"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignoutOut"}}}},"401":{"description":"missing, invalid, revoked or expired API key (code key_expired)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"409":{"description":"conflict with the account's state","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"413":{"description":"request body over the size cap","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"500":{"description":"our fault; names the request id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/auth/admin-signin":{"post":{"tags":["admin"],"summary":"The owner signs in with the admin password. No key.","description":"The owner's password sign-in for hookdetector.com/admin. Send {\"password\": \"...\"};\nthe answer is a web sign-in key (all scopes, 30 days, at most 3 per account) for the\nowner's account, the first email of HD_ADMIN_EMAILS, created on first use exactly as a\nGoogle sign-in would create it. A wrong password is 401 invalid_admin_password. Failed\ntries are limited to 5 per address per 15 minutes and 20 from all addresses per hour\n(429 with Retry-After, the right password included). 503 admin_password_unavailable\nwhen the server holds no valid HD_ADMIN_PASSWORD_HASH or no HD_ADMIN_EMAILS. The\npassword is never stored or logged. Browser only: no MCP tool. From the web's own\norigin the key is also set in the session cookie (FAILURE_MODES.md WS14).","operationId":"admin_signin_v1_auth_admin_signin_post","requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/AdminSigninIn"},{"type":"null"}],"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminSigninOut"}}}},"409":{"description":"conflict with the account's state","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"413":{"description":"request body over the size cap","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"422":{"description":"invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"429":{"description":"rate limited; see Retry-After","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"500":{"description":"our fault; names the request id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"401":{"description":"the password is wrong (invalid_admin_password)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"503":{"description":"password sign-in is not configured on this server (admin_password_unavailable)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}}}}},"/v1/admin/overview":{"get":{"tags":["admin"],"summary":"The whole service at a glance. Owner only.","description":"Counts across every account: accounts and people; runs (total, last 24 hours, last\n7 days, failed in the last 24 hours, in flight); hooks; credits charged (last 24\nhours, 7 days, ever) and every balance summed; access requests pending, usable codes\nand redemptions; the search provider's credits_left, low and accepting_runs (null when\nnot read yet); and the running commit. Needs the admin scope and an admin account.\nMCP: admin_overview.","operationId":"admin_overview_v1_admin_overview_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys","title":"Authorization"},"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminOverviewOut"}}}},"401":{"description":"missing, invalid, revoked or expired API key (code key_expired)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"413":{"description":"request body over the size cap","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"422":{"description":"invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"500":{"description":"our fault; names the request id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"403":{"description":"the key lacks the admin scope (insufficient_scope), or its account is not the owner's (admin_required)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}}}}},"/v1/admin/accounts":{"get":{"tags":["admin"],"summary":"Every account, newest first, searchable. Owner only.","description":"Every account, newest first: id, the person's email and name (null for a key-only\naccount), label, credits, created_at, last_seen_at, runs, hooks, charged_total,\nis_admin, admitted_by (the prefix of the access code that admitted it) and\nkeys_active; total counts every match. Needs the admin scope and an admin account.\nMCP: admin_list_accounts.","operationId":"admin_accounts_v1_admin_accounts_get","parameters":[{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"up to 200 characters: part of an email or label (any case), or the start of an id","title":"Q"},"description":"up to 200 characters: part of an email or label (any case), or the start of an id"},{"name":"limit","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":200,"minimum":1},{"type":"null"}],"description":"page size, 1 to 200 (default 50)","title":"Limit"},"description":"page size, 1 to 200 (default 50)"},{"name":"offset","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":1000000,"minimum":0},{"type":"null"}],"description":"how many to skip","title":"Offset"},"description":"how many to skip"},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys","title":"Authorization"},"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminAccountList"}}}},"401":{"description":"missing, invalid, revoked or expired API key (code key_expired)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"413":{"description":"request body over the size cap","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"422":{"description":"invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"500":{"description":"our fault; names the request id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"403":{"description":"the key lacks the admin scope (insufficient_scope), or its account is not the owner's (admin_required)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}}}}},"/v1/admin/accounts/{account_id}/credits":{"post":{"tags":["admin"],"summary":"Add or remove an account's credits, with a note. Owner only.","description":"Change an account's balance by delta (a whole number from -1,000,000 to 1,000,000,\nnot zero) with a required note (1 to 200 characters). A change that would take the\nbalance below zero is 422 credits_below_zero and nothing changes; an unknown account\nis 404 account_not_found. Every change is audited in the same transaction (GET\n/v1/admin/actions). Needs the admin scope and an admin account. MCP:\nadmin_adjust_credits.","operationId":"admin_credits_v1_admin_accounts__account_id__credits_post","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys","title":"Authorization"},"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys"}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/AdminCreditsIn"},{"type":"null"}],"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminCreditsOut"}}}},"401":{"description":"missing, invalid, revoked or expired API key (code key_expired)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"404":{"description":"not found on this account","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"413":{"description":"request body over the size cap","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"422":{"description":"invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"500":{"description":"our fault; names the request id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"403":{"description":"the key lacks the admin scope (insufficient_scope), or its account is not the owner's (admin_required)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}}}}},"/v1/admin/runs":{"get":{"tags":["admin"],"summary":"Every account's runs, newest first. Owner only.","description":"Research runs across every account, newest first: run_id, account_id, the person's\nemail, kind, topic, language, country, status, requested, hooks (results delivered:\nhooks for a short run, videos for a long or packaging run), reserved, charged, failure_kind, error, created_at and finished_at. Needs the admin scope and an\nadmin account. MCP: admin_list_runs.","operationId":"admin_runs_v1_admin_runs_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":200,"minimum":1},{"type":"null"}],"description":"page size, 1 to 200 (default 50)","title":"Limit"},"description":"page size, 1 to 200 (default 50)"},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"enum":["queued","running","done","failed"],"type":"string"},{"type":"null"}],"description":"only runs with this status","title":"Status"},"description":"only runs with this status"},{"name":"account_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"only this account's","title":"Account Id"},"description":"only this account's"},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys","title":"Authorization"},"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminRunList"}}}},"401":{"description":"missing, invalid, revoked or expired API key (code key_expired)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"413":{"description":"request body over the size cap","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"422":{"description":"invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"500":{"description":"our fault; names the request id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"403":{"description":"the key lacks the admin scope (insufficient_scope), or its account is not the owner's (admin_required)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}}}}},"/v1/admin/actions":{"get":{"tags":["admin"],"summary":"What admins did (sign-ins, credit changes), newest first. Owner only.","description":"The audit: every password sign-in and credit change, newest first, with who\n(actor_email), on which account (target_account_id) and the detail (for credits:\ndelta, note, before and after). Needs the admin scope and an admin account. MCP:\nadmin_list_actions.","operationId":"admin_actions_v1_admin_actions_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":200,"minimum":1},{"type":"null"}],"description":"page size, 1 to 200 (default 50)","title":"Limit"},"description":"page size, 1 to 200 (default 50)"},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys","title":"Authorization"},"description":"Bearer hd_... from POST /v1/accounts or POST /v1/keys"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminActionList"}}}},"401":{"description":"missing, invalid, revoked or expired API key (code key_expired)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"413":{"description":"request body over the size cap","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"422":{"description":"invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"500":{"description":"our fault; names the request id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}},"403":{"description":"the key lacks the admin scope (insufficient_scope), or its account is not the owner's (admin_required)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorOut"}}}}}}}},"components":{"schemas":{"AccessApprovedOut":{"properties":{"request_id":{"type":"string","title":"Request Id"},"email":{"type":"string","title":"Email"},"status":{"type":"string","const":"approved","title":"Status"},"decided_at":{"type":"string","title":"Decided At"},"code_id":{"type":"string","title":"Code Id"},"code_prefix":{"type":"string","title":"Code Prefix"},"code":{"type":"string","title":"Code","description":"the one-use code, shown here once and never again"},"emailed":{"type":"boolean","title":"Emailed","description":"false: the email did not go out; send the code yourself, or approve again for a new one"},"message":{"type":"string","title":"Message"}},"additionalProperties":true,"type":"object","required":["request_id","email","status","decided_at","code_id","code_prefix","code","emailed","message"],"title":"AccessApprovedOut","examples":[{"code":"HD-7K3M-Q9TX","code_id":"3c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e6f","code_prefix":"HD-7K3M","decided_at":"2026-09-23T10:00:00.000000+00:00","email":"you@example.com","emailed":true,"message":"Approved. The code was emailed to you@example.com.","request_id":"9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d","status":"approved"}]},"AccessCodeCreatedOut":{"properties":{"code_id":{"type":"string","title":"Code Id"},"code_prefix":{"type":"string","title":"Code Prefix","description":"the first 7 characters, to tell codes apart"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note"},"source":{"type":"string","enum":["owner","request","proof","other"],"title":"Source"},"max_uses":{"type":"integer","title":"Max Uses"},"uses":{"type":"integer","title":"Uses"},"expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expires At"},"revoked_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Revoked At"},"created_at":{"type":"string","title":"Created At"},"usable":{"type":"boolean","title":"Usable","description":"not revoked, not expired, not used up"},"code":{"type":"string","title":"Code","description":"the code, shown here once and never again"},"message":{"type":"string","title":"Message"}},"additionalProperties":true,"type":"object","required":["code_id","code_prefix","email","note","source","max_uses","uses","expires_at","revoked_at","created_at","usable","code","message"],"title":"AccessCodeCreatedOut","examples":[{"code":"HD-7K3M-Q9TX","code_id":"3c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e6f","code_prefix":"HD-7K3M","created_at":"2026-09-23T10:00:00.000000+00:00","max_uses":5,"message":"This is the only time the full code is shown.","note":"for the podcast crew","source":"owner","usable":true,"uses":0}]},"AccessCodeIn":{"properties":{"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email","description":"bind the code to this person: then only Google sign-in with this verified email redeems it, never REST or MCP. Leave out (the default) for a code anyone holding it may use on any door"},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note","description":"your own note, up to 500 characters"},"max_uses":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Uses","description":"how many accounts it may create, 1 to 100000; 1 when left out"},"expires_in_days":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Expires In Days","description":"1 to 365; never expires when left out"}},"type":"object","title":"AccessCodeIn"},"AccessCodeItem":{"properties":{"code_id":{"type":"string","title":"Code Id"},"code_prefix":{"type":"string","title":"Code Prefix","description":"the first 7 characters, to tell codes apart"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note"},"source":{"type":"string","enum":["owner","request","proof","other"],"title":"Source"},"max_uses":{"type":"integer","title":"Max Uses"},"uses":{"type":"integer","title":"Uses"},"expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expires At"},"revoked_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Revoked At"},"created_at":{"type":"string","title":"Created At"},"usable":{"type":"boolean","title":"Usable","description":"not revoked, not expired, not used up"}},"additionalProperties":true,"type":"object","required":["code_id","code_prefix","email","note","source","max_uses","uses","expires_at","revoked_at","created_at","usable"],"title":"AccessCodeItem","examples":[{"code_id":"3c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e6f","code_prefix":"HD-7K3M","created_at":"2026-09-23T10:00:00.000000+00:00","max_uses":5,"note":"for the podcast crew","source":"owner","usable":true,"uses":1}]},"AccessCodeList":{"properties":{"codes":{"items":{"$ref":"#/components/schemas/AccessCodeItem"},"type":"array","title":"Codes","description":"newest first, at most 500; never the codes themselves"}},"additionalProperties":true,"type":"object","required":["codes"],"title":"AccessCodeList","examples":[{"codes":[{"code_id":"3c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e6f","code_prefix":"HD-7K3M","created_at":"2026-09-23T10:00:00.000000+00:00","max_uses":5,"note":"for the podcast crew","source":"owner","usable":true,"uses":1}]}]},"AccessCodeRevokedOut":{"properties":{"code_id":{"type":"string","title":"Code Id"},"code_prefix":{"type":"string","title":"Code Prefix","description":"the first 7 characters, to tell codes apart"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note"},"source":{"type":"string","enum":["owner","request","proof","other"],"title":"Source"},"max_uses":{"type":"integer","title":"Max Uses"},"uses":{"type":"integer","title":"Uses"},"expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expires At"},"revoked_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Revoked At"},"created_at":{"type":"string","title":"Created At"},"usable":{"type":"boolean","title":"Usable","description":"not revoked, not expired, not used up"},"revoked":{"type":"boolean","title":"Revoked"}},"additionalProperties":true,"type":"object","required":["code_id","code_prefix","email","note","source","max_uses","uses","expires_at","revoked_at","created_at","usable","revoked"],"title":"AccessCodeRevokedOut","examples":[{"code_id":"3c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e6f","code_prefix":"HD-7K3M","created_at":"2026-09-23T10:00:00.000000+00:00","max_uses":5,"note":"for the podcast crew","revoked":true,"revoked_at":"2026-09-23T10:00:00.000000+00:00","source":"owner","usable":false,"uses":1}]},"AccessRejectedOut":{"properties":{"request_id":{"type":"string","title":"Request Id"},"status":{"type":"string","const":"rejected","title":"Status"},"decided_at":{"type":"string","title":"Decided At"}},"additionalProperties":true,"type":"object","required":["request_id","status","decided_at"],"title":"AccessRejectedOut","examples":[{"decided_at":"2026-09-23T10:00:00.000000+00:00","request_id":"9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d","status":"rejected"}]},"AccessRequestIn":{"properties":{"email":{"type":"string","title":"Email","description":"where the code is sent","examples":["you@example.com"]},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note","description":"optional: who you are, what you will use it for (up to 500 characters)","examples":["I run a clipping agency"]}},"type":"object","required":["email"],"title":"AccessRequestIn"},"AccessRequestItem":{"properties":{"request_id":{"type":"string","title":"Request Id"},"email":{"type":"string","title":"Email"},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note"},"status":{"type":"string","enum":["pending","approved","rejected","other"],"title":"Status"},"code_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Code Id","description":"the code made when it was approved"},"code_prefix":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Code Prefix","description":"the first characters of that code"},"created_at":{"type":"string","title":"Created At"},"decided_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Decided At"}},"additionalProperties":true,"type":"object","required":["request_id","email","note","status","code_id","code_prefix","created_at","decided_at"],"title":"AccessRequestItem"},"AccessRequestList":{"properties":{"requests":{"items":{"$ref":"#/components/schemas/AccessRequestItem"},"type":"array","title":"Requests","description":"newest first, at most 500"}},"additionalProperties":true,"type":"object","required":["requests"],"title":"AccessRequestList","examples":[{"requests":[{"created_at":"2026-09-23T10:00:00.000000+00:00","email":"you@example.com","note":"I run a clipping agency","request_id":"9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d","status":"pending"}]}]},"AccessRequestOut":{"properties":{"status":{"type":"string","enum":["pending","emailed"],"title":"Status","description":"pending: waits for the owner's approval, then the code is emailed; emailed: the code is on its way now"},"message":{"type":"string","title":"Message"}},"additionalProperties":true,"type":"object","required":["status","message"],"title":"AccessRequestOut","examples":[{"message":"Thanks. We will email your access code once the request is approved.","status":"pending"}]},"AccountDeletedOut":{"properties":{"deleted":{"type":"boolean","title":"Deleted"},"account_id":{"type":"string","title":"Account Id"},"erased":{"additionalProperties":{"type":"integer"},"type":"object","title":"Erased","description":"rows erased, per kind"},"message":{"type":"string","title":"Message"}},"additionalProperties":true,"type":"object","required":["deleted","account_id","erased","message"],"title":"AccountDeletedOut","examples":[{"account_id":"8f1c2d3e-4b5a-4c6d-8e7f-9a0b1c2d3e4f","deleted":true,"erased":{"api_keys":1,"conversations":2,"decisions":9,"hooks":24,"messages":6,"runs":3},"message":"The account and everything in it are erased."}]},"AdminAccess":{"properties":{"pending_requests":{"type":"integer","title":"Pending Requests"},"active_codes":{"type":"integer","title":"Active Codes","description":"not revoked, not expired, not used up"},"redemptions":{"type":"integer","title":"Redemptions"}},"additionalProperties":true,"type":"object","required":["pending_requests","active_codes","redemptions"],"title":"AdminAccess"},"AdminAccountItem":{"properties":{"id":{"type":"string","title":"Id"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email","description":"the person using it, null for a key-only account"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label"},"credits":{"type":"integer","title":"Credits"},"created_at":{"type":"string","title":"Created At"},"last_seen_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Seen At"},"runs":{"type":"integer","title":"Runs"},"hooks":{"type":"integer","title":"Hooks"},"charged_total":{"type":"integer","title":"Charged Total"},"is_admin":{"type":"boolean","title":"Is Admin"},"admitted_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Admitted By","description":"the prefix of the access code that admitted it, null when none did"},"keys_active":{"type":"integer","title":"Keys Active"}},"additionalProperties":true,"type":"object","required":["id","email","name","label","credits","created_at","last_seen_at","runs","hooks","charged_total","is_admin","admitted_by","keys_active"],"title":"AdminAccountItem"},"AdminAccountList":{"properties":{"total":{"type":"integer","title":"Total","description":"accounts matching q, across every page"},"accounts":{"items":{"$ref":"#/components/schemas/AdminAccountItem"},"type":"array","title":"Accounts","description":"newest first"}},"additionalProperties":true,"type":"object","required":["total","accounts"],"title":"AdminAccountList","examples":[{"accounts":[{"admitted_by":"HD-7K3M","charged_total":63,"created_at":"2026-09-23T10:00:00.000000+00:00","credits":437,"email":"ana@example.com","hooks":24,"id":"8f1c2d3e-4b5a-4c6d-8e7f-9a0b1c2d3e4f","is_admin":false,"keys_active":2,"last_seen_at":"2026-09-23T10:00:00.000000+00:00","name":"Ana","runs":3}],"total":1}]},"AdminActionItem":{"properties":{"id":{"type":"string","title":"Id"},"action":{"type":"string","title":"Action","description":"signin_password, credits or other"},"actor_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Actor Email"},"target_account_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Target Account Id"},"detail":{"additionalProperties":true,"type":"object","title":"Detail","description":"for credits: delta, note, before and after"},"created_at":{"type":"string","title":"Created At"}},"additionalProperties":true,"type":"object","required":["id","action","actor_email","target_account_id","detail","created_at"],"title":"AdminActionItem"},"AdminActionList":{"properties":{"actions":{"items":{"$ref":"#/components/schemas/AdminActionItem"},"type":"array","title":"Actions","description":"newest first"}},"additionalProperties":true,"type":"object","required":["actions"],"title":"AdminActionList","examples":[{"actions":[{"action":"credits","actor_email":"owner@example.com","created_at":"2026-09-23T10:00:00.000000+00:00","detail":{"after":537,"before":437,"delta":100,"note":"refund"},"id":"1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d","target_account_id":"8f1c2d3e-4b5a-4c6d-8e7f-9a0b1c2d3e4f"}]}]},"AdminCredits":{"properties":{"charged_24h":{"type":"integer","title":"Charged 24H","description":"credits charged by runs settled in the last 24 hours"},"charged_7d":{"type":"integer","title":"Charged 7D"},"charged_total":{"type":"integer","title":"Charged Total"},"balance_total":{"type":"integer","title":"Balance Total","description":"every account's balance, summed, now"}},"additionalProperties":true,"type":"object","required":["charged_24h","charged_7d","charged_total","balance_total"],"title":"AdminCredits"},"AdminCreditsIn":{"properties":{"delta":{"type":"integer","maximum":1000000.0,"minimum":-1000000.0,"title":"Delta","description":"credits to add (positive) or remove (negative), not zero","examples":[100]},"note":{"type":"string","maxLength":200,"minLength":1,"title":"Note","description":"why, for the audit (required)","examples":["refund for a failed run"]}},"type":"object","title":"AdminCreditsIn"},"AdminCreditsOut":{"properties":{"account_id":{"type":"string","title":"Account Id"},"credits":{"type":"integer","title":"Credits","description":"the balance after the change"},"delta":{"type":"integer","title":"Delta"}},"additionalProperties":true,"type":"object","required":["account_id","credits","delta"],"title":"AdminCreditsOut","examples":[{"account_id":"8f1c2d3e-4b5a-4c6d-8e7f-9a0b1c2d3e4f","credits":537,"delta":100}]},"AdminHookCounts":{"properties":{"total":{"type":"integer","title":"Total"},"last_24h":{"type":"integer","title":"Last 24H"}},"additionalProperties":true,"type":"object","required":["total","last_24h"],"title":"AdminHookCounts"},"AdminOverviewOut":{"properties":{"accounts":{"type":"integer","title":"Accounts"},"users":{"type":"integer","title":"Users"},"runs":{"$ref":"#/components/schemas/AdminRunCounts"},"hooks":{"$ref":"#/components/schemas/AdminHookCounts"},"credits":{"$ref":"#/components/schemas/AdminCredits"},"access":{"$ref":"#/components/schemas/AdminAccess"},"search":{"$ref":"#/components/schemas/AdminSearch"},"research":{"anyOf":[{"$ref":"#/components/schemas/AdminResearch"},{"type":"null"}],"description":"what research cost and how long it took, by window"},"commit":{"type":"string","title":"Commit"}},"additionalProperties":true,"type":"object","required":["accounts","users","runs","hooks","credits","access","search","commit"],"title":"AdminOverviewOut","examples":[{"access":{"active_codes":5,"pending_requests":3,"redemptions":14},"accounts":42,"commit":"5d3194d0c1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6","credits":{"balance_total":9300,"charged_24h":610,"charged_7d":4100,"charged_total":18000},"hooks":{"last_24h":96,"total":2400},"runs":{"failed_24h":1,"in_flight":2,"last_24h":12,"last_7d":80,"total":310},"search":{"accepting_runs":true,"credits_left":5200,"low":false},"users":17}]},"AdminResearch":{"properties":{"last_24h":{"$ref":"#/components/schemas/AdminResearchWindow"},"last_7d":{"$ref":"#/components/schemas/AdminResearchWindow"}},"additionalProperties":true,"type":"object","required":["last_24h","last_7d"],"title":"AdminResearch"},"AdminResearchWindow":{"properties":{"runs_done":{"type":"integer","title":"Runs Done"},"runs_failed":{"type":"integer","title":"Runs Failed"},"provider_credits":{"type":"integer","title":"Provider Credits","description":"Scrape Creators credits the runs spent, failed ones included"},"provider_credits_per_run":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Provider Credits Per Run","description":"provider credits per finished run; null with none"},"charged":{"type":"integer","title":"Charged","description":"credits charged to customers"},"hooks":{"type":"integer","title":"Hooks"},"median_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Median Seconds","description":"how long a finished run took, median"},"stage_seconds":{"additionalProperties":{"type":"number"},"type":"object","title":"Stage Seconds","description":"median seconds per stage (searching_instagram, searching_tiktok, judging, transcribing, writing)"},"failed_by_kind":{"additionalProperties":{"type":"integer"},"type":"object","title":"Failed By Kind","description":"failed runs by kind: search, model, orphaned, other"}},"additionalProperties":true,"type":"object","required":["runs_done","runs_failed","provider_credits","provider_credits_per_run","charged","hooks","median_seconds","stage_seconds","failed_by_kind"],"title":"AdminResearchWindow"},"AdminRunCounts":{"properties":{"total":{"type":"integer","title":"Total"},"last_24h":{"type":"integer","title":"Last 24H"},"last_7d":{"type":"integer","title":"Last 7D"},"failed_24h":{"type":"integer","title":"Failed 24H"},"in_flight":{"type":"integer","title":"In Flight","description":"queued or running now"}},"additionalProperties":true,"type":"object","required":["total","last_24h","last_7d","failed_24h","in_flight"],"title":"AdminRunCounts"},"AdminRunItem":{"properties":{"run_id":{"type":"string","title":"Run Id"},"account_id":{"type":"string","title":"Account Id"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"},"kind":{"type":"string","title":"Kind","description":"short, long or packaging","default":"short"},"topic":{"type":"string","title":"Topic"},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language"},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country"},"status":{"type":"string","title":"Status","description":"queued, running, done or failed"},"requested":{"type":"integer","title":"Requested"},"hooks":{"type":"integer","title":"Hooks","description":"results delivered: hooks for a short run, videos for a long or packaging run"},"reserved":{"type":"integer","title":"Reserved"},"charged":{"type":"integer","title":"Charged"},"failure_kind":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Failure Kind"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"},"created_at":{"type":"string","title":"Created At"},"finished_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Finished At"}},"additionalProperties":true,"type":"object","required":["run_id","account_id","email","topic","language","country","status","requested","hooks","reserved","charged","failure_kind","error","created_at","finished_at"],"title":"AdminRunItem"},"AdminRunList":{"properties":{"runs":{"items":{"$ref":"#/components/schemas/AdminRunItem"},"type":"array","title":"Runs","description":"every account's runs, newest first"}},"additionalProperties":true,"type":"object","required":["runs"],"title":"AdminRunList","examples":[{"runs":[{"account_id":"8f1c2d3e-4b5a-4c6d-8e7f-9a0b1c2d3e4f","charged":63,"created_at":"2026-09-23T10:00:00.000000+00:00","email":"ana@example.com","finished_at":"2026-09-23T10:00:00.000000+00:00","hooks":10,"requested":10,"reserved":200,"run_id":"8f1c2d3e-4b5a-4c6d-8e7f-9a0b1c2d3e4f","status":"done","topic":"cold plunge recovery"}]}]},"AdminSearch":{"properties":{"credits_left":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Credits Left","description":"our credits with the search provider; null when not read yet"},"low":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Low"},"accepting_runs":{"type":"boolean","title":"Accepting Runs"}},"additionalProperties":true,"type":"object","required":["credits_left","low","accepting_runs"],"title":"AdminSearch"},"AdminSigninIn":{"properties":{"password":{"type":"string","maxLength":256,"minLength":1,"title":"Password","description":"the owner's password","examples":["your password"]}},"type":"object","title":"AdminSigninIn"},"AdminSigninOut":{"properties":{"api_key":{"type":"string","title":"Api Key","description":"a web sign-in key (all scopes, 30 days, at most 3 per account); shown once"},"key_id":{"type":"string","title":"Key Id"},"expires_at":{"type":"string","title":"Expires At"},"user":{"$ref":"#/components/schemas/SignedInUser"}},"additionalProperties":true,"type":"object","required":["api_key","key_id","expires_at","user"],"title":"AdminSigninOut","examples":[{"api_key":"hd_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","expires_at":"2026-09-23T10:00:00.000000+00:00","key_id":"8f1c2d3e-4b5a-4c6d-8e7f-9a0b1c2d3e4f","user":{"email":"owner@example.com","is_admin":true}}]},"ChatIn":{"properties":{"message":{"type":"string","maxLength":4000,"minLength":1,"title":"Message","examples":["hooks about cold plunge recovery"]},"conversation_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Conversation Id"},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"maxLength":60,"title":"Language","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"]},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"pattern":"^[A-Za-z]{2}$","title":"Country","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"]},"posted_within_days":{"type":["integer","null"],"enum":[7,30,90,365,0,null],"title":"Posted Within Days","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."},"seed_hook_ids":{"items":{"type":"string","format":"uuid"},"type":["array","null"],"maxItems":10,"uniqueItems":true,"title":"Seed Hook Ids","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."},"count":{"anyOf":[{"type":"integer","maximum":30.0,"minimum":1.0},{"type":"null"}],"title":"Count","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"},"platforms":{"items":{"type":"string","enum":["tiktok","instagram","youtube"]},"type":["array","null"],"maxItems":3,"minItems":1,"uniqueItems":true,"title":"Platforms","description":"since 2026-10-10: optional, which platforms to search and deliver from, a non-empty list of \"tiktok\", \"instagram\" and \"youtube\" (YouTube Shorts); left out (or all three) searches all three. [\"instagram\"] delivers Instagram reels only. Names are read without regard to case and a repeat counts once; an empty list or any other name is 422 naming platforms. The run echoes it as `platforms`.","examples":[["instagram"]]}},"type":"object","required":["message"],"title":"ChatIn"},"ChatOut":{"properties":{"conversation_id":{"type":"string","title":"Conversation Id"},"action":{"type":"string","enum":["clarify","research","answer"],"title":"Action"},"message":{"type":"string","title":"Message"},"run":{"anyOf":[{"$ref":"#/components/schemas/RunStartedOut"},{"type":"null"}]},"suggestions":{"items":{"type":"string"},"type":"array","maxItems":4,"title":"Suggestions"},"remix":{"anyOf":[{"$ref":"#/components/schemas/RemixOut"},{"type":"null"}],"description":"on an answer that wrote lines from one of this conversation's hooks (\"make it mine\", 1 credit): the full result, labelled; null otherwise"}},"additionalProperties":true,"type":"object","required":["conversation_id","action","message","run","suggestions"],"title":"ChatOut","examples":[{"action":"research","conversation_id":"5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a8b","message":"Researching cold plunge recovery.","run":{"created_at":"2026-09-23T10:00:00.000000+00:00","credits_reserved":200,"requested":10,"run_id":"8f1c2d3e-4b5a-4c6d-8e7f-9a0b1c2d3e4f","status":"queued","topic":"cold plunge recovery"},"suggestions":[]},{"action":"clarify","conversation_id":"5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a8b","message":"Strength, running or recovery?","suggestions":["Strength","Running","Recovery","Search it broadly"]},{"action":"answer","conversation_id":"5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a8b","message":"Hook 1 opens with a surprising contrast.","suggestions":["Compare hooks 1 and 2","More hooks on recovery"]}]},"ConversationList":{"properties":{"conversations":{"items":{"$ref":"#/components/schemas/ConversationSummary"},"type":"array","title":"Conversations"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"}},"additionalProperties":true,"type":"object","required":["conversations","next_cursor"],"title":"ConversationList","examples":[{"conversations":[{"conversation_id":"5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a8b","created_at":"2026-09-23T10:00:00.000000+00:00","messages":2,"title":"cold plunge","updated_at":"2026-09-23T10:00:00.000000+00:00"}]}]},"ConversationOut":{"properties":{"conversation_id":{"type":"string","title":"Conversation Id"},"title":{"type":"string","title":"Title"},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language"},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country"},"posted_within_days":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Posted Within Days","description":"the conversation's recency chip, or null for any time"},"created_at":{"type":"string","title":"Created At"},"updated_at":{"type":"string","title":"Updated At"},"messages":{"items":{"$ref":"#/components/schemas/MessageOut"},"type":"array","title":"Messages","description":"the newest page of messages (all of them in a short conversation), oldest first"},"runs":{"items":{"$ref":"#/components/schemas/RunOut"},"type":"array","title":"Runs","description":"the runs this page's messages point at, and runs attached in the same stretch of time"},"earlier_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Earlier Cursor","description":"pass as `before` for the page of older messages; null when this page reaches the first message"}},"additionalProperties":true,"type":"object","required":["conversation_id","title","created_at","updated_at","messages","runs"],"title":"ConversationOut","examples":[{"conversation_id":"5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a8b","created_at":"2026-09-23T10:00:00.000000+00:00","messages":[{"content":"cold plunge hooks","created_at":"2026-09-23T10:00:00.000000+00:00","role":"user"}],"runs":[],"title":"cold plunge","updated_at":"2026-09-23T10:00:00.000000+00:00"}]},"ConversationSummary":{"properties":{"conversation_id":{"type":"string","title":"Conversation Id"},"title":{"type":"string","title":"Title"},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language"},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country"},"posted_within_days":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Posted Within Days","description":"the conversation's recency chip, or null for any time"},"created_at":{"type":"string","title":"Created At"},"updated_at":{"type":"string","title":"Updated At"},"messages":{"type":"integer","title":"Messages"}},"additionalProperties":true,"type":"object","required":["conversation_id","title","created_at","updated_at","messages"],"title":"ConversationSummary"},"CreditsTodayOut":{"properties":{"day":{"type":"string","title":"Day","description":"the UTC day, YYYY-MM-DD"},"reserved":{"type":"integer","title":"Reserved","description":"credits reserved by every run started this UTC day"},"ceiling":{"type":"integer","title":"Ceiling","description":"past this, new runs are refused with 503 at_capacity until the next UTC midnight"}},"additionalProperties":true,"type":"object","required":["day","reserved","ceiling"],"title":"CreditsTodayOut"},"DecisionClearedOut":{"properties":{"hook_id":{"type":"string","title":"Hook Id"},"verdict":{"type":"null","title":"Verdict","description":"always null: the hook has no verdict now"}},"additionalProperties":true,"type":"object","required":["hook_id","verdict"],"title":"DecisionClearedOut","examples":[{"hook_id":"1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d"}]},"DecisionIn":{"properties":{"verdict":{"type":"string","pattern":"^(keep|reject)$","title":"Verdict","examples":["keep"]},"reason":{"anyOf":[{"type":"string","enum":["off_subject","weak_opening","wrong_language","low_quality","already_have","other"]},{"type":"null"}],"title":"Reason","description":"with verdict \"reject\" only, optional: why it was passed","examples":["off_subject"]},"note":{"anyOf":[{"type":"string","maxLength":280},{"type":"null"}],"title":"Note","description":"with a reason, optional: the reason in your own words"}},"type":"object","required":["verdict"],"title":"DecisionIn"},"DecisionOut":{"properties":{"hook_id":{"type":"string","title":"Hook Id"},"verdict":{"type":"string","title":"Verdict"}},"additionalProperties":true,"type":"object","required":["hook_id","verdict"],"title":"DecisionOut","examples":[{"hook_id":"1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d","verdict":"keep"}]},"DeleteAccountIn":{"properties":{"confirm":{"title":"Confirm","description":"must be exactly \"delete my account\"","examples":["delete my account"]}},"type":"object","title":"DeleteAccountIn"},"EmbedOut":{"properties":{"platform":{"type":"string","title":"Platform"},"video_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Video Id"},"watch_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Watch Url"},"embed_html":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Embed Html"},"thumbnail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Thumbnail","description":"a picture for the card: the provider's thumbnail, else cover_url, else still_url; never empty on a fallback"},"author":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Author"},"kind":{"anyOf":[{"type":"string","enum":["embed","fallback"]},{"type":"string"}],"title":"Kind"},"cached":{"type":"boolean","title":"Cached"},"cover_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cover Url"},"still_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Still Url","description":"the hook's still as a JPEG, served by this API; no key needed"}},"additionalProperties":true,"type":"object","required":["platform","video_id","watch_url","embed_html","thumbnail","author","kind","cached","cover_url"],"title":"EmbedOut","examples":[{"author":"someone","cached":true,"embed_html":"<blockquote ...></blockquote>","kind":"embed","platform":"tiktok","still_url":"https://api.hookdetector.com/v1/hooks/1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d/still","video_id":"7412345678901234567","watch_url":"https://www.tiktok.com/@someone/video/7412345678901234567"}]},"ErrorOut":{"properties":{"detail":{"type":"string","title":"Detail","description":"one sentence a person can read"},"code":{"type":"string","title":"Code","description":"stable machine code; see the Errors section of /llms.txt"},"errors":{"anyOf":[{"items":{"additionalProperties":true,"type":"object"},"type":"array"},{"type":"null"}],"title":"Errors","description":"on a 422 only: the field level list"}},"additionalProperties":true,"type":"object","required":["detail","code"],"title":"ErrorOut","examples":[{"code":"run_not_found","detail":"no such run for this account"}]},"ExchangeIn":{"properties":{"token":{"type":"string","maxLength":200,"minLength":1,"title":"Token","description":"the t value of the /signin/callback#t=... fragment"}},"type":"object","required":["token"],"title":"ExchangeIn"},"ExchangeOut":{"properties":{"api_key":{"type":"string","title":"Api Key","description":"the web key; shown once, send it as Bearer"},"key_id":{"type":"string","title":"Key Id"},"prefix":{"type":"string","title":"Prefix"},"created_at":{"type":"string","title":"Created At"},"expires_at":{"type":"string","title":"Expires At","description":"30 days from now"},"account_id":{"type":"string","title":"Account Id"},"return_to":{"type":"string","title":"Return To","description":"the same-site path to go to next"},"user":{"anyOf":[{"$ref":"#/components/schemas/SignedInUser"},{"type":"null"}]}},"additionalProperties":true,"type":"object","required":["api_key","key_id","prefix","created_at","expires_at","account_id","return_to","user"],"title":"ExchangeOut","examples":[{"account_id":"8f1c2d3e-4b5a-4c6d-8e7f-9a0b1c2d3e4f","api_key":"hd_...","created_at":"2026-09-23T10:00:00.000000+00:00","expires_at":"2026-09-23T10:00:00.000000+00:00","key_id":"8f1c2d3e-4b5a-4c6d-8e7f-9a0b1c2d3e4f","prefix":"hd_AbCdEfGh","return_to":"/chat","user":{"email":"ana@example.com","is_admin":false,"name":"Ana"}}]},"FindDecisionOut":{"properties":{"find_id":{"type":"string","title":"Find Id"},"verdict":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Verdict","description":"keep, reject, or null once cleared"}},"additionalProperties":true,"type":"object","required":["find_id","verdict"],"title":"FindDecisionOut","examples":[{"find_id":"7c8d9e0f-1a2b-4c3d-8e4f-5a6b7c8d9e0f","verdict":"keep"}]},"FindIndexOut":{"properties":{"finds":{"items":{"$ref":"#/components/schemas/FindOut"},"type":"array","title":"Finds"},"count":{"type":"integer","title":"Count","description":"how many are on this page"},"total":{"type":"integer","title":"Total","description":"how many entries match this kind, q and the filters, on any page"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"}},"additionalProperties":true,"type":"object","required":["finds","count","total","next_cursor"],"title":"FindIndexOut","examples":[{"count":1,"finds":[{"age_days":584.0,"analysed":true,"audience":"people curious about cold plunges","baseline_videos":28,"channel":"Someone","channel_handle":"someone","channel_id":"UC0000000000000000000001","channel_median_views":85000,"cut_on_phone":false,"duration_s":1104,"find_id":"7c8d9e0f-1a2b-4c3d-8e4f-5a6b7c8d9e0f","gap":"The picture shows the last day, the title only the rule. What changed in between?","kind":"packaging","language":"English","on_subject":"yes","outlier":28.2,"platform":"youtube","player_url":"https://www.youtube.com/embed/dQw4w9WgXcQ","posted_at":"2025-03-02T15:00:00Z","posted_exact":false,"promise":"You see what a month of it does.","rank":1,"run_id":"8f1c2d3e-4b5a-4c6d-8e7f-9a0b1c2d3e4f","score":2.4,"steal_it":"Show the end state, title the rule.","thumbnail_composition":"The face fills the left half, two yellow words sit top right","thumbnail_devices":["face_closeup","big_text"],"thumbnail_emotion":"shock","thumbnail_subject":"A man in an ice bath, eyes wide","thumbnail_text":"DAY 30","thumbnail_text_role":"adds_to_title","thumbnail_url":"https://i.ytimg.com/vi/dQw4w9WgXcQ/hq720.jpg","title":"I Cold Plunged Every Day For 30 Days","title_device":"challenge","title_overlap":0.0,"title_template":"I {did the thing} Every Day For {time}","video_id":"dQw4w9WgXcQ","views":2400000,"views_per_day":4109.6,"watch_url":"https://www.youtube.com/watch?v=dQw4w9WgXcQ","why":"28x this channel's usual 85K views"}],"total":1}]},"FindOut":{"properties":{"find_id":{"type":"string","format":"uuid","title":"Find Id","description":"the id to keep, reject or read it by; absent on an item of `progress.items`"},"run_id":{"type":"string","format":"uuid","title":"Run Id","description":"the run that found it"},"kind":{"type":"string","enum":["long","packaging"],"title":"Kind","description":"long or packaging"},"rank":{"type":"integer","title":"Rank","description":"its place in the run, from 1: the strongest outlier first"},"verdict":{"type":["string","null"],"enum":["keep","reject",null],"title":"Verdict","description":"keep, reject or null"},"video_id":{"type":"string","title":"Video Id","description":"the YouTube video id"},"platform":{"type":"string","title":"Platform","description":"always \"youtube\""},"title":{"type":"string","title":"Title","description":"the video's title as YouTube shows it, word for word (runs of spaces read as one)"},"channel":{"type":["string","null"],"title":"Channel","description":"the channel's name"},"channel_handle":{"type":["string","null"],"title":"Channel Handle","description":"the channel's @handle, without the @"},"watch_url":{"type":"string","title":"Watch Url","description":"where to watch it"},"player_url":{"type":"string","title":"Player Url","description":"an iframe src that plays it (16:9)"},"thumbnail_url":{"type":"string","title":"Thumbnail Url","description":"the video's thumbnail on YouTube's image host; `thumbnail_urls` lists every size, best first, and a size a video lacks answers 404"},"views":{"type":"integer","title":"Views","description":"views when the run read it"},"likes":{"type":["integer","null"],"title":"Likes","description":"likes, when YouTube gave them"},"comments":{"type":["integer","null"],"title":"Comments","description":"comments, when YouTube gave them"},"age_days":{"type":["number","null"],"title":"Age Days","description":"days since it was posted, when the run read it"},"views_per_day":{"type":["number","null"],"title":"Views Per Day","description":"views divided by age_days"},"baseline_videos":{"type":["integer","null"],"title":"Baseline Videos","description":"how many recent uploads the channel's usual was measured on"},"found_by":{"type":["string","null"],"title":"Found By","description":"the search that surfaced it"},"duration_s":{"type":["integer","null"],"title":"Duration S","description":"length in seconds; always over 180"},"posted_at":{"type":["string","null"],"title":"Posted At","description":"when it was posted (ISO 8601). Exact when `posted_exact` is true; otherwise the latest it can be, from YouTube's \"3 months ago\", and never before 2023 either way"},"posted_exact":{"type":"boolean","title":"Posted Exact","description":"whether posted_at is the real timestamp"},"channel_median_views":{"type":["integer","null"],"title":"Channel Median Views","description":"the channel's usual: the median views of its recent long-form uploads"},"outlier":{"type":["number","null"],"title":"Outlier","description":"views divided by channel_median_views: 28.2 means 28 times what this channel normally gets"},"outlier_score":{"type":"integer","title":"Outlier Score","description":"the video on one fixed scale, 0 to 100: the outlier, its reach, how fresh it is and how hard its viewers reacted, placed between two fixed anchors. Nothing else a run found moves it, so finds from different runs rank together; it moves only with the video's own numbers and age"},"channel_size":{"type":["string","null"],"enum":["small","medium","large",null],"title":"Channel Size","description":"the channel by what its uploads usually get (channel_median_views): small, medium or large"},"seen_before":{"type":"boolean","title":"Seen Before","description":"true when an earlier run of yours on this tab already found this video; a keep or reject made then is already on it"},"analysis_from_earlier_read":{"type":"boolean","title":"Analysis From Earlier Read","description":"true when the opening or the package analysis is the one an earlier run of yours wrote for this video; the numbers are this run's"},"channel_id":{"type":["string","null"],"title":"Channel Id","description":"the channel's YouTube id (UC...)"},"thumbnail_urls":{"items":{"type":"string"},"type":"array","title":"Thumbnail Urls","description":"every thumbnail size, best first; a size a video lacks answers 404"},"score":{"type":["number","null"],"title":"Score","description":"the ranking score outlier_score is scaled from"},"hook_lines":{"type":"integer","title":"Hook Lines","description":"long: how many lines of `intro` the hook reaches, counted from line 1; the last may be only partly the hook, `hook_words` counts exactly"},"hook_end_s":{"type":"number","title":"Hook End S","description":"long: the second the hook ends, to a tenth, never null. Inside a line it is estimated at the opening's own pace"},"hook_start_s":{"type":"number","title":"Hook Start S","description":"long: the second the hook starts: 0 or close to it, later when a sponsor read or a greeting came first"},"hook_words":{"type":"integer","title":"Hook Words","description":"long: the hook's length in words"},"auto_captions":{"type":"boolean","title":"Auto Captions","description":"long: true when the opening carries almost no punctuation (auto captions): its sentences are not marked and names may be misheard"},"about_subject":{"type":["string","null"],"enum":["yes","partly",null],"title":"About Subject","description":"long: whether the video is about the run's subject, read from its title and opening in the call that analyses it: yes, or partly (the subject is one part of it, or it covers a neighbouring subject). Null when the question was not asked (a channel or video link) or not answered. A video the model answers \"no\" for is never delivered"},"hook_type_other":{"type":["string","null"],"title":"Hook Type Other","description":"long: the device in words when hook_type is \"other\""},"title_device_other":{"type":["string","null"],"title":"Title Device Other","description":"packaging: the device in words when title_device is \"other\""},"outlier_tier":{"type":["string","null"],"enum":["outlier","strong","breakout",null],"title":"Outlier Tier","description":"outlier (under 5 times the channel's usual), strong (5 to 10) or breakout (10 or more)"},"likes_per_1k":{"type":["number","null"],"title":"Likes Per 1K","description":"likes for every 1,000 views"},"comments_per_1k":{"type":["number","null"],"title":"Comments Per 1K","description":"comments for every 1,000 views"},"style_match":{"type":["boolean","null"],"title":"Style Match","description":"when the request asked for a kind of opening or title: whether this one is that kind. Null when it asked for none"},"angle":{"type":["string","null"],"title":"Angle","description":"the specific angle the video takes on the subject"},"format":{"type":["string","null"],"title":"Format","description":"long: the kind of video, one of talking_head, voiceover, vlog, tutorial, interview, documentary, challenge, review, story, other"},"opens_with":{"type":["string","null"],"title":"Opens With","description":"long: how the first seconds begin, one of cold_open, greeting, teaser, question, sponsor, other"},"thumbnail_checked":{"type":"boolean","title":"Thumbnail Checked","description":"packaging: true when thumbnail_url is the size YouTube really served and the analysis looked at"},"thumbnail_sha256":{"type":"string","title":"Thumbnail Sha256","description":"packaging: a digest of the picture the analysis looked at; a later run reuses the analysis only while YouTube serves the same picture"},"analysed_at":{"type":"string","title":"Analysed At","description":"packaging: when the thumbnail was looked at (ISO 8601); a reused analysis keeps its own date"},"title_chars":{"type":"integer","title":"Title Chars","description":"packaging: the title's length in characters"},"cut_on_phone":{"type":"boolean","title":"Cut On Phone","description":"packaging: true when the title is longer than 60 characters, where a phone roughly cuts a title. Approximate: YouTube publishes no limit, and the cut moves with the screen, the font and the letters (about 58 to 62)"},"thumbnail_text_role":{"type":["string","null"],"title":"Thumbnail Text Role","description":"packaging: how the words on the thumbnail stand to the title, one of repeats_title, adds_to_title, contrasts_title, no_text. Counted in code from `title_overlap` where that can be (repeats_title at 0.7 or more, adds_to_title under 0.3); between, the model's reading"},"title_overlap":{"type":["number","null"],"title":"Title Overlap","description":"packaging: the share of the thumbnail's words that are also in the title, 0 to 1, counted in code. null when the thumbnail has no words, or its words are in a script the title does not use"},"on_subject":{"type":["string","null"],"enum":["yes","partly","no",null],"title":"On Subject","description":"packaging: whether the look, told the run's subject, judged the package to be about it: yes, or partly (it covers the subject as one part of something broader). A package judged no is never delivered. null when the run was on a channel's or a video's address, when the look gave no usable verdict, or when the analysis is one an earlier run wrote (its subject may have been another)"},"thumbnail_faces":{"type":["integer","null"],"title":"Thumbnail Faces","description":"packaging: how many faces the thumbnail shows"},"thumbnail_colors":{"type":["string","null"],"title":"Thumbnail Colors","description":"packaging: the colours that carry the frame"},"why":{"type":["string","null"],"title":"Why","description":"one line naming the outlier in words"},"language":{"type":["string","null"],"title":"Language","description":"the language spoken, as YouTube names the caption track"},"analysed":{"type":"boolean","title":"Analysed","description":"false when the model could not analyse it: the find is still real, its analysis fields are null"},"hook":{"type":"string","title":"Hook","description":"long: the hook, verbatim: the lines of `intro` the analysis marks as the hook, cut at the last sentence end that keeps it inside 25 seconds and 60 words, a leading sponsor read or greeting left out. The whole opening is `intro_text`"},"hook_seconds":{"type":["number","null"],"title":"Hook Seconds","description":"long: the second the hook ends; the same number as `hook_end_s`, kept for older readers"},"intro":{"items":{"type":"object"},"type":"array","title":"Intro","description":"long: the opening as it was said, cut by the transcript's own clock: lines of n (from 1), t (start second, null when the transcript had no clock) and text"},"intro_text":{"type":"string","title":"Intro Text","description":"long: the same words as one string"},"intro_seconds":{"type":"integer","title":"Intro Seconds","description":"long: how much of the opening was read"},"hook_type":{"type":["string","null"],"title":"Hook Type","description":"long: since 2026-10-02: the device the opening uses, one of curiosity_gap, contrarian_claim, specific_number, identity_callout, stakes_threat, promise_payoff, pattern_interrupt, story_open, question, direct_command, other (then `hook_type_other` names it in two to five words). Null on older hooks and when the write-up gave none. A client should treat an unknown value as other."},"mechanism":{"type":["string","null"],"title":"Mechanism","description":"long: why the opening holds a viewer, quoting it word for word"},"promise":{"type":["string","null"],"title":"Promise","description":"what the viewer is told they will get"},"open_loop":{"type":["string","null"],"title":"Open Loop","description":"long: the question the opening leaves unanswered"},"title_link":{"type":["string","null"],"title":"Title Link","description":"long: how the first lines pay off the title"},"beats":{"items":{"type":"object"},"type":"array","title":"Beats","description":"long: the opening in consecutive parts: from and to (line numbers of `intro`), beat (one of hook, stakes, proof, credibility, context, promise, preview, story, payoff, cta, other) and does"},"template":{"type":["string","null"],"title":"Template","description":"long: the hook as a fill-in structure with {placeholders}"},"steal_it":{"type":["string","null"],"title":"Steal It","description":"how another niche reuses the move"},"audience":{"type":["string","null"],"title":"Audience","description":"the viewer it is made for"},"title_device":{"type":["string","null"],"title":"Title Device","description":"packaging: the title's device, one of curiosity_gap, contrarian_claim, specific_number, how_to, listicle, challenge, story, question, warning, comparison, authority, timeliness, identity_callout, other"},"title_template":{"type":["string","null"],"title":"Title Template","description":"packaging: the title as a fill-in structure with {placeholders}"},"thumbnail_text":{"type":["string","null"],"title":"Thumbnail Text","description":"packaging: the words printed on the thumbnail, as a vision model read them; null when none"},"thumbnail_subject":{"type":["string","null"],"title":"Thumbnail Subject","description":"packaging: what the picture shows"},"thumbnail_emotion":{"type":["string","null"],"title":"Thumbnail Emotion","description":"packaging: the feeling it signals"},"thumbnail_composition":{"type":["string","null"],"title":"Thumbnail Composition","description":"packaging: how the frame is built"},"thumbnail_devices":{"items":{"type":"string"},"type":"array","title":"Thumbnail Devices","description":"packaging: up to four of face_closeup, big_text, arrow_or_circle, before_after, number, contrast_color, object_hero, screenshot, split_screen, action_shot, collage, minimal, other"},"gap":{"type":["string","null"],"title":"Gap","description":"packaging: what the thumbnail shows that the title does not say, and the question that leaves"}},"additionalProperties":true,"type":"object","title":"FindOut","description":"One result of a long or packaging run. Sent back exactly as stored: a long find has\nthe opening fields, a packaging find the title and thumbnail fields, and a field one\nkind does not have is absent, never null.","examples":[{"about_subject":"yes","age_days":584.0,"analysed":true,"audience":"people curious about cold plunges","auto_captions":false,"baseline_videos":28,"beats":[{"beat":"hook","does":"admits he nearly quit","from":1,"to":1},{"beat":"promise","does":"holds back the result","from":2,"to":2}],"channel":"Someone","channel_handle":"someone","channel_id":"UC0000000000000000000001","channel_median_views":85000,"duration_s":1104,"find_id":"7c8d9e0f-1a2b-4c3d-8e4f-5a6b7c8d9e0f","hook":"I got into freezing water every single morning for a month, and by day nine I wanted to quit.","hook_end_s":7.4,"hook_lines":1,"hook_seconds":7.4,"hook_start_s":0.2,"hook_type":"story_open","hook_words":18,"intro":[{"n":1,"t":0.2,"text":"I got into freezing water every single morning for a month, and by day nine I wanted to quit."},{"n":2,"t":7.4,"text":"But what happened to my sleep by the end is the reason I am still doing it."}],"intro_seconds":60,"intro_text":"I got into freezing water every single morning for a month, and by day nine I wanted to quit. But what happened to my sleep by the end is the reason I am still doing it.","kind":"long","language":"English","mechanism":"\"By day nine I wanted to quit\" admits the cost before any benefit, so the viewer trusts what follows and waits to hear why he stayed.","open_loop":"What happened to his sleep?","outlier":28.2,"platform":"youtube","player_url":"https://www.youtube.com/embed/dQw4w9WgXcQ","posted_at":"2025-03-02T15:00:00Z","posted_exact":false,"promise":"You will see what a month of cold plunges really changes.","rank":1,"run_id":"8f1c2d3e-4b5a-4c6d-8e7f-9a0b1c2d3e4f","score":2.4,"steal_it":"Open on the moment you nearly gave up, then hold the result back.","template":"I did {hard thing} every day for {time}, and by {early point} I wanted to quit.","thumbnail_url":"https://i.ytimg.com/vi/dQw4w9WgXcQ/hq720.jpg","title":"I Cold Plunged Every Day For 30 Days","title_link":"The first line repeats the title's thirty days and adds the low point.","video_id":"dQw4w9WgXcQ","views":2400000,"views_per_day":4109.6,"watch_url":"https://www.youtube.com/watch?v=dQw4w9WgXcQ","why":"28x this channel's usual 85K views"},{"age_days":584.0,"analysed":true,"audience":"people curious about cold plunges","baseline_videos":28,"channel":"Someone","channel_handle":"someone","channel_id":"UC0000000000000000000001","channel_median_views":85000,"cut_on_phone":false,"duration_s":1104,"find_id":"7c8d9e0f-1a2b-4c3d-8e4f-5a6b7c8d9e0f","gap":"The picture shows the last day, the title only the rule. What changed in between?","kind":"packaging","language":"English","on_subject":"yes","outlier":28.2,"platform":"youtube","player_url":"https://www.youtube.com/embed/dQw4w9WgXcQ","posted_at":"2025-03-02T15:00:00Z","posted_exact":false,"promise":"You see what a month of it does.","rank":1,"run_id":"8f1c2d3e-4b5a-4c6d-8e7f-9a0b1c2d3e4f","score":2.4,"steal_it":"Show the end state, title the rule.","thumbnail_composition":"The face fills the left half, two yellow words sit top right","thumbnail_devices":["face_closeup","big_text"],"thumbnail_emotion":"shock","thumbnail_subject":"A man in an ice bath, eyes wide","thumbnail_text":"DAY 30","thumbnail_text_role":"adds_to_title","thumbnail_url":"https://i.ytimg.com/vi/dQw4w9WgXcQ/hq720.jpg","title":"I Cold Plunged Every Day For 30 Days","title_device":"challenge","title_overlap":0.0,"title_template":"I {did the thing} Every Day For {time}","video_id":"dQw4w9WgXcQ","views":2400000,"views_per_day":4109.6,"watch_url":"https://www.youtube.com/watch?v=dQw4w9WgXcQ","why":"28x this channel's usual 85K views"}]},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"HealthDeepOut":{"properties":{"status":{"type":"string","title":"Status"},"version":{"type":"string","title":"Version"},"database":{"additionalProperties":true,"type":"object","title":"Database"},"schema":{"additionalProperties":true,"type":"object","title":"Schema"},"providers":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Providers"},"fallback_providers":{"anyOf":[{"additionalProperties":{"type":"boolean"},"type":"object"},{"type":"null"}],"title":"Fallback Providers","description":"optional providers, configured or not (never a value): anthropic, the model fallback. Unlike providers, a missing one never makes status degraded"},"provider_status":{"anyOf":[{"$ref":"#/components/schemas/ProviderStatusOut"},{"type":"null"}]},"model":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Model","description":"the language model plan, names only: {\"primary\": \"anthropic\" or \"openai\", \"name\" (e.g. claude-haiku-5-5), \"effort\" (e.g. xhigh, null for OpenAI), \"backup\", \"speech\": the provider that hears dialect clips first, \"speech_backup\": the one behind it, \"anthropic_keys\": how many Anthropic keys are in the chain, \"anthropic_keys_spent\": the chain positions (1 is the main key) this process found out of credit or refused in the last 10 minutes, positions only}"},"commit":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Commit","description":"the git commit this image was built from; \"unknown\" outside a deploy"},"credits_today":{"anyOf":[{"$ref":"#/components/schemas/CreditsTodayOut"},{"type":"null"}],"description":"the global daily research ceiling and how much of it runs started today (UTC) reserved; null when the database is not up"}},"additionalProperties":true,"type":"object","required":["status","version","database","schema","providers","provider_status"],"title":"HealthDeepOut","examples":[{"database":{"latency_ms":3.1,"state":"up","up":true},"fallback_providers":{"anthropic":true},"provider_status":{"model":{"last_success_at":"2026-09-23T10:00:00.000000+00:00","state":"ok"},"refusing":false,"search":{"accepting_runs":true,"credits_checked_at":"2026-09-23T10:00:00.000000+00:00","credits_left":12000,"low":false,"low_threshold":3500,"state":"unknown"},"source":"how recent runs and chat turns ended","window_minutes":30},"providers":{"openai":true,"scrapecreators":true},"schema":{"current":true,"missing":[]},"status":"ok","version":"2.1.0"}]},"HealthOut":{"properties":{"status":{"anyOf":[{"type":"string","enum":["ok","degraded"]},{"type":"string"}],"title":"Status"},"version":{"type":"string","title":"Version"},"commit":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Commit","description":"the git commit this image was built from; \"unknown\" outside a deploy"},"database":{"anyOf":[{"type":"string","enum":["up","busy","down"]},{"type":"string"}],"title":"Database","description":"\"busy\": every pooled connection is in use; the service is loaded, not down, and status stays ok"},"started_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Started At","description":"when this process started (ISO 8601); a new value means a restart"},"runs_in_flight":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Runs In Flight","description":"research runs queued or running across the service right now; null when the database did not answer. An operator restarts only at 0"}},"additionalProperties":true,"type":"object","required":["status","version","database"],"title":"HealthOut","examples":[{"commit":"5d3194d0c1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6","database":"up","runs_in_flight":0,"started_at":"2026-09-23T16:11:02.123456+00:00","status":"ok","version":"2.4.0"}]},"HookOut":{"properties":{"hook_id":{"type":"string","title":"Hook Id"},"rank":{"type":"integer","title":"Rank"},"hook":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Hook","description":"the exact opening line; null when the clip had none"},"catchy_line":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Catchy Line"},"main_idea":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Main Idea"},"why_it_travelled":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Why It Travelled"},"topic":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Topic"},"onscreen_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Onscreen Text","description":"text burned into the video; on hooks found before 2026-09-26, every slide of a photo post in order"},"transcript":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Transcript"},"transcript_kind":{"anyOf":[{"type":"string","enum":["speech","music","none","other"]},{"type":"null"}],"title":"Transcript Kind"},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language","description":"the language or dialect the clip was judged to be in, named in English (\"Moroccan Darija\", not \"Arabic\"); null for hooks found before this field"},"platform":{"anyOf":[{"type":"string","enum":["tiktok","instagram","youtube"]},{"type":"string"}],"title":"Platform","description":"tiktok, instagram or youtube (YouTube Shorts only, never long-form, since 2026-10-01; its watch_url is the Shorts form)"},"creator":{"type":"string","title":"Creator"},"views":{"type":"integer","title":"Views"},"likes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Likes"},"above_floor":{"type":"boolean","title":"Above Floor"},"watch_url":{"type":"string","title":"Watch Url"},"cover_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cover Url"},"slide_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Slide Count","description":"always null on hooks found since 2026-09-26 (no carousels are delivered); the slide count of a TikTok photo post on older hooks"},"found_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Found By"},"caption":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Caption"},"verdict":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Verdict"},"format":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Format","description":"what kind of clip it is: talking_head, podcast, interview, stage (a person on camera talking; the main deck), or voiceover, text_on_screen, broll, skit, lipsync, trending_sound, slideshow, call_recording (a recorded call or voicemail), live_interaction (the creator with real people in the scene, not talking to the viewer), other (never delivered since 2026-10-08). Null on hooks found before 2026-09-29. A client should treat an unknown value as other."},"section":{"type":"string","const":"main","title":"Section","description":"always \"main\": the deck of a person on camera talking. Bonus was removed on 2026-10-08, and bonus hooks older runs stored are no longer returned.","default":"main"},"creator_country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Creator Country","description":"the country of the creator's TikTok account as an ISO 3166-1 alpha-2 code (\"MA\"), as TikTok states it on the clip's search row. It is the account's country, not GPS. Always null on Instagram, which states none, and on hooks found before 2026-09-30. Never guessed."},"place":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Place","description":"the name of the place the creator tagged on the post (at most 80 characters), null when untagged, as most posts are. Where it was posted from, not where the creator lives."},"place_country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Place Country","description":"the tagged place's country as an ISO 3166-1 alpha-2 code, only when the platform states it (TikTok usually does, Instagram rarely). Null otherwise; never guessed from the name or the map position."},"banner":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Banner","description":"since 2026-09-30: the banner hook, the title-style text the creator placed on the video to hook the viewer (a headline, a \"POV:\", a question, a list title), verbatim as one line of at most 200 characters. Null when the clip has none: running subtitles of the speech, usernames, watermarks and a lower third naming the speaker are never a banner. Null on hooks found before 2026-09-30. `onscreen_text` still holds all the text read on screen."},"match":{"anyOf":[{"type":"string","enum":["exact","close"]},{"type":"null"}],"title":"Match","description":"since 2026-09-30, on runs with a language or a country: \"exact\" when the clip is about the subject itself, \"close\" when it is about a neighbouring subject with the same intent (general wealth-building advice for \"getting wealthy in your 20s\"): a close match, delivered after every exact match and to be labelled as one. Null on hooks found before 2026-09-30 and on runs with no language or country, which are judged as before."},"style_match":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Style Match","description":"since 2026-10-04, on a run whose request asked for a kind of opening (\"controversial hooks\", \"question hooks\", \"storytime\"): true when this hook opens that way (its hook_type is one asked), false when it is a strong hook on the subject that opens differently, delivered after the ones that match. Null when no kind was asked."},"signals":{"properties":{"followers":{"type":"integer","description":"the creator's followers"},"shares":{"type":"integer"},"saves":{"type":"integer"},"comments":{"type":"integer"},"posted":{"type":"string","description":"the post date, YYYY-MM-DD, UTC"},"reach":{"type":"number","description":"views over the creator's followers: 12.5 means it reached 12.5 times the creator's own audience"},"shares_per_1k":{"type":"number","description":"shares per 1,000 views"},"saves_per_1k":{"type":"number","description":"saves per 1,000 views"},"opening":{"type":"string","enum":["banger","ok","weak","other"],"description":"how hard the clip's first words stop a scrolling viewer, as the write-up rated them; absent on hooks found before 2026-10-13"},"opening_why":{"type":"string","description":"at most 15 words on why the opening rates as it does"}},"type":["object","null"],"title":"Signals","description":"how far the clip travelled beyond its creator, from the platform's own numbers (TikTok carries all of them, Instagram at most comments and the post date); each key only when the platform stated it; null for a hook with none. Hooks are ranked by views, reach and shares and saves per view together since 2026-09-28"},"posted_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Posted At","description":"when the clip was posted, exact when the platform gives it; null when unknown"},"age_days":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Age Days","description":"days since posted_at, at delivery"},"views_per_day":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Views Per Day","description":"views divided by age_days"},"creator_median_views":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Creator Median Views","description":"the creator's usual views per video: the median of their recent videos, pinned excluded (TikTok may use likes per video as a proxy, said so in outlier_basis)"},"outlier":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Outlier","description":"views divided by the creator's usual views per video (`creator_median_views`): 31.2 means 31 times what this creator normally gets. Null when the creator's baseline was not priced for this run."},"outlier_basis":{"anyOf":[{"type":"string","enum":["median_views","likes_per_video"]},{"type":"null"}],"title":"Outlier Basis","description":"what creator_median_views was measured from, or null"},"duration_s":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Duration S","description":"the clip's length in seconds"},"paid":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Paid","description":"an ad, a paid partnership or branded content, as the platform labels it; null when unknown"},"sound_reuse":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Sound Reuse","description":"videos reusing the clip's sound"},"own_sound":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Own Sound","description":"the sound is the creator's own"},"hashtags":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Hashtags","description":"the post's hashtags, at most 20, without the #"},"virality":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Virality","description":"since 2026-10-01: how viral the clip is, 0 to 1 (0.58 is 58 out of 100), since 2026-10-02 on one fixed scale: the same clip scores the same in every run. Built from the outlier (views over the creator's usual, when known), freshness, travel (shares and saves per 1,000 views), own-sound reuse and a paid penalty; null on hooks found before this field."},"virality_why":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Virality Why","description":"one short line naming the strongest signal behind `virality` (\"31x this creator's usual\", \"1.2M views in 4 days\"); never empty when virality is set; null when unknown"},"hook_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Hook Type","description":"since 2026-10-02: the device the opening uses, one of curiosity_gap, contrarian_claim, specific_number, identity_callout, stakes_threat, promise_payoff, pattern_interrupt, story_open, question, direct_command, other (then `hook_type_other` names it in two to five words). Null on older hooks and when the write-up gave none. A client should treat an unknown value as other."},"hook_type_other":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Hook Type Other","description":"two to five words naming the device when hook_type is other, else null"},"mechanism":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mechanism","description":"since 2026-10-02: why the opening stops the scroll, in at most two short sentences: what the first three seconds make the viewer think or feel, quoting the clip's own words (every quote is checked word for word against what was said or shown), and the open question the clip then pays off. Never a view or follower count. `why_it_travelled` carries the same text. Null on older hooks."},"visual_hook":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Visual Hook","description":"since 2026-10-02: one sentence on what the banner or other on-screen text adds to the spoken opening; null when no on-screen text hooks"},"audience":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Audience","description":"since 2026-10-02: the specific viewer this opening stops, a few words"},"template":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Template","description":"since 2026-10-02: the hook as a fill-in structure of at most 20 words, its subject words replaced by {placeholders}"},"steal_it":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Steal It","description":"since 2026-10-02: one sentence on how a creator in another niche reuses this exact move"},"video_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Video Id"},"player_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Player Url","description":"a 9:16 iframe src (TikTok's player, Instagram's reel embed, YouTube's embed); null for an unusable id or a photo post found before 2026-09-26"},"aspect":{"type":"string","title":"Aspect"},"still_url":{"type":"string","title":"Still Url","description":"the card image as a JPEG, served by this API; no key needed"},"media_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Media Url","description":"our own player's source: put it in a <video src>; it redirects to the clip's original file on TikTok's or Instagram's CDN (no key, nothing stored). null for YouTube (use player_url) and photo posts; a 404 or 503 from it means show player_url instead"}},"additionalProperties":true,"type":"object","required":["hook_id","rank","hook","catchy_line","main_idea","why_it_travelled","topic","onscreen_text","transcript","platform","creator","views","likes","above_floor","watch_url","cover_url","slide_count","found_by","caption","verdict","video_id","player_url","aspect","still_url"],"title":"HookOut","examples":[{"above_floor":true,"age_days":3.5,"aspect":"9:16","audience":"people who already cold plunge","banner":"STOP DOING 10 MINUTES","caption":"cold plunge tips","catchy_line":"Two minutes is the whole trick.","creator":"someone","creator_country":"US","creator_median_views":38000,"duration_s":41,"format":"talking_head","found_by":"search","hashtags":["coldplunge","recovery"],"hook":"Nobody tells you this about cold plunges.","hook_id":"1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d","hook_type":"contrarian_claim","language":"English","likes":90000,"main_idea":"Short exposure beats long.","mechanism":"\"Nobody tells you this\" promises a secret about a routine the viewer already does. The clip pays it off with the two-minute rule.","onscreen_text":"STOP DOING 10 MINUTES","outlier":31.2,"outlier_basis":"median_views","own_sound":true,"paid":false,"platform":"tiktok","player_url":"https://www.tiktok.com/player/v1/7412345678901234567?autoplay=1","posted_at":"2026-09-28T10:00:00+00:00","rank":1,"section":"main","sound_reuse":1200,"steal_it":"Name a routine your viewers already do and promise the thing nobody says about it.","template":"Nobody tells you this about {routine}.","topic":"cold plunge timing","transcript":"Nobody tells you this...","transcript_kind":"speech","video_id":"7412345678901234567","views":1200000,"views_per_day":342857.1,"virality":0.91,"virality_why":"31x this creator's usual","visual_hook":"The banner \"STOP DOING 10 MINUTES\" adds an order that contradicts the usual advice.","watch_url":"https://www.tiktok.com/@someone/video/7412345678901234567","why_it_travelled":"\"Nobody tells you this\" promises a secret about a routine the viewer already does. The clip pays it off with the two-minute rule."}]},"KeepsOut":{"properties":{"kept":{"items":{"$ref":"#/components/schemas/HookOut"},"type":"array","title":"Kept"},"count":{"type":"integer","title":"Count","description":"how many are on this page"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"}},"additionalProperties":true,"type":"object","required":["kept","count","next_cursor"],"title":"KeepsOut","examples":[{"count":1,"kept":[{"above_floor":true,"age_days":3.5,"aspect":"9:16","audience":"people who already cold plunge","banner":"STOP DOING 10 MINUTES","caption":"cold plunge tips","catchy_line":"Two minutes is the whole trick.","creator":"someone","creator_country":"US","creator_median_views":38000,"duration_s":41,"format":"talking_head","found_by":"search","hashtags":["coldplunge","recovery"],"hook":"Nobody tells you this about cold plunges.","hook_id":"1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d","hook_type":"contrarian_claim","language":"English","likes":90000,"main_idea":"Short exposure beats long.","mechanism":"\"Nobody tells you this\" promises a secret about a routine the viewer already does. The clip pays it off with the two-minute rule.","onscreen_text":"STOP DOING 10 MINUTES","outlier":31.2,"outlier_basis":"median_views","own_sound":true,"paid":false,"platform":"tiktok","player_url":"https://www.tiktok.com/player/v1/7412345678901234567?autoplay=1","posted_at":"2026-09-28T10:00:00+00:00","rank":1,"section":"main","sound_reuse":1200,"steal_it":"Name a routine your viewers already do and promise the thing nobody says about it.","template":"Nobody tells you this about {routine}.","topic":"cold plunge timing","transcript":"Nobody tells you this...","transcript_kind":"speech","video_id":"7412345678901234567","views":1200000,"views_per_day":342857.1,"virality":0.91,"virality_why":"31x this creator's usual","visual_hook":"The banner \"STOP DOING 10 MINUTES\" adds an order that contradicts the usual advice.","watch_url":"https://www.tiktok.com/@someone/video/7412345678901234567","why_it_travelled":"\"Nobody tells you this\" promises a secret about a routine the viewer already does. The clip pays it off with the two-minute rule."}]}]},"KeptFindsOut":{"properties":{"kept":{"items":{"$ref":"#/components/schemas/FindOut"},"type":"array","title":"Kept"},"count":{"type":"integer","title":"Count","description":"how many are on this page"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"}},"additionalProperties":true,"type":"object","required":["kept","count","next_cursor"],"title":"KeptFindsOut","examples":[{"count":1,"kept":[{"about_subject":"yes","age_days":584.0,"analysed":true,"audience":"people curious about cold plunges","auto_captions":false,"baseline_videos":28,"beats":[{"beat":"hook","does":"admits he nearly quit","from":1,"to":1},{"beat":"promise","does":"holds back the result","from":2,"to":2}],"channel":"Someone","channel_handle":"someone","channel_id":"UC0000000000000000000001","channel_median_views":85000,"duration_s":1104,"find_id":"7c8d9e0f-1a2b-4c3d-8e4f-5a6b7c8d9e0f","hook":"I got into freezing water every single morning for a month, and by day nine I wanted to quit.","hook_end_s":7.4,"hook_lines":1,"hook_seconds":7.4,"hook_start_s":0.2,"hook_type":"story_open","hook_words":18,"intro":[{"n":1,"t":0.2,"text":"I got into freezing water every single morning for a month, and by day nine I wanted to quit."},{"n":2,"t":7.4,"text":"But what happened to my sleep by the end is the reason I am still doing it."}],"intro_seconds":60,"intro_text":"I got into freezing water every single morning for a month, and by day nine I wanted to quit. But what happened to my sleep by the end is the reason I am still doing it.","kind":"long","language":"English","mechanism":"\"By day nine I wanted to quit\" admits the cost before any benefit, so the viewer trusts what follows and waits to hear why he stayed.","open_loop":"What happened to his sleep?","outlier":28.2,"platform":"youtube","player_url":"https://www.youtube.com/embed/dQw4w9WgXcQ","posted_at":"2025-03-02T15:00:00Z","posted_exact":false,"promise":"You will see what a month of cold plunges really changes.","rank":1,"run_id":"8f1c2d3e-4b5a-4c6d-8e7f-9a0b1c2d3e4f","score":2.4,"steal_it":"Open on the moment you nearly gave up, then hold the result back.","template":"I did {hard thing} every day for {time}, and by {early point} I wanted to quit.","thumbnail_url":"https://i.ytimg.com/vi/dQw4w9WgXcQ/hq720.jpg","title":"I Cold Plunged Every Day For 30 Days","title_link":"The first line repeats the title's thirty days and adds the low point.","video_id":"dQw4w9WgXcQ","views":2400000,"views_per_day":4109.6,"watch_url":"https://www.youtube.com/watch?v=dQw4w9WgXcQ","why":"28x this channel's usual 85K views"}]}]},"KeyAccess":{"properties":{"scopes":{"items":{"type":"string","enum":["read","write","research","admin"]},"type":"array","title":"Scopes"},"credit_limit":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Credit Limit","description":"null: no cap"},"credits_used":{"type":"integer","title":"Credits Used","description":"charged by settled runs started with this key, plus what its runs in flight hold"},"expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expires At","description":"null: never expires"},"expired":{"type":"boolean","title":"Expired"}},"additionalProperties":true,"type":"object","required":["scopes","credit_limit","credits_used","expires_at","expired"],"title":"KeyAccess"},"KeyCreatedOut":{"properties":{"key_id":{"type":"string","title":"Key Id"},"name":{"type":"string","title":"Name"},"prefix":{"type":"string","title":"Prefix"},"api_key":{"type":"string","title":"Api Key","description":"shown this once; store it now"},"created_at":{"type":"string","title":"Created At"}},"additionalProperties":true,"type":"object","required":["key_id","name","prefix","api_key","created_at"],"title":"KeyCreatedOut","examples":[{"api_key":"hd_yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy","created_at":"2026-09-23T10:00:00.000000+00:00","key_id":"1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d","name":"ci runner","prefix":"hd_ZyXwVuTs"}]},"KeyIn":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name","description":"up to 60 characters, to tell your keys apart; defaults to 'API key'","examples":["ci runner"]},"scopes":{"items":{"type":"string","enum":["read","write","research","admin"]},"type":"array","minItems":1,"uniqueItems":true,"title":"Scopes","description":"what the key may do: read, write, research, admin. Left out: all four. Give an agent only what it needs, for example [\"read\", \"write\", \"research\"]","examples":[["read","write","research"]]},"credit_limit":{"type":"integer","maximum":100000.0,"minimum":1.0,"title":"Credit Limit","description":"the most credits runs started with this key may spend over its life, 1 to 100000; left out: no cap","examples":[1000]},"expires_in_days":{"type":"integer","maximum":365.0,"minimum":1.0,"title":"Expires In Days","description":"the key stops working after this many days, 1 to 365; left out: never","examples":[90]}},"type":"object","title":"KeyIn"},"KeyList":{"properties":{"keys":{"items":{"$ref":"#/components/schemas/KeyOut"},"type":"array","title":"Keys"},"scopes":{"items":{"type":"string","enum":["read","write","research","admin"]},"type":"array","title":"Scopes","description":"what the key making this call may do"},"access":{"additionalProperties":{"$ref":"#/components/schemas/KeyAccess"},"type":"object","title":"Access","description":"per key_id: its scopes, cap, spend and expiry"}},"additionalProperties":true,"type":"object","required":["keys","scopes","access"],"title":"KeyList","examples":[{"access":{"original":{"credits_used":63,"expired":false,"scopes":["read","write","research","admin"]}},"keys":[{"created_at":"2026-09-23T10:00:00.000000+00:00","current":true,"key_id":"original","last_used_at":"2026-09-23T10:00:00.000000+00:00","name":"Original key","prefix":"hd_AbCdEfGh"}],"scopes":["read","write","research","admin"]}]},"KeyOut":{"properties":{"key_id":{"type":"string","title":"Key Id"},"name":{"type":"string","title":"Name"},"prefix":{"type":"string","title":"Prefix"},"created_at":{"type":"string","title":"Created At"},"last_used_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Used At"},"current":{"type":"boolean","title":"Current"}},"additionalProperties":true,"type":"object","required":["key_id","name","prefix","created_at","last_used_at","current"],"title":"KeyOut"},"KeyRevokedOut":{"properties":{"key_id":{"type":"string","title":"Key Id"},"revoked":{"type":"boolean","title":"Revoked"},"revoked_at":{"type":"string","title":"Revoked At"},"current":{"type":"boolean","title":"Current"},"message":{"type":"string","title":"Message"}},"additionalProperties":true,"type":"object","required":["key_id","revoked","revoked_at","current","message"],"title":"KeyRevokedOut","examples":[{"current":false,"key_id":"1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d","message":"Revoked. Calls with this key are refused from now on.","revoked":true,"revoked_at":"2026-09-23T10:00:00.000000+00:00"}]},"LocaleOut":{"properties":{"subject":{"type":["string","null"],"title":"Subject","description":"what was searched, read out of the topic"},"language":{"type":["string","null"],"title":"Language","description":"the language or dialect enforced, or null"},"country":{"type":["string","null"],"title":"Country","description":"the creators' country enforced, or null"},"exclude":{"items":{"type":"string"},"type":"array","title":"Exclude","description":"languages, dialects and countries the judges ruled out"}},"additionalProperties":true,"type":"object","title":"LocaleOut"},"LongformIn":{"properties":{"topic":{"type":"string","maxLength":500,"minLength":3,"title":"Topic","description":"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)","examples":["home espresso","https://www.youtube.com/@name"]},"count":{"type":"integer","maximum":30.0,"minimum":1.0,"title":"Count","description":"how many long-form hooks to deliver, 1 to 30","default":8},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"maxLength":60,"title":"Language","description":"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","examples":["Spanish"]},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"pattern":"^[A-Za-z]{2}$","title":"Country","description":"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","examples":["US"]},"min_outlier":{"type":["number","null"],"maximum":100.0,"minimum":1.5,"title":"Min Outlier","description":"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","examples":[3]},"posted_within_days":{"type":["integer","null"],"enum":[30,90,365,730,null],"title":"Posted Within Days","description":"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","examples":[365]},"channel_size":{"type":["string","null"],"enum":["small","medium","large",null],"title":"Channel Size","description":"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","examples":["small"]},"intro_seconds":{"type":["integer","null"],"maximum":120.0,"minimum":20.0,"title":"Intro Seconds","description":"optional, 20 to 120 (default 60): how many seconds of each video's opening to read and return","examples":[60]},"openers":{"type":["integer","null"],"maximum":6.0,"minimum":0.0,"title":"Openers","description":"optional, 0 to 6 (default 3): how many openings to write for the subject from the structure of the real ones; 0 for none","examples":[3]},"reuse":{"type":"boolean","title":"Reuse","description":"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","default":true}},"type":"object","required":["topic"],"title":"LongformIn"},"MeOut":{"properties":{"account_id":{"type":"string","title":"Account Id"},"credits":{"type":"integer","title":"Credits"},"key_prefix":{"type":"string","title":"Key Prefix"},"key_id":{"type":"string","title":"Key Id","description":"the key making this call: \"original\" or a key id"},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label"},"created_at":{"type":"string","title":"Created At"},"scopes":{"items":{"type":"string","enum":["read","write","research","admin"]},"type":"array","title":"Scopes","description":"what the key making this call may do"},"credit_limit":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Credit Limit","description":"the most this key's runs may spend over its life; null is no cap"},"expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expires At","description":"when this key stops working; null is never"},"user":{"anyOf":[{"$ref":"#/components/schemas/SignedInUser"},{"type":"null"}],"description":"the person signed in with Google who uses this account; null for an account made with a key alone"}},"additionalProperties":true,"type":"object","required":["account_id","credits","key_prefix","key_id","label","created_at","scopes","credit_limit","expires_at"],"title":"MeOut","examples":[{"account_id":"8f1c2d3e-4b5a-4c6d-8e7f-9a0b1c2d3e4f","created_at":"2026-09-23T10:00:00.000000+00:00","credits":437,"key_id":"original","key_prefix":"hd_AbCdEfGh","scopes":["read","write","research","admin"]}]},"MediaOut":{"properties":{"url":{"type":"string","title":"Url","description":"the clip's original video file on the platform's CDN; it plays in a <video> on any page and answers range requests"},"expires_in":{"type":"integer","title":"Expires In","description":"seconds a client may keep the address; ask again after"}},"additionalProperties":true,"type":"object","required":["url","expires_in"],"title":"MediaOut","description":"GET /v1/hooks/{hook_id}/media?format=json (FAILURE_MODES.md BT-M17).","examples":[{"expires_in":300,"url":"https://v16m.tiktokcdn-us.com/.../video/tos/.../"}]},"MessageOut":{"properties":{"role":{"type":"string","title":"Role"},"content":{"type":"string","title":"Content"},"run_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Run Id"},"created_at":{"type":"string","title":"Created At"}},"additionalProperties":true,"type":"object","required":["role","content","run_id","created_at"],"title":"MessageOut"},"PackagingIn":{"properties":{"topic":{"type":"string","maxLength":500,"minLength":3,"title":"Topic","description":"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)","examples":["home espresso","https://www.youtube.com/@name"]},"count":{"type":"integer","maximum":30.0,"minimum":1.0,"title":"Count","description":"how many packages to deliver, 1 to 30","default":12},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"maxLength":60,"title":"Language","description":"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","examples":["Spanish"]},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"pattern":"^[A-Za-z]{2}$","title":"Country","description":"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","examples":["US"]},"min_outlier":{"type":["number","null"],"maximum":100.0,"minimum":1.5,"title":"Min Outlier","description":"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","examples":[3]},"posted_within_days":{"type":["integer","null"],"enum":[30,90,365,730,null],"title":"Posted Within Days","description":"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","examples":[365]},"channel_size":{"type":["string","null"],"enum":["small","medium","large",null],"title":"Channel Size","description":"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","examples":["small"]},"ideas":{"type":["integer","null"],"maximum":10.0,"minimum":0.0,"title":"Ideas","description":"optional, 0 to 10 (default 6): how many written packages (a title and a thumbnail concept) to add for the subject; 0 for none","examples":[6]},"reuse":{"type":"boolean","title":"Reuse","description":"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","default":true}},"type":"object","required":["topic"],"title":"PackagingIn"},"PatternOut":{"properties":{"name":{"type":"string","title":"Name","description":"the structure, named in a few words"},"template":{"type":"string","title":"Template","description":"the structure with {placeholders} for the subject-specific words, to fill for another niche"},"why":{"type":"string","title":"Why","description":"what it does to the viewer"},"hook_ids":{"items":{"type":"string"},"type":"array","title":"Hook Ids","description":"the delivered hooks using it"},"count":{"type":"integer","title":"Count","description":"how many: len(hook_ids)"}},"additionalProperties":true,"type":"object","required":["name","template","why","hook_ids","count"],"title":"PatternOut"},"ProgressCounts":{"properties":{"found":{"type":"integer","title":"Found"},"above_floor":{"type":"integer","title":"Above Floor"},"on_topic":{"type":"integer","title":"On Topic"},"read":{"type":"integer","title":"Read"},"held":{"type":"integer","title":"Held"},"written":{"type":"integer","title":"Written"},"wanted":{"type":"integer","title":"Wanted"},"talking":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Talking","description":"clips checked so far that show a person talking (the main deck's kind); since 2026-09-29"},"channels":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Channels","description":"Long form and Packaging: channels measured so far, counting up while they are"},"outliers":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Outliers","description":"Long form and Packaging: candidates that beat their channel by the bar once ranked, counting each channel's other uploads that did; once channels are measured"},"measured":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Measured","description":"Long form and Packaging: videos held against their own channel so far, the set the finished run's report.landscape.videos counts; once a channel is measured (since 2026-10-08)"},"beat":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Beat","description":"Long form and Packaging: how many of those beat the bar, the finished run's report.landscape.outliers; use it, not outliers, for a 'beat their channel' number (since 2026-10-08)"}},"additionalProperties":true,"type":"object","required":["found","above_floor","on_topic","read","held","written","wanted"],"title":"ProgressCounts"},"ProgressFeedEntry":{"properties":{"key":{"type":"string","title":"Key","description":"opaque and stable per clip within the run; never the provider's id"},"platform":{"type":"string","enum":["tiktok","instagram","youtube"],"title":"Platform"},"creator":{"type":"string","title":"Creator"},"views":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Views"},"state":{"type":"string","enum":["found","reading","talking","bonus","passed"],"title":"State","description":"found: in the pool; reading: being read; talking: a person talking, held for the deck; passed: dropped (off subject, other language, not talking, not needed). bonus appears only on runs from before 2026-10-08, when Bonus was removed"},"thumb_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Thumb Url","description":"absolute URL of the clip's cover as a JPEG, no key needed (GET /v1/runs/{run_id}/thumbs/{key}); null when there is none"},"line":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Line","description":"the banner or the opening words, only once the judges passed the clip and it is on its way to delivery"}},"additionalProperties":true,"type":"object","required":["key","platform","creator","views","state","thumb_url","line"],"title":"ProgressFeedEntry"},"ProgressOut":{"properties":{"stage":{"type":"string","title":"Stage","description":"one of: queued, starting, reusing, searching_instagram, searching_tiktok, judging, transcribing, writing, searching_youtube, saving, done, failed, other"},"message":{"type":"string","title":"Message"},"updated_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Updated At"},"counts":{"anyOf":[{"$ref":"#/components/schemas/ProgressCounts"},{"type":"null"}]},"preview":{"anyOf":[{"items":{"$ref":"#/components/schemas/ProgressPreview"},"type":"array"},{"type":"null"}],"title":"Preview"},"feed":{"anyOf":[{"items":{"$ref":"#/components/schemas/ProgressFeedEntry"},"type":"array"},{"type":"null"}],"title":"Feed","description":"the live research wall: the clips the run is working on, newest first, at most 24; only while a run shows one (since 2026-09-30)"},"items":{"anyOf":[{"items":{"additionalProperties":true,"type":"object"},"type":"array"},{"type":"null"}],"title":"Items","description":"on a running long or packaging run: the finds written so far in rank order, each as a card (video_id, kind, rank, title, channel, watch_url, thumbnail_url, views, duration_s, posted_at, posted_exact, age_days, outlier, outlier_score, outlier_tier, style_match, analysed, and hook on Long form, thumbnail_text and thumbnail_faces on Packaging); the whole find, in the shape `finds` carries, arrives with the finished run, which is when this is gone (since 2026-10-07; cards only since 2026-10-08)"},"scan":{"anyOf":[{"$ref":"#/components/schemas/ProgressScan"},{"type":"null"}],"description":"on a running long or packaging run once it has seen a video: the videos it is looking at as tiles, and counts; gone once the run is done or failed (since 2026-10-08)"}},"additionalProperties":true,"type":"object","required":["stage","message","updated_at"],"title":"ProgressOut"},"ProgressPreview":{"properties":{"hook":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Hook"},"platform":{"type":"string","enum":["tiktok","instagram","youtube"],"title":"Platform"},"creator":{"type":"string","title":"Creator"},"views":{"type":"integer","title":"Views"},"topic":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Topic"},"cover_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cover Url"},"onscreen_hook":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Onscreen Hook","description":"the text on screen that opens the clip (its banner, else its first line on screen), null when none was read"},"why":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Why","description":"why the opening holds a viewer, as the finished hook's mechanism says it; null when none"},"hook_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Hook Type","description":"since 2026-10-02: the device the opening uses, one of curiosity_gap, contrarian_claim, specific_number, identity_callout, stakes_threat, promise_payoff, pattern_interrupt, story_open, question, direct_command, other (then `hook_type_other` names it in two to five words). Null on older hooks and when the write-up gave none. A client should treat an unknown value as other."},"watch_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Watch Url","description":"the clip on its platform"},"player_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Player Url","description":"a 9:16 iframe src, the player the finished hook gets; null when it has none"},"thumb_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Thumb Url","description":"the clip's cover as a JPEG, served by this API with no key (the wall's route); null when there is none. Use it rather than cover_url, which is the platform's own address and may not load on your page"}},"additionalProperties":true,"type":"object","required":["hook","platform","creator","views","topic","cover_url"],"title":"ProgressPreview"},"ProgressScan":{"properties":{"tiles":{"items":{"$ref":"#/components/schemas/ProgressScanTile"},"type":"array","title":"Tiles","description":"at most 60, in the order the run first saw each video (oldest first); a tile never changes place. When more exist, the oldest not beat, reading or ready go first"},"lanes":{"additionalProperties":{"type":"integer"},"type":"object","title":"Lanes","description":"counts: found (videos the run holds), on_topic (judged to be about the subject), measured (held against their own channel, the set report.landscape counts), beat (of those, how many beat the bar), reading (being read right now), ready (written up so far)"}},"additionalProperties":true,"type":"object","required":["tiles","lanes"],"title":"ProgressScan"},"ProgressScanTile":{"properties":{"v":{"type":"string","title":"V","description":"the YouTube video id (11 characters of A-Z a-z 0-9 _ -); its thumbnail is https://i.ytimg.com/vi/{v}/mqdefault.jpg"},"s":{"type":"string","title":"S","description":"found: a search returned it; on_topic: the title judge kept it; off_topic: the judge dropped it; measured: its channel's usual is known and it is under the bar; beat: its views are at least the bar times the channel's usual; reading: its transcript (Long form) or thumbnail (Packaging) is being read; ready: written up, it is a result; passed: dropped (no captions, another language, before 2023, unreadable, below the bar once dated, not needed). Read an unknown state as found"},"x":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"X","description":"how many times its channel's usual views it has, to one decimal, once known; else null"}},"additionalProperties":true,"type":"object","required":["v","s","x"],"title":"ProgressScanTile"},"ProviderOut":{"properties":{"state":{"anyOf":[{"type":"string","enum":["ok","refusing","unknown"]},{"type":"string"}],"title":"State"},"last_failure_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Failure At"},"last_success_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Success At"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"},"credits_left":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Credits Left","description":"search only: our credits left with the search provider, from its free balance endpoint read at most every 10 minutes; null until read"},"credits_checked_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Credits Checked At","description":"search only: when credits_left was read (ISO 8601)"},"low":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Low","description":"search only: credits_left is under low_threshold (about a day of use); null until read"},"low_threshold":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Low Threshold","description":"search only: HD_SEARCH_CREDITS_LOW"},"accepting_runs":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Accepting Runs","description":"search only: false while the provider refuses us (HTTP 402, 401 or 403); a new run is then 503 search_unavailable, nothing reserved"}},"additionalProperties":true,"type":"object","required":["state","last_failure_at","last_success_at","message"],"title":"ProviderOut"},"ProviderStatusOut":{"properties":{"model":{"$ref":"#/components/schemas/ProviderOut"},"search":{"$ref":"#/components/schemas/ProviderOut"},"refusing":{"type":"boolean","title":"Refusing","description":"true when either provider reads refusing"},"window_minutes":{"type":"integer","title":"Window Minutes"},"source":{"type":"string","title":"Source"}},"additionalProperties":true,"type":"object","required":["model","search","refusing","window_minutes","source"],"title":"ProviderStatusOut"},"RemixFindIn":{"properties":{"niche":{"type":"string","maxLength":120,"minLength":1,"title":"Niche","description":"your niche, product or audience, as you would say it (1 to 120 characters)","examples":["teeth whitening"]},"n":{"type":"integer","maximum":5.0,"minimum":1.0,"title":"N","description":"how many lines to write, 1 to 5 (default 3)","examples":[3]},"mode":{"type":["string","null"],"enum":["openings","intro",null],"title":"Mode","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","examples":["intro"]},"video_brief":{"type":["string","null"],"maxLength":600,"minLength":10,"title":"Video Brief","description":"required for mode \"intro\", 10 to 600 characters: what your video is about, in a few sentences, in your own words and language. The intro uses only the facts and numbers you write here; anything else becomes a {placeholder}","examples":["A beginner video on why home espresso tastes sour, built around my 18 grams in, 36 grams out recipe."]}},"type":"object","title":"RemixFindIn","description":"POST /v1/finds/{find_id}/remix. Openings or packages: niche and n. Write my intro\n(a long find): mode \"intro\" and video_brief; niche and n are not used."},"RemixFindOut":{"properties":{"find_id":{"type":"string","title":"Find Id"},"kind":{"type":"string","title":"Kind","description":"long or packaging: what was written from"},"niche":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Niche","description":"the niche you gave; null in intro mode"},"n":{"type":"integer","title":"N","description":"how many were written; 1 in intro mode"},"mode":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mode","description":"\"intro\" on a Write my intro answer; absent on openings and packages"},"video_brief":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Video Brief","description":"intro mode: your video's description, as asked (whitespace collapsed)"},"written":{"items":{"$ref":"#/components/schemas/WrittenFromFind"},"type":"array","title":"Written","description":"at most n; one that is the real find's own words is dropped. Intro mode: one object holding the beats"},"label":{"type":"string","title":"Label","description":"always \"Written by Hook Detector from a real opening's structure\" for a long find and \"Written by Hook Detector from a real package's structure\" for a package: written, never found; show the label with them"},"credits_charged":{"type":"integer","title":"Credits Charged","description":"always 1"},"credits":{"type":"integer","title":"Credits","description":"your balance after this call"},"remix_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Remix Id","description":"this remix's id: GET /v1/remixes/{remix_id} reads it back, free"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"}},"additionalProperties":true,"type":"object","required":["find_id","kind","niche","n","written","label","credits_charged","credits"],"title":"RemixFindOut","examples":[{"credits":436,"credits_charged":1,"find_id":"7c8d9e0f-1a2b-4c3d-8e4f-5a6b7c8d9e0f","kind":"packaging","label":"Written by Hook Detector from a real package's structure","n":1,"niche":"teeth whitening","written":[{"kept_structure":"the challenge title and the end state shown","thumbnail":"Your smile filling the left half, two yellow words top right","thumbnail_text":"DAY 30","title":"I Whitened My Teeth Every Day For 30 Days"}]},{"credits":436,"credits_charged":1,"find_id":"7c8d9e0f-1a2b-4c3d-8e4f-5a6b7c8d9e0f","kind":"long","label":"Written by Hook Detector from a real opening's structure","mode":"intro","n":1,"video_brief":"A beginner video on why home espresso tastes sour, built around my 18 grams in, 36 grams out recipe.","written":[{"beats":[{"beat":"hook","does":"the thousandth shot, on camera","from_s":0.0,"placeholders":["home espresso hobby"],"say":"Weeks into my {home espresso hobby}, I finally know why my shots tasted sour.","seconds":12.0,"suggested_on_screen":"SOUR?","to_s":12.0,"words":12,"words_budget":33}],"kept_structure":"the number first, then the proof of effort","mode":"intro","numbers_to_placeholders":0,"placeholders":["home espresso hobby"],"seconds":47.0,"timing":"measured","words_per_second":2.78}]}]},"RemixHookIn":{"properties":{"niche":{"type":"string","maxLength":120,"minLength":1,"title":"Niche","description":"your niche, product or audience, as you would say it (1 to 120 characters)","examples":["teeth whitening"]},"n":{"type":"integer","maximum":5.0,"minimum":1.0,"title":"N","description":"how many lines to write, 1 to 5 (default 3)","examples":[3]},"mode":{"type":["string","null"],"enum":["openings",null],"title":"Mode","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"}},"type":"object","title":"RemixHookIn","description":"POST /v1/hooks/{hook_id}/remix. `mode` exists to refuse \"intro\" in a clear 422."},"RemixList":{"properties":{"remixes":{"items":{"$ref":"#/components/schemas/SavedRemixOut"},"type":"array","title":"Remixes","description":"newest first, at most 20"}},"additionalProperties":true,"type":"object","required":["remixes"],"title":"RemixList"},"RemixOut":{"properties":{"hook_id":{"type":"string","title":"Hook Id"},"niche":{"type":"string","title":"Niche"},"n":{"type":"integer","title":"N"},"written":{"items":{"$ref":"#/components/schemas/WrittenLine"},"type":"array","title":"Written","description":"at most n lines; a line that copies the real hook is dropped"},"label":{"type":"string","title":"Label","description":"always \"Written by Hook Detector from a real hook's structure\": these are written lines, never found hooks; show the label with them"},"credits_charged":{"type":"integer","title":"Credits Charged","description":"always 1"},"credits":{"type":"integer","title":"Credits","description":"your balance after this call"},"remix_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Remix Id","description":"this remix's id: GET /v1/remixes/{remix_id} reads it back, free"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"}},"additionalProperties":true,"type":"object","required":["hook_id","niche","n","written","label","credits_charged","credits"],"title":"RemixOut","examples":[{"credits":436,"credits_charged":1,"hook_id":"1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d","label":"Written by Hook Detector from a real hook's structure","n":2,"niche":"teeth whitening","written":[{"kept_structure":"the secret opener","line":"Nobody tells you this about teeth whitening."},{"kept_structure":"the order to stop","line":"Stop whitening your teeth for 10 minutes."}]}]},"ResearchIn":{"properties":{"topic":{"type":"string","maxLength":500,"minLength":3,"title":"Topic","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.","examples":["short term rental tax strategy"]},"count":{"type":"integer","maximum":30.0,"minimum":1.0,"title":"Count","description":"how many hooks to deliver, 1 to 30","default":10},"conversation_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Conversation Id","description":"attach the run to one of your conversations"},"reuse":{"type":"boolean","title":"Reuse","description":"start from rows you already paid for","default":true},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"maxLength":60,"title":"Language","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.","examples":["Moroccan Darija"]},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"pattern":"^[A-Za-z]{2}$","title":"Country","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.","examples":["MA"]},"posted_within_days":{"type":["integer","null"],"enum":[7,30,90,365,null],"title":"Posted Within Days","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.","examples":[30]},"seed_hook_ids":{"items":{"type":"string","format":"uuid"},"type":["array","null"],"maxItems":10,"uniqueItems":true,"title":"Seed Hook Ids","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."},"platforms":{"items":{"type":"string","enum":["tiktok","instagram","youtube"]},"type":["array","null"],"maxItems":3,"minItems":1,"uniqueItems":true,"title":"Platforms","description":"since 2026-10-10: optional, which platforms to search and deliver from, a non-empty list of \"tiktok\", \"instagram\" and \"youtube\" (YouTube Shorts); left out (or all three) searches all three. [\"instagram\"] delivers Instagram reels only. Names are read without regard to case and a repeat counts once; an empty list or any other name is 422 naming platforms. The run echoes it as `platforms`.","examples":[["instagram"]]}},"type":"object","required":["topic"],"title":"ResearchIn"},"RunList":{"properties":{"runs":{"items":{"$ref":"#/components/schemas/RunSummary"},"type":"array","title":"Runs"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"pass as ?cursor= for the next page; null on the last page"}},"additionalProperties":true,"type":"object","required":["runs","next_cursor"],"title":"RunList","examples":[{"runs":[{"charged":63,"created_at":"2026-09-23T10:00:00.000000+00:00","finished_at":"2026-09-23T10:00:00.000000+00:00","requested":10,"run_id":"8f1c2d3e-4b5a-4c6d-8e7f-9a0b1c2d3e4f","status":"done","topic":"cold plunge recovery"}]}]},"RunOut":{"properties":{"run_id":{"type":"string","title":"Run Id"},"kind":{"type":"string","title":"Kind","description":"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","default":"short"},"topic":{"type":"string","title":"Topic"},"requested":{"type":"integer","title":"Requested"},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language","description":"the language or dialect asked for, as sent, or null for any language"},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country","description":"the country asked for, as an ISO 3166-1 alpha-2 code (upper-cased, UK read as GB), or null for any country"},"status":{"anyOf":[{"type":"string","enum":["queued","running","done","failed"]},{"type":"string"}],"title":"Status"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"on a failed run: why, ending with the request id"},"reserved":{"type":"integer","title":"Reserved","description":"credits held for this run when it started; what it does not use is refunded when it finishes"},"charged":{"type":"integer","title":"Charged","description":"credits deducted from your balance for this run: what it used, never more than `reserved`; 0 while it runs, on a failed run, and on a run that found no hooks within the account's allowance of free empty runs (three a day, 500 credits in all; `outcome` says which)"},"credits_ig":{"type":"integer","title":"Credits Ig","description":"Instagram provider credits used so far, including on a refunded run; what you pay is `charged`"},"credits_tt":{"type":"integer","title":"Credits Tt","description":"TikTok provider credits used so far, including on a refunded run; what you pay is `charged`"},"credits_yt":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Credits Yt","description":"of credits_tt, the provider credits that went to YouTube Shorts (since 2026-10-01; null on older runs); already inside credits_tt, never added again; what you pay is `charged`"},"posted_within_days":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Posted Within Days","description":"the recency window asked for (7, 30, 90 or 365 days), or null"},"seed_hook_ids":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Seed Hook Ids","description":"the seed hooks asked for, or null"},"platforms":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Platforms","description":"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"},"stats":{"$ref":"#/components/schemas/RunStats","description":"how a finished run went; {} until it finishes. Keys can be added; read the ones you know"},"progress":{"$ref":"#/components/schemas/ProgressOut"},"created_at":{"type":"string","title":"Created At"},"finished_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Finished At"},"request_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Request Id","description":"the X-Request-Id of the call that started it"},"conversation_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Conversation Id","description":"the chat conversation this run belongs to, or null for a run started outside one"},"hooks":{"items":{"$ref":"#/components/schemas/HookOut"},"type":"array","title":"Hooks","description":"the main deck: clips where a person on camera is talking, ranked. Since 2026-09-29 it never holds a clip without talking; a run that found fewer talking heads than asked says so in `outcome`"},"bonus":{"items":{"$ref":"#/components/schemas/HookOut"},"type":"array","title":"Bonus","description":"always empty. Bonus (clips without a person talking) was removed on 2026-10-08; the key stays so existing clients keep working.","default":[]},"outcome":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Outcome","description":"on a finished run that delivered fewer hooks than asked for (or none): one plain paragraph on why, what to try next, and whether an empty run was free. On a full run whose hooks under the view floor are there because the country filter set most clips aside: that one sentence, with no advice. On a run that asked for a kind of opening, one more sentence: how many hooks open that way. null on any other run"},"patterns":{"anyOf":[{"items":{"$ref":"#/components/schemas/PatternOut"},"type":"array"},{"type":"null"}],"title":"Patterns","description":"since 2026-10-01: up to 3 opening structures SHARED by at least two of this deck's hooks (a device, never a topic), from the delivered deck (`hooks`), grouped by `hook_type` in code since 2026-10-02 and computed before the run is marked done, in the deck's language, the most shared first and then by the summed virality of their hooks: each `name`, `template` (the structure with {placeholders}), `why` and `hook_ids` (the delivered hooks using it) with `count` (always 2 or more). One cheap model call names the groups; null on older runs, when the call failed on a deck without hook types, and when no structure is shared (then `stats.patterns_note` says so in one line)."},"understood":{"anyOf":[{"$ref":"#/components/schemas/UnderstoodOut"},{"type":"null"}],"description":"since 2026-10-04: what the request was understood to ask, read before the first search: the subject searched and judged (hook words such as \"controversial hooks about\" taken out), its language and country, its core and audience, the neighbours searched beside it, the kinds of opening asked (styles, style_other) and the content asked (wants). Null on older runs and until it is read."},"options":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Options","description":"long and packaging runs only: the options the request set (min_outlier, intro_seconds, openers, ideas); {} when it set none"},"finds":{"anyOf":[{"items":{"$ref":"#/components/schemas/FindOut"},"type":"array"},{"type":"null"}],"title":"Finds","description":"long and packaging runs only (since 2026-10-07): the run's results, ranked, each a real long-form YouTube video that beat its own channel's usual views (`outlier`, never under the run's min_outlier) and was posted in 2023 or later. On a long run each carries the video's opening as it was said (`hook`, `intro`) and why it holds; on a packaging run, what its title and thumbnail do together. When the topic was a video's address the one find is that video, whatever its outlier. [] until the run is done: while it runs, the finds written so far are in `progress.items`, as cards"},"report":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Report","description":"long and packaging runs only, once done; null until then. Long: `patterns`, the opening types at least two of the finds share (hook_type, count, video_ids, template, example); `shape`, how the openings are built, counted from the finds (videos, mapped, hook_seconds, how long the hook runs, and hook_words as medians, beats: each beat with the count of openings using it and the median second it starts_at, and sequence: the order of beats most of them share); and `openers` (hook, then, hook_type, based_on: openings WRITTEN for the subject from those structures, always labelled by `openers_label`, \"Written by Hook Detector from the structure of these real openings\", and never counted as a find). Packaging: `title_patterns` and `thumbnail_patterns` (each name, template or description, why, video_ids, count: what at least two of the packages share), `ideas` (title, thumbnail, thumbnail_text, pattern, based_on: packages WRITTEN for the subject from those patterns, always labelled by `ideas_label`, \"Written by Hook Detector from the patterns in these real packages\", and never counted as a find; an idea over 70 characters, with more than 5 words on its thumbnail, or one that repeats a real title is dropped), `numbers` (the packages counted: title_chars and title_words as medians, titles_with_number, titles_with_question, titles_cut_on_phone (with phone_title_chars, the approximate length a phone cuts a title at), thumbnails_with_text and thumbnail_words over the `analysed` ones, and the title_devices, thumbnail_devices and emotions that repeat, each name and count; and age_cohorts: the same counts for packages posted in the last split_days days (recent) and for older ones, with the undated counted apart) and `playbook_note` (one line when the patterns could not be written). Both kinds: `landscape`, the subject at a glance, counted from the videos the run held against their own channel (videos, channels, bar, outliers and their share, strong, breakouts, small_channel_outliers, recent_outliers, median_views, and beat_2x, beat_5x and beat_10x: how many beat their channel by that much whatever bar the run asked for); null when none was measured"}},"additionalProperties":true,"type":"object","required":["run_id","topic","requested","status","error","reserved","charged","credits_ig","credits_tt","stats","progress","created_at","finished_at","request_id","conversation_id","hooks"],"title":"RunOut","examples":[{"charged":63,"conversation_id":"5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a8b","created_at":"2026-09-23T10:00:00.000000+00:00","credits_ig":30,"credits_tt":33,"finished_at":"2026-09-23T10:00:00.000000+00:00","hooks":[{"above_floor":true,"age_days":3.5,"aspect":"9:16","audience":"people who already cold plunge","banner":"STOP DOING 10 MINUTES","caption":"cold plunge tips","catchy_line":"Two minutes is the whole trick.","creator":"someone","creator_country":"US","creator_median_views":38000,"duration_s":41,"format":"talking_head","found_by":"search","hashtags":["coldplunge","recovery"],"hook":"Nobody tells you this about cold plunges.","hook_id":"1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d","hook_type":"contrarian_claim","language":"English","likes":90000,"main_idea":"Short exposure beats long.","mechanism":"\"Nobody tells you this\" promises a secret about a routine the viewer already does. The clip pays it off with the two-minute rule.","onscreen_text":"STOP DOING 10 MINUTES","outlier":31.2,"outlier_basis":"median_views","own_sound":true,"paid":false,"platform":"tiktok","player_url":"https://www.tiktok.com/player/v1/7412345678901234567?autoplay=1","posted_at":"2026-09-28T10:00:00+00:00","rank":1,"section":"main","sound_reuse":1200,"steal_it":"Name a routine your viewers already do and promise the thing nobody says about it.","template":"Nobody tells you this about {routine}.","topic":"cold plunge timing","transcript":"Nobody tells you this...","transcript_kind":"speech","video_id":"7412345678901234567","views":1200000,"views_per_day":342857.1,"virality":0.91,"virality_why":"31x this creator's usual","visual_hook":"The banner \"STOP DOING 10 MINUTES\" adds an order that contradicts the usual advice.","watch_url":"https://www.tiktok.com/@someone/video/7412345678901234567","why_it_travelled":"\"Nobody tells you this\" promises a secret about a routine the viewer already does. The clip pays it off with the two-minute rule."}],"progress":{"counts":{"above_floor":40,"found":80,"held":10,"on_topic":20,"read":16,"wanted":10,"written":10},"message":"10 hooks ready","stage":"done","updated_at":"2026-09-23T10:00:00.000000+00:00"},"request_id":"a1b2c3","requested":10,"reserved":200,"run_id":"8f1c2d3e-4b5a-4c6d-8e7f-9a0b1c2d3e4f","stats":{},"status":"done","topic":"cold plunge recovery"}]},"RunStartedOut":{"properties":{"run_id":{"type":"string","title":"Run Id"},"status":{"type":"string","title":"Status"},"kind":{"type":"string","title":"Kind","description":"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","default":"short"},"topic":{"type":"string","title":"Topic"},"requested":{"type":"integer","title":"Requested"},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language","description":"the language or dialect asked for, as sent, or null for any language"},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country","description":"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":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Posted Within Days","description":"the recency window asked for (7, 30, 90 or 365 days), or null"},"seed_hook_ids":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Seed Hook Ids","description":"the seed hooks asked for (\"find more like these\"), or null"},"platforms":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Platforms","description":"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":{"type":"integer","title":"Credits Reserved"},"created_at":{"type":"string","title":"Created At"}},"additionalProperties":true,"type":"object","required":["run_id","status","topic","requested","credits_reserved","created_at"],"title":"RunStartedOut","examples":[{"created_at":"2026-09-23T10:00:00.000000+00:00","credits_reserved":200,"requested":10,"run_id":"8f1c2d3e-4b5a-4c6d-8e7f-9a0b1c2d3e4f","status":"queued","topic":"cold plunge recovery"}]},"RunStats":{"properties":{"locale":{"anyOf":[{"$ref":"#/components/schemas/LocaleOut"},{"type":"null"}],"description":"what the first step understood: the subject searched and the locale enforced"},"rejected_after_reading":{"type":"integer","title":"Rejected After Reading","description":"clips the judges refused after reading what they say and show: another language or dialect, or off topic"},"dropped_other_country":{"type":"integer","title":"Dropped Other Country","description":"TikTok clips placed in another country, dropped before any credit was spent on them"},"credits_lent_to_tiktok":{"type":"integer","title":"Credits Lent To Tiktok","description":"credits of Instagram's share that TikTok searched with instead; they are counted in credits_tt, not credits_ig"},"dropped_carousels":{"type":"integer","title":"Dropped Carousels","description":"TikTok photo posts (slideshows) and Instagram carousels met at search and dropped unread, free: a run delivers single videos only since 2026-09-26"},"search_breadth":{"properties":{"phrases":{"additionalProperties":{"type":"integer"},"type":"object","description":"search phrases asked for per round, per platform: 6 each for a request of up to 5 hooks, 30 on Instagram and 12 on TikTok for a larger one"},"instagram_reels":{"additionalProperties":{"type":"integer"},"type":"object","description":"Instagram sweeps that bought the viewless reels route (bought) and that skipped it because the popular route already found enough (skipped)"}},"type":"object","title":"Search Breadth","description":"how widely the run searched, since 2026-09-28"},"rejections":{"properties":{"other_country":{"type":"integer","description":"TikTok clips placed in another country, dropped unread and free, each clip once"},"off_subject_or_locale":{"type":"integer","description":"read, and the judge refused them: not about the subject or, on a run with a language or country, not in it (one verdict answers both)"},"other_language":{"type":"integer","description":"written up, and their own write-up placed them in another language or dialect"},"no_words":{"type":"integer","description":"read, nothing said or shown, on a run whose language rules such a clip out"},"other":{"type":"integer","description":"not checked because a check or a write-up failed"},"languages":{"additionalProperties":{"type":"integer"},"type":"object","description":"the languages the other_language clips were placed in, with counts, at most five plus other"}},"type":"object","title":"Rejections","description":"why clips were refused, each clip in one bucket once; `outcome` says it in words"},"held_back":{"properties":{"proof":{"type":"integer","description":"on the subject, but no sign it travelled: a TikTok clip with fewer views than its creator has followers and no outlier of 2x, a clip under the view floor without proof, or one doing less than its creator's usual; each clip once, none that was delivered after all"},"relevance":{"type":"integer","description":"read, and the judge refused them: not about the subject or, on a run with a language or country, not in it"},"language":{"type":"integer","description":"their own write-up placed them in another language or dialect"},"no_words":{"type":"integer","description":"nothing said or shown, on a run whose language rules such a clip out"},"supply":{"type":"integer","description":"on the subject, 10,000 views or more, never read because the search ended first; counted only when the deck came back short"},"other":{"type":"integer","description":"not checked because a step failed"}},"type":"object","title":"Held Back","description":"what kept clips out of the deck, by reason, each clip once, since 2026-10-08; `outcome` says the largest reasons in one sentence on a short deck"},"credits_waived":{"type":"integer","title":"Credits Waived","description":"provider credits a run that found no hooks used and was not charged; they still count against the key's credit_limit"},"waiver_refused":{"properties":{"limit":{"type":"string","enum":["per_day","lifetime"]},"allowance":{"type":"integer"}},"type":"object","title":"Waiver Refused","description":"on a run that found no hooks and was charged: the allowance of free empty runs that was used up, per_day (runs a UTC day) or lifetime (credits in all)"},"transcription":{"type":"object","title":"Transcription","description":"speech recognition on a dialect request: whether the language asked for is a dialect, TikTok clips whose auto captions (or missing captions) were heard again by OpenAI, the seconds and estimated dollars (ours, never charged to you), failures and clips past the run's ceiling"},"charged":{"type":"integer","title":"Charged","description":"credits deducted from your balance for this run: what it used, never more than `reserved`; 0 while it runs, on a failed run, and on a run that found no hooks within the account's allowance of free empty runs (three a day, 500 credits in all; `outcome` says which)"},"credits_earlier_attempts":{"type":"integer","title":"Credits Earlier Attempts","description":"provider credits spent by attempts before a restart; never charged to you"},"talking":{"properties":{"checked":{"type":"integer","description":"clips checked for a person talking"},"talking":{"type":"integer","description":"of those, a person talking"},"not_talking":{"type":"integer","description":"clips on the subject dropped because nobody talks on camera (since 2026-10-08)"},"vision_calls":{"type":"integer","description":"frame checks it took"},"certain_by_text":{"type":"integer","description":"decided from the words alone, with no frame check"}},"type":"object","title":"Talking","description":"how the run told talking heads (hooks) from clips where nobody talks, since 2026-09-29"},"model":{"type":"object","title":"Model","description":"language model usage for the run"},"patterns_model":{"type":"object","title":"Patterns Model","description":"language model usage of the one patterns call made after delivery (since 2026-10-01); absent when no call was made"},"patterns_note":{"type":"string","title":"Patterns Note","description":"beside null patterns, one honest line: no opening structure is shared by two or more of the deck's hooks (quality round, 2026-10-01); absent otherwise"},"posted_within_days":{"type":"integer","title":"Posted Within Days","description":"the recency window the engine honoured, in days (since 2026-10-01)"},"seeds":{"type":"integer","title":"Seeds","description":"seed hooks the engine steered by (since 2026-10-01)"},"pool_size":{"type":"integer","title":"Pool Size","description":"distinct clips found"},"reused_from_earlier_runs":{"type":"integer","title":"Reused From Earlier Runs","description":"clips taken from your earlier runs, not bought again"},"resumed_from_earlier_attempt":{"type":"integer","title":"Resumed From Earlier Attempt","description":"clips carried over from before a restart"},"above_floor":{"type":"integer","title":"Above Floor","description":"clips above the view floor"},"on_topic_above_floor":{"type":"integer","title":"On Topic Above Floor","description":"of those, the clips judged on topic"},"delivered":{"type":"integer","title":"Delivered","description":"hooks delivered"},"delivered_above_floor":{"type":"integer","title":"Delivered Above Floor","description":"delivered hooks above the view floor"},"undescribed_dropped":{"type":"integer","title":"Undescribed Dropped","description":"chosen clips dropped because they could not be read"},"hooks_not_verbatim":{"type":"integer","title":"Hooks Not Verbatim","description":"opening lines not found word for word in the clip"},"catchy_lines_not_verbatim":{"type":"integer","title":"Catchy Lines Not Verbatim","description":"catchy lines not found word for word"},"catchy_lines_anchored":{"type":"integer","title":"Catchy Lines Anchored","description":"catchy lines matched to the transcript"},"catchy_lines_by_number":{"type":"integer","title":"Catchy Lines By Number","description":"catchy lines picked by number"},"cross_posts_merged":{"type":"integer","title":"Cross Posts Merged","description":"the same clip on both platforms, counted once"},"timings":{"type":"object","title":"Timings","description":"seconds spent per stage"},"calls_by_route":{"type":"object","title":"Calls By Route","description":"provider calls per route"},"latency":{"type":"object","title":"Latency","description":"per provider route: calls, seconds, p50_s and max_s"}},"additionalProperties":true,"type":"object","title":"RunStats","description":"A finished run's stats. Every key is optional: a failed or older run carries fewer,\nand a key added later reaches clients before this model names it."},"RunSummary":{"properties":{"run_id":{"type":"string","title":"Run Id"},"kind":{"type":"string","title":"Kind","description":"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","default":"short"},"topic":{"type":"string","title":"Topic"},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label","description":"for a long or packaging run asked for by a YouTube address: the channel's name or the video's title, once the run knows it. Null for a subject in words"},"requested":{"type":"integer","title":"Requested"},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language","description":"the language or dialect asked for, as sent, or null for any language"},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country","description":"the country asked for, as an ISO 3166-1 alpha-2 code (upper-cased, UK read as GB), or null for any country"},"status":{"type":"string","title":"Status"},"charged":{"type":"integer","title":"Charged","description":"credits deducted from your balance for this run: what it used, never more than `reserved`; 0 while it runs, on a failed run, and on a run that found no hooks within the account's allowance of free empty runs (three a day, 500 credits in all; `outcome` says which)"},"created_at":{"type":"string","title":"Created At"},"finished_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Finished At"},"conversation_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Conversation Id","description":"the chat conversation this run belongs to, or null"}},"additionalProperties":true,"type":"object","required":["run_id","topic","requested","status","charged","created_at","finished_at","conversation_id"],"title":"RunSummary"},"SavedRemixOut":{"properties":{"remix_id":{"type":"string","title":"Remix Id"},"hook_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Hook Id","description":"the hook it was written from, or null"},"find_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Find Id","description":"the find it was written from, or null"},"kind":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Kind","description":"long or packaging, for a find's remix"},"mode":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mode","description":"\"intro\" for a Write my intro remix; absent otherwise"},"video_brief":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Video Brief","description":"a Write my intro remix: the description of the video it was written for"},"niche":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Niche","description":"the niche asked for; null for a Write my intro remix"},"n":{"type":"integer","title":"N"},"written":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Written","description":"as the paid answer held it: lines from a hook, openings or packages from a find, or one intro object (mode intro)"},"label":{"type":"string","title":"Label","description":"written by Hook Detector, never found: show it with them"},"credits_charged":{"type":"integer","title":"Credits Charged"},"created_at":{"type":"string","title":"Created At"}},"additionalProperties":true,"type":"object","required":["remix_id","niche","n","written","label","credits_charged","created_at"],"title":"SavedRemixOut","description":"A paid remix read back (audit F16): what the answer that paid for it held, without\nthe balance."},"ShowcaseHook":{"properties":{"hook_id":{"type":"string","title":"Hook Id"},"still_url":{"type":"string","title":"Still Url","description":"the hook's still as a JPEG, no key needed"},"platform":{"type":"string","enum":["tiktok","instagram","youtube"],"title":"Platform"},"creator":{"type":"string","title":"Creator"},"views":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Views"},"line":{"type":"string","title":"Line","description":"the banner on the video, else its opening line"}},"additionalProperties":true,"type":"object","required":["hook_id","still_url","platform","creator","views","line"],"title":"ShowcaseHook","examples":[{"creator":"someone","hook_id":"1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d","line":"Nobody tells you this about cold plunges.","platform":"tiktok","still_url":"https://api.hookdetector.com/v1/hooks/1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d/still","views":1200000}]},"SignedInUser":{"properties":{"email":{"type":"string","title":"Email"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"avatar_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Avatar Url"},"is_admin":{"type":"boolean","title":"Is Admin"}},"additionalProperties":true,"type":"object","required":["email","name","avatar_url","is_admin"],"title":"SignedInUser"},"SignoutOut":{"properties":{"signed_out":{"type":"boolean","title":"Signed Out"},"key_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Key Id"},"revoked_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Revoked At"},"message":{"type":"string","title":"Message"}},"additionalProperties":true,"type":"object","required":["signed_out","key_id","revoked_at","message"],"title":"SignoutOut","examples":[{"key_id":"8f1c2d3e-4b5a-4c6d-8e7f-9a0b1c2d3e4f","message":"Signed out. This key no longer works.","revoked_at":"2026-09-23T10:00:00.000000+00:00","signed_out":true}]},"SignupIn":{"properties":{"label":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}],"title":"Label","description":"your own note, to tell accounts apart","examples":["my research agent"]},"access_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Access Code","description":"your beta access code, HD-XXXX-XXXX (case and dashes do not matter). Required: without it the answer is 403 access_code_required. Ask for one at hookdetector.com/access.","examples":["HD-7K3M-Q9TX"]}},"type":"object","title":"SignupIn"},"SignupOut":{"properties":{"account_id":{"type":"string","title":"Account Id"},"api_key":{"type":"string","title":"Api Key","description":"the only copy there will ever be; store it now"},"credits":{"type":"integer","title":"Credits"},"created_at":{"type":"string","title":"Created At"},"message":{"type":"string","title":"Message"},"next":{"type":"string","title":"Next"}},"additionalProperties":true,"type":"object","required":["account_id","api_key","credits","created_at","message","next"],"title":"SignupOut","examples":[{"account_id":"8f1c2d3e-4b5a-4c6d-8e7f-9a0b1c2d3e4f","api_key":"hd_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","created_at":"2026-09-23T10:00:00.000000+00:00","credits":500,"message":"This is the only copy of your key.","next":"POST /v1/research"}]},"UnderstoodOut":{"properties":{"subject":{"type":"string","title":"Subject","description":"what the clips must be about, the request's own words without instructions or hook words"},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language"},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country"},"core":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Core","description":"the subject without a qualifier of who it is for"},"audience":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Audience","description":"who that qualifier names"},"neighbours":{"items":{"type":"string"},"type":"array","title":"Neighbours","description":"narrower parts of the subject also searched, delivered as close matches","default":[]},"styles":{"items":{"type":"string"},"type":"array","title":"Styles","description":"the kinds of opening the request asked for, each a hook_type (contrarian_claim for \"controversial\" or \"hot takes\", story_open for \"storytime\", question, specific_number ...), or other (then style_other names it); [] when none was asked","default":[]},"style_other":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Style Other","description":"the asked kind of opening when styles holds \"other\""},"wants":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Wants","description":"what the request asked the content to deliver: advice, story, opinion, explainer, comparison or other; null when it did not say"},"intent":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Intent","description":"what the person who typed the subject wants a clip to give them, one sentence"},"look_alikes":{"items":{"type":"string"},"type":"array","title":"Look Alikes","description":"subjects that share the subject's words but are another subject; never delivered","default":[]},"typed":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Typed","description":"the request as typed, when its spelling was fixed before searching; null otherwise"}},"additionalProperties":true,"type":"object","required":["subject"],"title":"UnderstoodOut"},"UsageDay":{"properties":{"date":{"type":"string","title":"Date"},"runs":{"type":"integer","title":"Runs"},"charged":{"type":"integer","title":"Charged"}},"additionalProperties":true,"type":"object","required":["date","runs","charged"],"title":"UsageDay"},"UsageOut":{"properties":{"credits":{"type":"integer","title":"Credits"},"spent_total":{"type":"integer","title":"Spent Total"},"reserved_now":{"type":"integer","title":"Reserved Now"},"runs_total":{"type":"integer","title":"Runs Total"},"last_30_days":{"items":{"$ref":"#/components/schemas/UsageDay"},"type":"array","title":"Last 30 Days"},"recent":{"items":{"$ref":"#/components/schemas/UsageRun"},"type":"array","title":"Recent"}},"additionalProperties":true,"type":"object","required":["credits","spent_total","reserved_now","runs_total","last_30_days","recent"],"title":"UsageOut","examples":[{"credits":437,"last_30_days":[{"charged":63,"date":"2026-09-23","runs":1}],"recent":[],"reserved_now":0,"runs_total":1,"spent_total":63}]},"UsageRun":{"properties":{"run_id":{"type":"string","title":"Run Id"},"kind":{"type":"string","title":"Kind","description":"short, long or packaging: which kind of research the run was","default":"short"},"topic":{"type":"string","title":"Topic"},"status":{"type":"string","title":"Status"},"reserved":{"type":"integer","title":"Reserved"},"charged":{"type":"integer","title":"Charged"},"created_at":{"type":"string","title":"Created At"},"finished_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Finished At"}},"additionalProperties":true,"type":"object","required":["run_id","topic","status","reserved","charged","created_at","finished_at"],"title":"UsageRun"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"WrittenFromFind":{"properties":{"hook":{"type":"string","title":"Hook","description":"long: what the creator says first"},"then":{"type":["string","null"],"title":"Then","description":"long: what the next half minute has to deliver for the hook to pay off"},"title":{"type":"string","title":"Title","description":"packaging: the written title"},"thumbnail":{"type":"string","title":"Thumbnail","description":"packaging: what the thumbnail shows and how it is framed"},"thumbnail_text":{"type":["string","null"],"title":"Thumbnail Text","description":"packaging: the words to print on it, or null for none"},"kept_structure":{"type":["string","null"],"title":"Kept Structure","description":"what was kept from the real find, in a few words"},"mode":{"type":"string","enum":["intro"],"title":"Mode","description":"intro: present only on a Write my intro answer"},"seconds":{"type":"number","title":"Seconds","description":"intro: where the intro ends, in seconds from the video's start; never past the real opening"},"timing":{"type":"string","enum":["measured","estimated"],"title":"Timing","description":"intro: measured when the real opening's captions carry a clock, estimated (from its words) when they do not"},"words_per_second":{"type":"number","title":"Words Per Second","description":"intro: the real opening's speaking rate the word budgets use"},"beats":{"items":{"properties":{"beat":{"type":"string","description":"hook, stakes, proof, credibility, context, promise, preview, story, payoff, cta or other: the real beat it follows"},"does":{"type":["string","null"],"description":"what the real beat does"},"from_s":{"type":"number"},"to_s":{"type":"number"},"seconds":{"type":"number","description":"to_s minus from_s, from the real opening's own timing"},"words_budget":{"type":"integer","description":"the most words these seconds hold at words_per_second"},"say":{"type":"string","description":"the words to say, with {placeholders} for what only the creator knows"},"words":{"type":"integer","description":"words in say; a placeholder counts one"},"placeholders":{"items":{"type":"string"},"type":"array"},"suggested_on_screen":{"type":["string","null"],"description":"a suggestion, not part of what is said"},"suggested_broll":{"type":["string","null"],"description":"a suggestion, not part of what is said"}},"type":"object"},"type":"array","title":"Beats","description":"intro: one entry per beat of the real opening, in order"},"placeholders":{"items":{"type":"string"},"type":"array","title":"Placeholders","description":"intro: every {placeholder} to fill in, once each"},"numbers_to_placeholders":{"type":"integer","title":"Numbers To Placeholders","description":"intro: how many numbers the model wrote that neither your video_brief nor the real opening's template gave; each is now {number}"}},"additionalProperties":true,"type":"object","title":"WrittenFromFind","description":"One thing written from a find: an opening (hook, then) from a long find, a package\n(title, thumbnail, thumbnail_text) from a packaging find."},"WrittenLine":{"properties":{"line":{"type":"string","title":"Line","description":"the written opening line, one sentence"},"kept_structure":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Kept Structure","description":"what was kept from the real hook, in a few words"}},"additionalProperties":true,"type":"object","required":["line","kept_structure"],"title":"WrittenLine"}}}}