YouTube Transcript in C# with HttpClient and Records

From dotnet run to a printed transcript took about two seconds on my machine, and most of that was the network round trip. No NuGet packages appear anywhere in this post — getting a YouTube transcript in C# is one GET request, and HttpClient plus System.Text.Json have shipped in the box for years. By the end you'll have a complete console app with record types for the response, an async Main, and error handling that reads the API's structured error object instead of guessing from status codes.
Three records
The API returns {"result": {...}} on success and {"error": {...}} on failure, so model the envelope directly:
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);
One Deserialize<Envelope> call handles every response. Whichever property is non-null is your answer. RetryAfterSeconds is nullable because only rate-limit errors carry it.
The program
Targets .NET 8, works as-is on 6 and 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 maps the API's camelCase fields onto PascalCase record properties, so there's not a single [JsonPropertyName] attribute in sight. The url parameter accepts every YouTube URL variant plus bare 11-character ids, and the header can be Authorization: Bearer yt_... instead of x-api-key if that fits your middleware better.
About that maxChars=40000
maxChars caps transcript length; 150000 is both the default and the hard ceiling. If a video's captions run past your cap, Truncated comes back true and you get the first N characters — so check the flag before assuming you have the full text. I set 40000 in the example because these transcripts usually end up in an LLM prompt, and at roughly four characters per token that's a 10k-token budget. If your model calls go through the OpenAI Responses API, there's a way to skip the HTTP plumbing entirely via the MCP server with OpenAI; the Anthropic equivalent is in using transcripts with the Claude API.
Error codes to branch on
ApiError.Code is a stable string: UNAUTHORIZED (401), VALIDATION_ERROR and INVALID_URL (400), VIDEO_NOT_FOUND and TRANSCRIPT_UNAVAILABLE (404), RATE_LIMIT_EXCEEDED (429), YOUTUBE_ERROR (500). The one that surprises people is TRANSCRIPT_UNAVAILABLE: transcripts come from YouTube's caption tracks, and a video whose owner disabled captions has nothing to return — no retry policy fixes that. Pattern match on the code, treat it as a permanent skip.
For a zero-signup test key, GET https://youtube2text.org/api/demo-key — it's shared and capped at 5 videos per month per IP, so it's for experiments only. If Go is also in your stack, the same call in Golang uses the identical envelope trick.
The complete machine-readable reference — every parameter, every error, ready to paste into a system prompt — is at youtube2text.org/api.md.