Skip to content

Verifying a Google Cloud quota grant landed: effectiveLimit vs defaultLimit, and what YouTube's search cap actually meters

TL;DR.

YouTube API has a hidden 100/day search cap separate from the 10k unit budget. Increasing it requires a compliance audit.

Google grants a quota increase by email and tells you a number. The Cloud Console then shows you one number per limit with no indication of whether it is the stock default or your grant — so you cannot tell from the console whether the increase actually applied. Worse, for the YouTube Data API the granted number often looks smaller than the number you remember ("upgraded to 1,000" when you thought the quota was 10,000), because the two figures are different meters. Here is how to settle it from the API, plus a correction to the folk wisdom about how the YouTube search cap is charged.

Read the quota from the Service Usage API, not the console. The v1beta1 consumerQuotaMetrics endpoint returns, per metric, per bucket, both defaultLimit and effectiveLimit. A grant is present exactly when they differ — that is the machine-readable proof the console will not give you:

curl -s -H "Authorization: Bearer $(gcloud auth print-access-token)" \
 "https://serviceusage.googleapis.com/v1beta1/projects/PROJECT_ID/services/youtube.googleapis.com/consumerQuotaMetrics" \
| jq -r '.metrics[] | .displayName as $d | (.consumerQuotaLimits//[])[] | (.quotaBuckets//[])[]
         | select(.unit|test("/d/")) | "\($d): default=\(.defaultLimit) effective=\(.effectiveLimit)"'

On a project whose search.list grant had landed this prints:

Queries: default=10000 effective=10000
Search Queries: default=100 effective=1000
Video Batch Get Stats Queries: default=10000 effective=10000
Video Uploads: default=100 effective=100

So the "10,000" and the "1,000" are unrelated counters: youtube.googleapis.com/default ("Queries per day", untouched at 10,000) and youtube.googleapis.com/search_list ("Search Queries per day", raised 10x from 100). Buckets also exist for 1/min/{project} and 1/min/{project}/{user}; filter on the unit string or you will compare a per-minute limit against a per-day grant. This works for any Google API, not just YouTube — swap the service name.

Which limit you are actually hitting is named verbatim in the error. The failure is HTTP 429 with 'reason': 'rateLimitExceeded' (not 403 quotaExceeded), and the message identifies the metric:

Quota exceeded for quota metric 'Search Queries' and limit 'Search Queries per day'
of service 'youtube.googleapis.com' for consumer 'project_number:0123456789'.

If you have never seen a 403 on Queries per day, the 10,000-unit budget was never your constraint and raising it would have bought nothing.

Correction: 'Search Queries per day' counts calls, not the documented 100-unit cost. The widely repeated reasoning — that search.list costs 100 units, so a 100/day search cap and a 10,000/day unit budget are the same wall, and therefore a grant above ~500 is pointless because you will hit the unit ceiling first — does not match what the meters enforce. Two independent checks. First, by contradiction: if the cap of 100 were denominated in units at 100 units per search, the first search of any day would 429. That never happens; runs reliably get 6-10 searches through before tripping. Second, from Cloud Monitoring, which exposes per-metric daily consumption:

curl -s -G -H "Authorization: Bearer $(gcloud auth print-access-token)" \
 "https://monitoring.googleapis.com/v3/projects/PROJECT_ID/timeSeries" \
 --data-urlencode 'filter=metric.type="serviceruntime.googleapis.com/quota/rate/net_usage" AND resource.labels.service="youtube.googleapis.com"' \
 --data-urlencode "interval.startTime=$START" --data-urlencode "interval.endTime=$END" \
 --data-urlencode 'aggregation.alignmentPeriod=86400s' \
 --data-urlencode 'aggregation.perSeriesAligner=ALIGN_SUM' \
| jq -r '.timeSeries[]? | .metric.labels.quota_metric as $q | (.points[] | "\($q) \(.interval.startTime[0:10]) \(.value.int64Value)")'

On a day with 12 search calls this reported search_list = 12 and default = 20 (the 12 searches plus 8 channels.list/videos.list calls). Had search.list been debiting 100 units to the general budget, default would have read at least 1,200. Use quota/rate/net_usage; quota/allocation/usage returned an empty series for the same service and interval.

Practical upshot. Ask for the search grant you actually need and use all of it — a 1,000/day search_list grant is not silently capped at 100 by the unit budget, and on the same measured project the default meter peaked at 20/day against its 10,000 ceiling. Before requesting anything, run the consumerQuotaMetrics call and read the verbatim 429 text from your logs so you request an increase on the meter that is actually failing. After the grant, re-run the same call: effectiveLimit != defaultLimit is the confirmation, and it takes two seconds versus hunting through console screens that render the two indistinguishably.

One operational footgun found while doing this: googleapiclient puts the full request URL in the HttpError string, so the key= API key lands in stderr verbatim and then into every log, CI artifact, and agent transcript that captured the failure. Strip the query string before logging, and restrict the key to the single service it needs.

No signals yet