YouTube-Transkript in C# mit HttpClient und Records

Von dotnet run bis zu einem gedruckten Transkript dauerte es auf meinem Computer etwa zwei Sekunden, und das meiste davon war die Netzwerk-Roundtrip. In diesem Beitrag erscheinen keine NuGet-Pakete — ein YouTube-Transkript in C# zu bekommen ist ein GET-Request, und HttpClient plus System.Text.Json werden seit Jahren in der Box versendet. Am Ende hast du eine komplette Konsolenanwendung mit Record-Typen für die Antwort, ein asynchroner Main und Fehlerbehandlung, die das strukturierte Fehlerobjekt der API liest, statt Vermutungen aus Statuscodes anzustellen.
Drei Records
Die API gibt {"result": {...}} bei Erfolg und {"error": {...}} bei Fehler zurück, also modelliere die Hülle direkt:
public record Transcript(
string VideoId,
string Title,
string PubDate,
string Content,
int ContentSize,
bool Truncated);
public record ApiError(
string Code,
string Message,
int Status,
int? RetryAfterSeconds);
public record Envelope(Transcript? Result, ApiError? Error);
Ein Deserialize<Envelope>-Aufruf handhabt jede Antwort. Welche Eigenschaft nicht-null ist, ist deine Antwort. RetryAfterSeconds ist nullable, weil nur Rate-Limit-Fehler es tragen.
Das Programm
Zielt auf .NET 8, funktioniert so auf 6 und 7:
using System;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;
public static class Program
{
private static readonly HttpClient Http = new();
private static readonly JsonSerializerOptions Json =
new(JsonSerializerDefaults.Web);
public static async Task Main(string[] args)
{
var videoUrl = args.Length > 0
? args[0]
: "https://www.youtube.com/watch?v=dQw4w9WgXcQ";
var request = new HttpRequestMessage(HttpMethod.Get,
"https://youtube2text.org/api/transcribe" +
$"?url={Uri.EscapeDataString(videoUrl)}&maxChars=40000");
request.Headers.Add("x-api-key",
Environment.GetEnvironmentVariable("YT2TEXT_KEY"));
var response = await Http.SendAsync(request);
var envelope = JsonSerializer.Deserialize<Envelope>(
await response.Content.ReadAsStringAsync(), Json);
if (envelope?.Error is { } err)
{
Console.Error.WriteLine($"{err.Code}: {err.Message}");
if (err.RetryAfterSeconds is int wait)
Console.Error.WriteLine($"Retry after {wait}s");
Environment.Exit(1);
}
var t = envelope!.Result!;
Console.WriteLine($"{t.Title} ({t.ContentSize} chars, truncated: {t.Truncated})");
Console.WriteLine(t.Content[..Math.Min(t.Content.Length, 400)]);
}
}
JsonSerializerDefaults.Web ordnet die camelCase-Felder der API den PascalCase-Record-Eigenschaften zu, daher gibt es nicht ein einziges [JsonPropertyName]-Attribut in Sicht. Der url-Parameter akzeptiert jede YouTube-URL-Variante plus nackte 11-stellige IDs, und der Header kann statt x-api-key Authorization: Bearer yt_... sein, wenn das besser zu deinem Middleware passt.
Über dieses maxChars=40000
maxChars begrenzt die Transkriptlänge; 150000 ist sowohl der Standard als auch die harte Obergrenze. Wenn die Untertitel eines Videos deine Obergrenze überschreiten, kommt Truncated mit true zurück und du bekommst die ersten N Zeichen — also überprüfe das Flag, bevor du davon ausgehst, dass du den ganzen Text hast. Ich setze 40000 in dem Beispiel, weil diese Transkripte normalerweise in einem LLM-Prompt landen, und bei ungefähr vier Zeichen pro Token ist das ein 10k-Token-Budget. Wenn deine Model-Aufrufe durch die OpenAI Responses API gehen, gibt es einen Weg, die gesamte HTTP-Verkabelung über den MCP-Server mit OpenAI zu überspringen; das Anthropic-Äquivalent ist in Transkripte mit der Claude API verwenden.
Fehlercodes zum Branchen
ApiError.Code ist eine stabile Zeichenkette: UNAUTHORIZED (401), VALIDATION_ERROR und INVALID_URL (400), VIDEO_NOT_FOUND und TRANSCRIPT_UNAVAILABLE (404), RATE_LIMIT_EXCEEDED (429), YOUTUBE_ERROR (500). Der, der die Leute überrascht, ist TRANSCRIPT_UNAVAILABLE: Transkripte stammen aus Youtubes Untertitelspuren, und ein Video, dessen Besitzer Untertitel deaktiviert hat, hat nichts zurückzugeben — keine Retry-Richtlinie behebt das. Mustere auf den Code ab, behandle es als permanentes Überspringen.
Für einen Testschlüssel ohne Anmeldung GET https://youtube2text.org/api/demo-key — er wird geteilt und auf 5 Videos pro Monat pro IP begrenzt, daher ist er nur für Experimente. Wenn Go auch in deinem Stack ist, nutzt der gleiche Aufruf in Golang den identischen Hüllen-Trick.
Die komplette maschinenlesbare Referenz — jeden Parameter, jeden Fehler, bereit zum Einfügen in eine Systemaufforderung — ist bei youtube2text.org/api.md.