I built a YouTube-to-text tool, and three things turned out much harder than expected A developer built SummarizeVideoToText, a tool that converts YouTube videos into text workspaces with transcripts, AI summaries, and Q&A panels. The project revealed three unexpected challenges: YouTube's transcript endpoints fail frequently, requiring a chain of providers with careful fallback logic; caching summaries permanently is more cost-effective than regenerating them; and Notion's block-based API has permission quirks that return incomplete objects. The developer also noted that videos without captions cannot be summarized, with audio transcription planned for the future. Someone links a 45-minute conference talk and says "the good part is in the middle somewhere." You want three sentences. You do not want 45 minutes. So I built SummarizeVideoToText: paste a video link, get a text workspace — full transcript, AI summary, timestamped chapters, a mind map, and a Q&A panel you can interrogate about the video. No sign-up needed to try it. That's the pitch. The interesting part is what broke along the way. My first version called one endpoint and assumed a transcript came back. In practice, that endpoint fails constantly — YouTube rotates its internals, some videos need a proof-of-origin token, some tracks exist but not in the language you asked for. What actually works is a chain of providers where each layer falls back to the next: export class ChainProvider implements TranscriptProvider { constructor private readonly providers: TranscriptProvider {} // try each in turn; fall through on failure } The non-obvious part is knowing when not to fall through. Two cases end the chain immediately: invalid url — the link itself is broken. No provider will do better. no transcript — layer one confirmed the page loads fine and has no caption track at all. A paid provider will confirm the same thing and bill you for it.Everything else falls through. That distinction is the difference between a robust chain and a machine that burns API credits to rediscover the same "nope." The honest limitation this leaves: if a video has no captions in any form, there's nothing to summarize. I show that plainly instead of pretending. Audio transcription for YouTube is on the roadmap; TikTok and Instagram already go through AI transcription because they rarely ship captions. I started with Redis and a TTL, like you do. Then I watched the logs: a video would get summarized, sit for a week, the key would expire, someone would open the same URL — and the whole pipeline would run again. New caption fetch, new LLM call, new bill. The realization: a video's content never changes. There is no correctness reason to ever evict a summary. TTL made sense for a hot cache, not for the artifact itself. So it became two layers: Storing a few KB of text forever costs orders of magnitude less than regenerating it once. If your pipeline has an expensive deterministic step, "cache expiry" and "delete the result" should not be the same decision. The user-visible payoff is that opening a video someone else already summarized is instant and costs nobody anything — which is also why I could leave the free tier usable without an account. I wanted "export this whole note to Notion." I assumed I'd POST some Markdown. Notion's API is a block model — every heading, paragraph, and list item is a typed object, and the constraints stack up fast: And the one that cost me an evening of confusion: an integration without "read content" permission gets partial objects back. Creating a page returns an object with an id and no url . Searching returns pages with no properties , so no title. Nothing errors. You just get undefined where you expected a link, and a page titled "" . Two lessons. First, when an API returns a suspiciously empty field, check the permission scope before you check your code. Second, degrade instead of inventing: my first fix put the string " Untitled page " in the UI, which turned a missing title into a confidently wrong one. The real fix was to reconstruct the URL from the ID notion.so/