Did you know that over 500 hours of video are uploaded to YouTube every single minute, and you can automate every second of it from your C# application? In this hands‑on guide I’ll walk you through every step—from the very first “Hello, YouTube!” request all the way to publishing videos, building playlists, and squeezing every millisecond of performance out of Google’s Data API v3. Grab a coffee (or three), fire up Visual Studio, and let’s make your app a YouTube power‑studio!
Prerequisites
Before we touch a line of code, make sure you have:
- Google account with two‑factor enabled (better security = happier subscribers).
- Visual Studio 2022/2025 (Community is fine) or VS Code with the .NET 8 SDK.
- NuGet access – we’ll pull in
Google.Apis.YouTube.v3
and friends. - Basic C# knowledge (async/await, dependency injection, a healthy respect for exceptions).
Setting Up Your YouTube API Project

- Create a project in Google Cloud Console.
- Enable YouTube Data API v3 (and only v3—v2 retired in 2015).
- Configure OAuth 2.0 Consent Screen → External → Add scopes
.../auth/youtube
and.../auth/youtube.upload
. - Create credentials
- OAuth Client ID (Desktop) for full user access.
- API Key for no‑auth read‑only demos.
- Download
credentials.json
and stash it outside version control.
Tip: Add your dev redirect URI as
http://localhost:8080/
, then use Google’s built‑in local server callback—zero plumbing!
Set Up Your C# Project
# Create solution & console project
dotnet new sln -n YoutubeApiLab
dotnet new console -n YoutubeApiLab.App -f net8.0
dotnet sln YoutubeApiLab.sln add YoutubeApiLab.App
# Add Google client libraries
dotnet add YoutubeApiLab.App package Google.Apis.YouTube.v3
dotnet add YoutubeApiLab.App package Google.Apis.Auth
dotnet add YoutubeApiLab.App package Google.Apis.OAuth2.v2
Now wire up a tiny helper to centralize the service creation:
public static class YoutubeServiceFactory
{
public static async Task<YouTubeService> CreateAsync(CancellationToken ct)
{
using var stream = new FileStream("credentials.json", FileMode.Open, FileAccess.Read);
var cred = await GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.FromStream(stream).Secrets,
new[] { YouTubeService.Scope.Youtube, YouTubeService.Scope.YoutubeUpload },
"my-youtube-app", ct, new FileDataStore("token_cache", true));
return new YouTubeService(new BaseClientService.Initializer
{
HttpClientInitializer = cred,
ApplicationName = "YouTube API Lab"
});
}
}
Authenticating with the YouTube API

OAuth sounds scary; in practice it’s just three objects:
var yt = await YoutubeServiceFactory.CreateAsync(ct);
Console.WriteLine($"Authenticated as: {yt.HttpClientInitializer?.UserId}");
The first run pops a browser window; subsequent runs reuse the token from FileDataStore
. Keep those refresh tokens safe—move them to secure storage before production.
Service Accounts?
Service accounts can’t upload public videos (only to channels linked via CMS). Stick to OAuth if you’re controlling a regular channel.
Retrieving Video Data
Need the latest views count on your viral cat video?
var request = yt.Videos.List("snippet,statistics,contentDetails");
request.Id = "dQw4w9WgXcQ"; // never gona give...
var response = await request.ExecuteAsync(ct);
var video = response.Items.First();
Console.WriteLine($"{video.Snippet.Title} has {video.Statistics.ViewCount} views");
Pagination & Partial Responses
Always set MaxResults
≤ 50 and request only the fields you need (fields
parameter) to conserve quota.
Uploading Videos

Uploading is where quotas bite. Use resumable uploads and keep chunks smallish (5 MB):
var file = new FileInfo("my_clip.mp4");
var video = new Video
{
Snippet = new VideoSnippet
{
Title = "🎉 API Demo Upload",
Description = "Uploaded via Google.Apis.YouTube.v3 – sample code",
Tags = new[] { "csharp", "youtube api", "demo" },
CategoryId = "28" // Science & Technology
},
Status = new VideoStatus { PrivacyStatus = "private" }
};
using var fs = file.OpenRead();
var insert = yt.Videos.Insert(video, "snippet,status", fs, "video/*");
insert.ChunkSize = ResumableUpload.MinimumChunkSize;
insert.ProgressChanged += progress =>
{
Console.WriteLine($"{progress.BytesSent / 1024 / 1024} MB sent – {progress.Status}");
};
var result = await insert.UploadAsync(ct);
Console.WriteLine($"New video id = {result.Id}");
Heads‑up: uploads can time‑out on spotty networks. Catch GoogleApiException
, inspect Errors[0].Reason
, and ResumeAsync()
the upload.
Managing Playlists
// 1) Create playlist
var playlist = await yt.Playlists.Insert(new Playlist
{
Snippet = new PlaylistSnippet { Title = "API Best Of" },
Status = new PlaylistStatus { PrivacyStatus = "public" }
}, "snippet,status").ExecuteAsync(ct);
// 2) Add a video
await yt.PlaylistItems.Insert(new PlaylistItem
{
Snippet = new PlaylistItemSnippet
{
PlaylistId = playlist.Id,
ResourceId = new ResourceId { Kind = "youtube#video", VideoId = "dQw4w9WgXcQ" }
}
}, "snippet").ExecuteAsync(ct);
Remember to batch operations where possible using BatchRequest
—one HTTP call, many mutations.
Implementing Video Search
var search = yt.Search.List("snippet");
search.Q = "dotnet 9 release";
search.Type = "video";
search.PublishedAfter = DateTime.UtcNow.AddMonths(-1);
search.Order = SearchResource.ListRequest.OrderEnum.ViewCount;
search.MaxResults = 10;
var searchResponse = await search.ExecuteAsync(ct);
foreach (var item in searchResponse.Items)
{
Console.WriteLine($"{item.Snippet.Title} – {item.Id.VideoId}");
}
Combine search results with a second Videos.List
call to fetch stats in bulk (pass comma‑joined video IDs). This uses two quota units instead of ten.
Handling Error Responses
quotaExceeded
– You’ve hit daily 10 000‑unit limit. Reduce fields, cache responses, or request a higher quota in Cloud Console.invalidParameter
– Double‑check enum values; “publick” is not a privacy status (I’ve typed that… twice).forbidden
/videoAbuseReportRejected
– Channel policy or content violation. No code fix—review Community Guidelines.- Network hiccups – use exponential back‑off: wait 1 s → 2 s → 4 s, capping at 32 s.
Quick helper:
static async Task<T> Retry<T>(Func<Task<T>> action, int retries = 5)
{
for (var i = 0; i < retries; i++)
{
try { return await action(); }
catch (GoogleApiException ex) when (ex.HttpStatusCode >= HttpStatusCode.InternalServerError)
{
await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, i)));
}
}
throw new Exception("Max retries exceeded");
}
Best Practices for Performance & Security

Goal | Do | Avoid |
---|---|---|
Lower quota usage | Request partial fields (fields=items(id,snippet/title) ), batch requests | Repeated Videos.List per ID |
Faster UX | Cache responses in Redis with ETags | Polling every page load |
Secure tokens | Encrypt token_cache with DPAPI or Azure Key Vault | Storing refresh tokens in plain JSON |
Scalable uploads | Off‑load to background worker, show progress | Blocking UI thread |
Finally, document every scope you request in onboarding—privacy‑savvy users will thank you.
FAQ: Rapid‑Fire Answers for Busy Devs
No. API keys are read‑only. Uploads require user‑granted OAuth scope.
Fill the “quota extension” form in Cloud Console after you have a working app and analytics showing real demand.
Yes—via the YouTube Live Streaming API. Same auth, different service class.
YouTube transcodes in the background. Poll Videos.List(part="processingDetails")
until ProcessingStatus == succeeded
.
Switch to a verified OAuth screen, restrict credentials by SHA‑1 fingerprint, encrypt refresh tokens, and monitor usage with Stackdriver.
Conclusion: Your First Million Views Starts Here
You’ve learned how to authenticate, fetch analytics, upload content, manage playlists, search smartly, and guard against the quota monster—all from clean, modern C# code. Now it’s your turn: clone a repo, wire up those credentials, and let your application become the next backstage manager of your YouTube empire. I’d love to hear what you build—drop a comment below and let’s chat!