Table of Contents
- What Google gives every project by default
- Why it runs out sooner than you expected
- Your three options
- Wait for the reset
- Request a quota extension
- Upload through a service that already handles it
- Uploading through PublishQ
- From the terminal or CI
- From your own code
- From a coding agent
- Setup guides for other agents
- After the switch
A quotaExceeded (403) error means your Google Cloud project has used up its daily YouTube Data API allowance, and YouTube will refuse further requests from it until the quota resets1. Uploads have a bucket of their own, so the error can hit after 100 videos.insert calls even when the rest of your quota looks untouched2. The fastest way out is to stop uploading through your own project: connect the channel to PublishQ and send the video through its API, CLI or MCP server. You don't need a Google project, so there is no quota of yours to run out.

What Google gives every project by default
Google's quota page lists the default allocation for any project that turns on the YouTube Data API2:
| Bucket | Default per day | Cost per call |
|---|---|---|
videos.insert (uploads) | 100 calls | 1 unit, in the Video Uploads bucket3 |
search.list | 100 calls | 1 unit |
| Everything else combined | 10,000 units | 1 for most reads, 50 for most writes |
A few more rows from the same table matter for an upload pipeline. Setting a custom thumbnail with thumbnails.set costs 50 units from the shared 10,000, and so do videos.update, playlistItems.insert and captions.delete. Uploading a caption track with captions.insert costs 4002. Daily quotas reset at midnight Pacific Time2.
You can watch your usage on the Quotas page of the Google Cloud console2. If that page shows the uploads bucket at 100 of 100, you have your answer.
Why it runs out sooner than you expected
The allowance belongs to the project, and the project is shared by every script, cron job and user that authenticates through it. A few patterns burn through it quickly:
- Retry loops. Every request costs at least one unit, invalid ones included4. A script that retries a failed upload ten times can spend up to ten upload calls on one video.
- Paging. Each extra page of results is charged again2, so a sync job that walks a big channel's video list every few minutes adds up.
- Thumbnails and edits on top of uploads. One upload plus a thumbnail plus a metadata fix is 1 call from the uploads bucket and 100 units from the shared pool.
- Several apps on one project. A test harness and the production pipeline on the same project eat from the same 100 uploads.
- Multi-tenant tools. If you built something your clients use, all of their channels upload through your one project.
None of this is a bug in your code. The default is sized for light use, and real automation outgrows it.
Your three options
Wait for the reset
The counter goes back to zero at midnight Pacific2. For a one-off spike that is fine. Queue the remaining videos and run them after the reset. For a pipeline that hits the ceiling every day, waiting just moves the error to tomorrow.
Request a quota extension
Google requires an audit before it grants more quota than the default. The audit checks that your project complies with the YouTube API Services Terms of Service, and you start it by submitting the Audit and Quota Extension Form5. Google's team contacts you after you submit it. An extension within 12 months of a passed audit uses the same form, and there is a separate appeals form if you fail5.
The form asks for a lot6:
- your legal name, your organization's legal name, address and website
- the API client's name (it must not contain the word "YouTube" without written approval), its access URL, privacy policy and terms
- demo login credentials with full access, or an end-to-end screencast if there is no demo account
- the Google Cloud project numbers, with one form per API client
- screenshots of your privacy policy, homepage and OAuth consent flow
- for upload tools, either a complete working upload script or a screencast of the full flow ending with the video live on YouTube
- the per-day quota you want and a detailed justification
The form also says applications must show "significant independent value" to the YouTube ecosystem, and those that cannot may not get more quota6. For a personal automation or an internal script, that is a hard case to make.
Upload through a service that already handles it
The third option is to stop being the API client at all. With PublishQ you connect your YouTube channel once, and every upload goes through PublishQ. You never create a Cloud project, never fill in the audit form and never watch a quota counter. In your code, videos.insert becomes two calls: upload the file, then create a post.

Uploading through PublishQ
Three routes reach the same API. Pick the one that matches how your pipeline already runs.
From the terminal or CI
npm i -g @publishq/cli
pq login
pq accounts list --platform YOUTUBE
pq media upload --file ./video.mp4
pq posts create --content "Weekly build log, episode 12" \
--account <account-id> --media-id <media-id> --publish-nowEvery command returns JSON, so a shell script or a CI job can read the IDs with jq. In CI, set PUBLISHQ_API_KEY=pq_live_... instead of logging in. Swap --publish-now for --scheduled-at 2026-12-01T09:00:00Z to queue it, or leave both off to keep a draft. The full command list is in the CLI docs and on the YouTube CLI page.
From your own code
Create a key at https://publishq.com/app/api-keys, upload the file to POST /media (or get a signed upload URL from POST /media/uploads for files up to 4 GB, see media uploads), then create the post:
curl -X POST https://publishq.com/api/v1/posts \
-H "Authorization: Bearer pq_live_..." \
-H "Content-Type: application/json" \
-d '{
"content": "Weekly build log, episode 12",
"mediaIds": ["3b9f6c2e-71d4-4a8e-9c05-d2e8a6f41b77"],
"accounts": [{
"accountId": "a7c41e90-5d2b-4f63-8e1a-0c9b7d3f6e24",
"platformSpecificSettings": {
"description": "Changelog and repo links below.",
"privacyStatus": "unlisted",
"categoryId": "28",
"madeForKids": false
}
}],
"publishNow": true
}'content is the YouTube title (100 characters max). privacyStatus defaults to public, so set it if you want an unlisted test first. The rest of the YouTube fields, including thumbnailMediaId for a custom thumbnail, are on the YouTube API docs page. TypeScript users can use @publishq/sdk instead of raw HTTP. More on the REST side is on the YouTube API page.
From a coding agent
If you wrote your upload script with Claude Code, Cursor or Codex, you can hand the job back to the same agent. In Claude Code:
claude mcp add --transport http publishq https://publishq.com/mcpThen run /mcp, choose Authenticate and approve in the browser. Cursor takes {"mcpServers":{"publishq":{"url":"https://publishq.com/mcp"}}} in ~/.cursor/mcp.json. For Codex, run npx @publishq/cli@latest login, then codex mcp add publishq -- npx -y @publishq/mcp@latest. After that, "upload ./out/ep12.mp4 to my YouTube channel as unlisted, category 28" is a whole request. The agent calls publishq_upload_media and publishq_create_post for you. There is a longer walkthrough in uploading to YouTube from Claude Code and on the YouTube MCP page.
The API, CLI and MCP server are on every plan, the Free plan included. See pricing for the rest.
Setup guides for other agents
Each of these pages has the setup for that agent and YouTube-specific prompts:
| Agent | YouTube setup |
|---|---|
| Claude Code | https://publishq.com/claude-code/youtube |
| Cursor | https://publishq.com/cursor/youtube |
| Codex | https://publishq.com/codex/youtube |
| Claude Cowork | https://publishq.com/claude-cowork/youtube |
| Gemini CLI | https://publishq.com/gemini-cli/youtube |
| Kiro | https://publishq.com/kiro/youtube |
| Manus | https://publishq.com/manus/youtube |
| Hermes Agent | https://publishq.com/hermes-agent/youtube |
| OpenClaw | https://publishq.com/openclaw/youtube |
| Muse | https://publishq.com/muse/youtube |
For any other agent, open https://publishq.com/agents and paste the line at the top of that page into your agent. It sets itself up and sends you a link to approve the login in your browser.
After the switch

The nightly job that used to stop at video 100 now finishes the queue. A retry no longer spends a call from a 100-a-day bucket of yours. The same create call can also carry TikTok, Instagram and Facebook accounts, each with its own caption, so one render can go out everywhere. If you want to keep a calendar view of what is queued, the YouTube scheduler shows it, and YouTube limits lists the title, description and tag rules a post still has to meet.
Stop planning uploads around Google's reset clock. Create a PublishQ account, connect your channel and send the next video through it.
Footnotes
-
Errors - YouTube Data API (2026-11) - quotaExceeded (403): the request cannot be completed because the quota is exceeded ↩
-
Quota costs for API requests - YouTube Data API (2026-11) - default allocation of 100 search.list calls, 100 videos.insert calls and 10,000 units for other endpoints, separate buckets, per-method costs, paging charged per page, reset at midnight Pacific Time, Quotas page in Cloud console ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7
-
Videos: insert - YouTube Data API (2026-11) - 100 calls per day, 1 unit per call in the Video Uploads quota bucket ↩
-
Getting started - YouTube Data API (2026-11) - all requests, including invalid requests, cost at least one quota unit ↩
-
Quota and Compliance Audits - YouTube Data API (2026-11) - audit required for quota beyond the default, Audit and Quota Extension Form, extensions within 12 months, appeals ↩ ↩2
-
YouTube API Services - Audit and Quota Extension Form (2026-11) - information, evidence and justification the form requires, one form per API client, significant independent value requirement ↩ ↩2
