Transcripción de YouTube en C# con HttpClient y records

De dotnet run a una transcripción impresa pasaron unos dos segundos en mi máquina, y la mayor parte fue el viaje de ida y vuelta por la red. En este artículo no aparece ningún paquete de NuGet — obtener una transcripción de YouTube en C# es una petición GET, y HttpClient más System.Text.Json vienen incluidos de serie desde hace años. Al final tendrás una aplicación de consola completa con tipos record para la respuesta, un Main asíncrono y un manejo de errores que lee el objeto de error estructurado de la API en lugar de adivinar a partir de los códigos de estado.
Tres records
La API devuelve {"result": {...}} en caso de éxito y {"error": {...}} en caso de fallo, así que modela el sobre directamente:
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);
Una sola llamada a Deserialize<Envelope> maneja todas las respuestas. La propiedad que no sea null es tu respuesta. RetryAfterSeconds es nullable porque solo los errores de límite de tasa lo traen.
El programa
Apunta a .NET 8, funciona tal cual en 6 y 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 mapea los campos camelCase de la API sobre las propiedades PascalCase de los records, así que no hay ni un solo atributo [JsonPropertyName] a la vista. El parámetro url acepta todas las variantes de URL de YouTube más ids de 11 caracteres a secas, y la cabecera puede ser Authorization: Bearer yt_... en lugar de x-api-key si encaja mejor con tu middleware.
Sobre ese maxChars=40000
maxChars limita la longitud de la transcripción; 150000 es a la vez el valor por defecto y el techo duro. Si los subtítulos de un video superan tu límite, Truncated vuelve como true y recibes los primeros N caracteres — así que comprueba la bandera antes de asumir que tienes el texto completo. Puse 40000 en el ejemplo porque estas transcripciones suelen acabar en un prompt de LLM, y a unos cuatro caracteres por token eso es un presupuesto de 10k tokens. Si tus llamadas al modelo van por la OpenAI Responses API, hay una forma de saltarse la fontanería HTTP por completo vía el servidor MCP con OpenAI; el equivalente de Anthropic está en usar transcripciones con la Claude API.
Códigos de error sobre los que ramificar
ApiError.Code es una cadena estable: UNAUTHORIZED (401), VALIDATION_ERROR e INVALID_URL (400), VIDEO_NOT_FOUND y TRANSCRIPT_UNAVAILABLE (404), RATE_LIMIT_EXCEEDED (429), YOUTUBE_ERROR (500). El que sorprende a la gente es TRANSCRIPT_UNAVAILABLE: las transcripciones salen de las pistas de subtítulos de YouTube, y un video cuyo dueño desactivó los subtítulos no tiene nada que devolver — ninguna política de reintentos arregla eso. Haz pattern matching sobre el código y trátalo como un descarte permanente.
Para una clave de prueba sin registro, haz GET a https://youtube2text.org/api/demo-key — es compartida y está limitada a 5 videos al mes por IP, así que es solo para experimentos. Si Go también está en tu stack, la misma llamada en Golang usa el mismo truco del sobre.
La referencia completa legible por máquina — cada parámetro, cada error, lista para pegar en un system prompt — está en youtube2text.org/api.md.