HomeBlogTranscribePlansAPI Keys

YouTube-Transkript in C# mit HttpClient und Records

C# Konsolenanwendung, die ein YouTube-Transkript mit System.Text.Json deserialisiert

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.