YouTube's Data API v3 quota is 10,000 units per key per day. For ViralVidVault, which fetches trending videos across seven European regions every seven hours, that quota needs careful management. One careless loop can burn through an entire day's allocation in minutes.
The Quota Math
Understand what each call costs before writing any fetch logic:
| API Call | Quota Cost |
|---|---|
search.list |
100 units |
videos.list |
1 unit |
channels.list |
1 unit |
playlistItems.list |
1 unit |
A naive approach — searching each category in each region — means 7 regions times 15 categories times 100 units = 10,500 units per fetch cycle. That's more than one key allows per day.
Key Rotation Architecture
Multiple API keys, rotated by usage. Store usage data in the same SQLite database that holds video data:
class YouTubeKeyManager {
private PDO $db;
public function __construct(PDO $db) {
$this->db = $db;
$this->resetExpiredKeys();
}
private function resetExpiredKeys(): void {
// Google resets quotas at midnight Pacific Time
$this->db->exec(
"UPDATE api_keys
SET daily_usage = 0, last_reset = datetime('now')
WHERE last_reset < datetime('now', '-1 day')"
);
}
public function getBestKey(): ?array {
$stmt = $this->db->query(
"SELECT id, api_key, daily_usage
FROM api_keys
WHERE active = 1 AND daily_usage < 9500
ORDER BY daily_usage ASC
LIMIT 1"
);
return $stmt->fetch(PDO::FETCH_ASSOC) ?: null;
}
public function recordUsage(int $keyId, int $units): void {
$stmt = $this->db->prepare(
"UPDATE api_keys
SET daily_usage = daily_usage + ?,
total_usage = total_usage + ?,
last_used = datetime('now')
WHERE id = ?"
);
$stmt->execute([$units, $units, $keyId]);
}
}
The getBestKey() method selects the key with the lowest daily usage, spreading the load evenly. The 9,500-unit threshold leaves a 500-unit buffer to prevent accidental overruns.
Rate Limiter Class
Google enforces per-second limits in addition to daily quotas. A simple token bucket keeps you under the wire:
class TokenBucketLimiter {
private float $tokens;
private float $maxTokens;
private float $refillRate;
private float $lastRefill;
public function __construct(float $requestsPerSecond = 3.0) {
$this->maxTokens = $requestsPerSecond;
$this->tokens = $requestsPerSecond;
$this->refillRate = $requestsPerSecond;
$this->lastRefill = microtime(true);
}
public function acquire(): void {
$this->refill();
while ($this->tokens < 1.0) {
usleep(50000); // 50ms
$this->refill();
}
$this->tokens -= 1.0;
}
private function refill(): void {
$now = microtime(true);
$elapsed = $now - $this->lastRefill;
$this->tokens = min($this->maxTokens, $this->tokens + $elapsed * $this->refillRate);
$this->lastRefill = $now;
}
}
Unlike a simple sleep(1) between calls, the token bucket allows bursts up to the limit and then throttles smoothly.
Quota-Efficient Fetch Strategy
The biggest win is avoiding search.list entirely when possible. The videos.list endpoint with chart=mostPopular costs just 1 unit:
function fetchRegionContent(
YouTubeKeyManager $km,
TokenBucketLimiter $limiter,
string $region
): array {
$results = [];
// 1 unit per call instead of 100
$key = $km->getBestKey();
$limiter->acquire();
$popular = youtubeGet('videos', [
'part' => 'snippet,statistics',
'chart' => 'mostPopular',
'regionCode' => $region,
'maxResults' => 50,
'key' => $key['api_key'],
]);
$km->recordUsage($key['id'], 1);
$results = array_merge($results, $popular['items'] ?? []);
// Per-category popular (also 1 unit each)
$categories = ['1', '2', '10', '17', '20', '22', '23', '24', '25', '26', '28'];
foreach ($categories as $catId) {
$key = $km->getBestKey();
if (!$key) break;
$limiter->acquire();
$catVideos = youtubeGet('videos', [
'part' => 'snippet,statistics',
'chart' => 'mostPopular',
'regionCode' => $region,
'videoCategoryId' => $catId,
'maxResults' => 50,
'key' => $key['api_key'],
]);
$km->recordUsage($key['id'], 1);
$results = array_merge($results, $catVideos['items'] ?? []);
}
return $results;
}
For 7 regions with 11 categories each, that's 7 * 12 = 84 units per fetch cycle. Compare that to 10,500 units with the search.list approach. A 125x improvement.
Handling Quota Errors Gracefully
When a key hits its limit, YouTube returns a 403 with a specific error reason. Catch it and rotate:
function youtubeGetWithRetry(
YouTubeKeyManager $km,
TokenBucketLimiter $limiter,
string $endpoint,
array $params
): ?array {
$attempts = 0;
$maxAttempts = 3;
while ($attempts < $maxAttempts) {
$key = $km->getBestKey();
if (!$key) {
error_log('All YouTube API keys exhausted');
return null;
}
$params['key'] = $key['api_key'];
$limiter->acquire();
$response = httpGet(buildYouTubeUrl($endpoint, $params));
if ($response['status'] === 200) {
return json_decode($response['body'], true);
}
if ($response['status'] === 403) {
$body = json_decode($response['body'], true);
$reason = $body['error']['errors'][0]['reason'] ?? 'unknown';
if (in_array($reason, ['quotaExceeded', 'dailyLimitExceeded'])) {
$km->recordUsage($key['id'], 10000); // Mark as exhausted
$attempts++;
continue;
}
}
error_log("YouTube API error: HTTP {$response['status']}");
return null;
}
return null;
}
Monitoring Dashboard
Track quota consumption to catch issues before they become outages:
function getQuotaDashboard(PDO $db): array {
$keys = $db->query(
"SELECT api_key, daily_usage, last_used, last_reset,
ROUND(daily_usage * 100.0 / 10000, 1) AS pct
FROM api_keys WHERE active = 1
ORDER BY daily_usage DESC"
)->fetchAll(PDO::FETCH_ASSOC);
$totalUsed = array_sum(array_column($keys, 'daily_usage'));
$totalCap = count($keys) * 10000;
return [
'total_used' => $totalUsed,
'total_capacity' => $totalCap,
'pct_used' => round($totalUsed / $totalCap * 100, 1),
'keys' => $keys,
];
}
On ViralVidVault, the admin panel displays this in real time. When aggregate usage crosses 70%, the system logs a warning. At 90%, it reduces fetch frequency to preserve quota for the rest of the day.
Key Takeaway
The YouTube API quota is not a problem to solve once — it's a constraint to design around from the start. Choose cheap endpoints over expensive ones, rotate keys evenly, and monitor consumption. The difference between a platform that runs smoothly and one that goes dark at 3pm because it burned through its quota is just a few hundred lines of management code.
This article is part of the Building ViralVidVault series.
Top comments (0)