YouTube Data API v3 lets you fetch trending videos for specific countries. Here's a practical guide based on building TopVideoHub, which fetches trending data from 9 Asia-Pacific regions.
Getting Started
You need a Google Cloud project with YouTube Data API v3 enabled and an API key.
# Test with curl first
curl "https://www.googleapis.com/youtube/v3/videos?part=snippet,statistics&chart=mostPopular®ionCode=JP&maxResults=10&key=YOUR_KEY"
PHP Implementation
Here's a clean PHP 8.3 implementation:
<?php
declare(strict_types=1);
enum Region: string {
case US = 'US';
case GB = 'GB';
case JP = 'JP';
case KR = 'KR';
case TW = 'TW';
case SG = 'SG';
case VN = 'VN';
case TH = 'TH';
case HK = 'HK';
}
class YouTubeApi {
private const string BASE_URL = 'https://www.googleapis.com/youtube/v3';
public function __construct(
private readonly string $apiKey,
private readonly QuotaTracker $quota,
) {}
/**
* Fetch trending videos for a region.
* API cost: 1 quota unit per call (videos.list with chart=mostPopular)
*/
public function fetchTrending(
Region $region,
int $maxResults = 50,
?string $categoryId = null,
): array {
$params = [
'part' => 'snippet,statistics,contentDetails',
'chart' => 'mostPopular',
'regionCode' => $region->value,
'maxResults' => min($maxResults, 50),
'key' => $this->apiKey,
];
if ($categoryId !== null) {
$params['videoCategoryId'] = $categoryId;
}
$url = self::BASE_URL . '/videos?' . http_build_query($params);
$response = $this->request($url);
$this->quota->record(cost: 1);
return array_map(
fn(array $item) => $this->normalizeVideo($item, $region),
$response['items'] ?? []
);
}
private function normalizeVideo(array $item, Region $region): array {
$snippet = $item['snippet'];
$stats = $item['statistics'] ?? [];
return [
'video_id' => $item['id'],
'title' => $snippet['title'],
'channel_title' => $snippet['channelTitle'],
'channel_id' => $snippet['channelId'],
'description' => mb_substr($snippet['description'] ?? '', 0, 500),
'thumbnail_url' => $this->bestThumbnail($snippet['thumbnails']),
'published_at' => $snippet['publishedAt'],
'category_id' => (int)($snippet['categoryId'] ?? 0),
'view_count' => (int)($stats['viewCount'] ?? 0),
'like_count' => (int)($stats['likeCount'] ?? 0),
'comment_count' => (int)($stats['commentCount'] ?? 0),
'region' => $region->value,
'duration' => $item['contentDetails']['duration'] ?? '',
];
}
private function bestThumbnail(array $thumbs): string {
foreach (['maxres', 'standard', 'high', 'medium', 'default'] as $key) {
if (isset($thumbs[$key]['url'])) {
return $thumbs[$key]['url'];
}
}
return '';
}
private function request(string $url): array {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_SSL_VERIFYPEER => true,
]);
$body = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($code !== 200 || $body === false) {
throw new \RuntimeException("API request failed: HTTP {$code}");
}
return json_decode($body, true, 512, JSON_THROW_ON_ERROR);
}
}
Quota Management
YouTube gives you 10,000 quota units per day. Here's the cost breakdown:
| Operation | Cost |
|---|---|
videos.list |
1 unit |
search.list |
100 units |
channels.list |
1 unit |
commentThreads.list |
1 unit |
For trending videos, always use videos.list with chart=mostPopular (1 unit) instead of search.list (100 units). This is 100x more efficient.
With 9 regions at 1 unit each, you can fetch all regions 1,111 times per day before hitting the quota. At TopVideoHub, we fetch every 2-4 hours, which uses roughly 100-200 units per day — well within limits.
Handling Categories
YouTube has a fixed set of video categories. Fetch them once and cache:
public function fetchCategories(Region $region): array {
$url = self::BASE_URL . '/videoCategories?' . http_build_query([
'part' => 'snippet',
'regionCode' => $region->value,
'key' => $this->apiKey,
]);
$response = $this->request($url);
$this->quota->record(cost: 1);
$categories = [];
foreach ($response['items'] as $item) {
if ($item['snippet']['assignable'] ?? false) {
$categories[$item['id']] = $item['snippet']['title'];
}
}
return $categories;
}
Note: available categories vary by region. Japan has categories that don't exist in the US, and vice versa.
Fetching Multiple Regions Efficiently
$api = new YouTubeApi($apiKey, $quota);
$allVideos = [];
foreach (Region::cases() as $region) {
try {
$videos = $api->fetchTrending($region, maxResults: 50);
$allVideos[$region->value] = $videos;
echo "[OK] {$region->value}: " . count($videos) . " videos\n";
} catch (\Throwable $e) {
echo "[ERR] {$region->value}: {$e->getMessage()}\n";
}
}
Pro Tips
- Cache responses aggressively — Trending data doesn't change every minute. Fetch every 2-4 hours.
-
Use
videos.listnotsearch.list— 100x cheaper in quota. - Handle rate limits gracefully — YouTube returns HTTP 429 when you exceed quotas. Implement exponential backoff.
- Store raw responses — Keep the full API response in your cache so you can extract additional fields later without re-fetching.
This approach powers TopVideoHub, serving trending video data from 9 regions reliably within free API quota limits.
Top comments (0)