FrameFetch
For agents → Try it free
Topic in, URL out

YouTube Video Search API

Search YouTube by keyword and get back up to 25 lightweight results — url, title, uploader, durationSec, thumbnail, views — with no login and no video download. It's the missing first step for an agent that only ever gets handed a URL: now it can start from a topic instead. Flat $0.002 per call, YouTube only.

Read the docs Pricing

An extract-only agent is half a tool

Every FrameFetch endpoint before this one needed a URL to start from — fine when a human hands one over, useless when an agent is working from a topic instead ("find me a review of the Framework 16" has no URL in it anywhere). POST /v1/search closes that loop: give it a keyword, get back candidate videos, then call /v1/extract on whichever one is relevant. A search-only tool and an extract-only tool are each half a workflow; together they're the whole thing, in two calls.

One call, a keyword in, results out

Send query (2–200 characters) and an optional limit (1–25, default 10) to POST /v1/search. No frames spec, no fields array — search has its own small, fixed shape. Under the hood it's one yt-dlp search pass: no per-video extraction, no media download, no LLM call.

curl -X POST https://framefetch.net/v1/search \
  -H "Authorization: Bearer <your-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "framework 16 laptop review",
    "limit": 3
  }'
{
  "platform": "youtube",
  "query": "framework 16 laptop review",
  "results": [
    { "url": "https://www.youtube.com/watch?v=...", "title": "Framework 16 Review: Just Josh", "uploader": "Just Josh", "durationSec": 1042, "uploadDate": null, "thumbnail": "https://i.ytimg.com/...", "views": 214830 },
    { "url": "https://www.youtube.com/watch?v=...", "title": "Framework 16 Review", "uploader": "Linus Tech Tips", "durationSec": 891, "uploadDate": null, "thumbnail": "https://i.ytimg.com/...", "views": 1820442 },
    { "url": "https://www.youtube.com/watch?v=...", "title": "Framework 16 Long Term Review", "uploader": "Garett", "durationSec": 763, "uploadDate": null, "thumbnail": "https://i.ytimg.com/...", "views": 58211 }
  ],
  "cost": { "totalMicros": 2000 }
}

This exact query — "framework 16 laptop review", limit 3 — is a real, measured call: 3 results back in about 2.7 seconds (Just Josh, Linus Tech Tips, and Garett, in that order), each with a channel name and duration. Read that as one observation, not a benchmark — a single timed call, not a latency distribution. Titles, view counts, and thumbnail URLs shown here are illustrative of the shape, not re-copied verbatim from that run.

What's in the response
FieldShapeWhat it captures
urlstringThe video's watch URL — feed this straight into /v1/extract
titlestring | nullVideo title, as yt-dlp's search returned it
uploaderstring | nullChannel / uploader name
durationSecnumber | nullVideo length in seconds
uploadDatestring | nullNull in practice. The field exists in the response shape, but yt-dlp's flat search extractor does not report it — expect null on nearly every real result, not an occasional gap. If you need a real upload date, get it from /v1/extract's metadata on the video's URL instead.
thumbnailstring | nullThumbnail image URL
viewsnumber | nullView count at search time

Every field comes straight from yt-dlp's flat-search JSON — nothing is invented, and nothing is back-filled by a second per-video lookup (that's what /v1/extract is for). results is ordered exactly as yt-dlp's search ranked them; FrameFetch never re-sorts. A search with no matches is a normal 200 with results: [], not an error.

The pairing: find, then ask

Search alone gets you a shortlist. The useful move is chaining it into /v1/extract's ask param on the result that looks right — one call to find candidates, one to get a direct answer from the best match, grounded in its transcript with timestamped quotes instead of you reading anything yourself:

curl -X POST https://framefetch.net/v1/extract \
  -H "Authorization: Bearer <your-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "<url from the /v1/search result above>",
    "ask": "Does the reviewer recommend it for programming?"
  }'
"ask": {
  "answer": "Yes — the reviewer says it handles day-to-day coding comfortably, with the usual caveats about battery life under heavy compile loads.",
  "quotes": [ { "t_sec": 312, "text": "for coding and writing, this thing is honestly great" } ],
  "confidence": "medium",
  "based_on": ["transcript"]
}

The ask object above is a schema illustration, not a re-run of the search example — it hasn't been executed against a specific video for this page. What's real is the shape: { answer, quotes, confidence, based_on }, exactly as documented on the question answering page. Two calls, $0.002 + $0.0075 = $0.0095 total, and you went from a topic to a sourced answer without reading a single transcript yourself.

Built for a tool loop, not a search bar
YouTube's own search UI

A page of results for a human to scan

Built for a person scrolling and clicking. There's no stable JSON shape to parse, no per-call cost to budget against, and no clean way to hand a result straight to a second tool call — a program has to scrape a rendered page to get anything structured out of it at all.

FrameFetch /v1/search

Called by a program, in a loop

  • Typed JSON out — { platform, query, results[], cost } — no scraping, no HTML to parse
  • Flat $0.002 per call regardless of limit, itemized in the same cost block every FrameFetch call returns
  • A result's url plugs directly into /v1/extract — same auth, same account, one integration
  • A genuine zero-result search is a normal 200, not something to catch as an error
Flat, honest pricing

One lightweight yt-dlp search pass, regardless of how many results come back — that's why it's priced flat rather than per-result: $0.002 per call, the same price floor a bare metadata-only extract costs. No LLM call, no video download, whether limit is 1 or 25. See the full rate card.

Cached like everything else

Identical query + limit + platform calls hit the same content-hash result cache /v1/extract uses — a repeat search for the same keywords is served from cache at the same $0.002 price floor rather than re-running yt-dlp. Pass "cache": false to force a fresh search (billed the same either way, since $0.002 is already the floor) and re-populate the cache for the next caller.

YouTube only, and that's a stated limit

Search runs on YouTube only, because that's the one platform where yt-dlp's search extractor actually works. platform defaults to "youtube" and accepts no other value today — sending anything else is a typed 400, not a silent fallback or a partial result. A genuine yt-dlp failure (every attempt, proxy and no-proxy, threw) is a distinct 502 SEARCH_FAILED — kept separate from a zero-result search, which is always a normal 200.

Use it from an AI agent (MCP)

FrameFetch ships an MCP server at POST https://framefetch.net/mcp. The framefetch_search tool takes the same query/limit/platform arguments as the HTTP API — identical behavior, so an agent working from a topic instead of a URL has a tool for that first step, not just for the ones after it.

FAQ
What does POST /v1/search return?

Up to limit (1-25, default 10) YouTube results, each with url, title, uploader, durationSec, thumbnail, and views — exactly what yt-dlp's search extractor returns, nothing invented or backfilled. uploadDate is also in the shape but is null in practice: yt-dlp's flat search does not return it, so treat it as effectively unpopulated rather than relying on it.

How is search priced?

A flat $0.002 per call regardless of limit or how many results actually came back, including a genuine zero-result search (a normal 200, not an error). That is the same floor a bare metadata-only extract costs. There is no LLM call and no video download involved in a search — it is a lightweight yt-dlp search pass.

Which platforms does search cover?

YouTube only. That is the one platform where yt-dlp's search extractor works; any other platform value in the request is a typed 400, not a silent fallback.

How do I get more than a URL list back?

Take a result's url from the search response and call POST /v1/extract on it for metadata, transcript, frames, or on-screen text — or add a top-level ask question to that same extract call and get a grounded answer with timestamped quotes instead of parsing anything yourself. Two calls: one to find the video, one to read or question it.

Is search cached?

Yes. Identical query + limit + platform calls are served from the same content-hash result cache /v1/extract uses — a repeat search costs the same $0.002 floor either way, cached or fresh.