# YouTube API OAuth: Client ID, Scopes, Refresh Tokens

How YouTube API OAuth works: API key vs OAuth 2.0, the client ID and consent screen, scopes like youtube.upload, refresh tokens, and the 7-day Testing expiry.

Canonical page: https://www.tryadeli.com/blog/youtube-api-oauth
Last updated: 2026-10-19

By Mika Reyes, Co-founder, Adeli. Published 2026-10-19.

## The short answer

The YouTube Data API needs OAuth 2.0 whenever a request touches a user's channel: uploads, edits, comments, private data. An API key only reads public data. Create an OAuth client ID in Google Cloud, configure the consent screen, request a scope such as youtube.upload with offline access, and store the refresh token. Apps left in Testing get refresh tokens that expire after 7 days. Adeli runs this whole flow for you.

YouTube API OAuth is the OAuth 2.0 flow your app runs so a channel owner can grant it access to their YouTube account. You need it for **any request that changes a channel or reads private data**, and you set it up in Google Cloud with an OAuth client ID, a consent screen, and one or more YouTube scopes. Below: when an API key is enough, the setup steps, why refresh tokens die, and the verification Google asks for once real users sign in.

## Do I need OAuth or an API key for the YouTube Data API?

**Use an API key for public data and OAuth 2.0 for anything tied to a user's channel.** Google's [getting started guide](https://developers.google.com/youtube/v3/getting-started) says operations that insert, update, or delete resources "always require user authorization," and that unauthorized requests "only retrieve public data."

| Task | API key | OAuth 2.0 |
|---|---|---|
| **Look up a public video or channel** | Yes | Yes |
| **Search public videos** | Yes | Yes |
| **Upload a video** | No | Yes |
| **Edit titles, thumbnails, playlists** | No | Yes |
| **Post or moderate comments** | No | Yes |
| **Read the signed-in user's private videos** | No | Yes |

An API key is a `key=` query parameter that identifies your Google Cloud project. It never identifies a user, so YouTube has no channel to act on. If your product posts for your customers, you need OAuth.

**Service accounts don't fill the gap.** Google's [authentication overview](https://developers.google.com/youtube/v3/guides/authentication) supports the service account flow only for YouTube content owners managing several channels, on calls that take `onBehalfOfContentOwner`. Elsewhere it fails with `NoLinkedYouTubeAccount`.

## What do you need before you start?

- **A Google Cloud project** with the YouTube Data API v3 enabled in the API Library.
- **A domain you control**, with a home page and a privacy policy on it. Verification later requires both.
- **A redirect URI** on your server that receives the authorization code, such as `https://app.example.com/oauth/youtube/callback`.
- **A test Google account that owns a YouTube channel.** An account without a channel can authorize but has nothing to upload to.

## Step 1: Configure the OAuth consent screen

**Set the app name, support email, authorized domains, and audience first.** The consent screen is what your customers see when Google asks them to approve access.

1. In Google Cloud, open **Google Auth Platform → Branding** and fill in the app name, logo, support email, home page, and privacy policy link.
2. Under **Audience**, choose **External** if anyone outside your Google Workspace will connect a channel.
3. Under **Data Access**, add the YouTube scopes you plan to request (Step 3).

These menu names follow Google's [consent screen setup guide](https://developers.google.com/workspace/guides/configure-oauth-consent).

The audience setting has a publishing status, and it changes how your tokens behave. Google's [app audience page](https://support.google.com/cloud/answer/15549945) describes two states:

| Publishing status | Who can sign in | Refresh tokens |
|---|---|---|
| **Testing** | Up to **100 test users** you list on the consent screen | Authorizations **expire 7 days** after consent |
| **In production** | Any Google account | Long-lived, subject to the expiry rules below |

**Testing is fine for a prototype and wrong for anything else.** If your users suddenly have to reconnect once a week, check this setting first.

## Step 2: Create the OAuth client ID

**Create a client of type "Web application" and register your exact redirect URI.** Go to **Google Auth Platform → Clients → Create client**, pick **Web application**, and add the callback URL under **Authorized redirect URIs**. Google's [web server OAuth guide](https://developers.google.com/identity/protocols/oauth2/web-server) walks through the same form.

Google gives you a **client ID** and a **client secret**. The client ID goes into the authorization URL. The client secret stays on your server and is only used in the token exchange. Never ship it in browser or mobile code.

## Step 3: Pick the narrowest YouTube scope

**Request only the scopes your feature uses.** Google's [web server OAuth guide](https://developers.google.com/youtube/v3/guides/auth/server-side-web-apps) lists the YouTube Data API v3 scopes:

| Scope | What Google's consent screen says |
|---|---|
| `youtube.upload` | "Manage your YouTube videos" |
| `youtube.readonly` | "View your YouTube account" |
| `youtube.force-ssl` | "See, edit, and permanently delete your YouTube videos, ratings, comments and captions" |
| `youtube` | "Manage your YouTube account" |
| `youtubepartner` | "View and manage your assets and associated content on YouTube" |

Each scope is prefixed with `https://www.googleapis.com/auth/`. If uploading is all your app does, `youtube.upload` is the narrowest choice. If you also post or moderate comments, you need `youtube.force-ssl`. Our [YouTube upload guide](https://www.tryadeli.com/blog/youtube-api-upload-video) covers which scopes `videos.insert` accepts.

## Step 4: Send the user to Google and exchange the code

**Redirect the user to Google's authorization endpoint with `access_type=offline` so you get a refresh token.**

```text
https://accounts.google.com/o/oauth2/v2/auth
  ?client_id=YOUR_CLIENT_ID
  &redirect_uri=https%3A%2F%2Fapp.example.com%2Foauth%2Fyoutube%2Fcallback
  &response_type=code
  &scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fyoutube.upload
  &access_type=offline
  &prompt=consent
  &include_granted_scopes=true
  &state=RANDOM_STATE_VALUE
```

- **`access_type=offline`** asks Google for a refresh token along with the access token.
- **`prompt=consent`** shows the consent screen again. Without it, Google prompts only the first time, and a reconnecting user may come back without a new refresh token.
- **`include_granted_scopes=true`** turns on incremental authorization, so a later scope request keeps the scopes already granted.
- **`state`** is your CSRF check. Compare it on the callback.

Google sends the user back to your redirect URI with a `code`. Exchange it on your server:

```bash
curl -X POST https://oauth2.googleapis.com/token \
  -d client_id="$GOOGLE_CLIENT_ID" \
  -d client_secret="$GOOGLE_CLIENT_SECRET" \
  -d code="$AUTH_CODE" \
  -d grant_type=authorization_code \
  -d redirect_uri="https://app.example.com/oauth/youtube/callback"
```

The response carries an `access_token`, its `expires_in` in seconds, and a `refresh_token`. **Store the refresh token encrypted, per channel.** It's the only thing that lets you act for that user later.

## Step 5: Refresh the access token

**Access tokens last about an hour, so swap the refresh token for a new one when they expire.** The request goes to the same token endpoint:

```bash
curl -X POST https://oauth2.googleapis.com/token \
  -d client_id="$GOOGLE_CLIENT_ID" \
  -d client_secret="$GOOGLE_CLIENT_SECRET" \
  -d refresh_token="$REFRESH_TOKEN" \
  -d grant_type=refresh_token
```

Google returns a new `access_token` and `expires_in`. The refresh token itself usually stays the same.

## Why does my YouTube refresh token stop working?

**Google invalidates refresh tokens in a handful of documented cases.** Its [OAuth 2.0 overview](https://developers.google.com/identity/protocols/oauth2) lists them:

- **Testing status.** An External app in Testing gets refresh tokens that expire in **7 days**.
- **Six months unused.** A refresh token that hasn't been used for six months stops working.
- **Too many tokens.** Google allows **100 refresh tokens per Google account per OAuth client ID**. Creating a 101st silently invalidates the oldest.
- **The user revoked access**, or chose time-based access that has run out.
- **The user changed their password**, when the token carries Gmail scopes. YouTube-only tokens aren't on that list.

A dead refresh token returns `invalid_grant` from the token endpoint. Mark the channel as disconnected and send the user back through Step 4. Retrying won't help.

## Does a YouTube OAuth app need Google verification?

**Yes, once it leaves Testing with YouTube scopes.** Google's [sensitive scope verification page](https://developers.google.com/identity/protocols/oauth2/production-readiness/sensitive-scope-verification) says sensitive scopes "require review by Google before any Google Account can grant access," and gives deleting a YouTube video as an example. No YouTube Data API scope appears on Google's [list of restricted scopes](https://support.google.com/cloud/answer/13464325), which names only the Data Portability API's YouTube scopes, so the extra security assessment for restricted scopes doesn't apply. The **Data Access** page in Google Cloud shows which category each scope you add falls into.

What the review asks for:

- **A demo video**, uploaded to YouTube as unlisted, showing a user granting each scope and your app using it.
- **A justification for each scope**, including why a narrower one won't work.
- **A privacy policy** on the same domain as your home page, linked from the consent screen.
- **Domain verification** in Google Search Console.

Google says the review typically takes **3 to 5 business days**. Until it passes, users see an "unverified app" screen, and Google's [unverified apps page](https://support.google.com/cloud/answer/7454865) caps you at **100 new users**.

**OAuth verification is not the YouTube API Services audit.** They're separate reviews. The audit checks your app against YouTube's API terms, and you need it to raise the default upload quota and to stop uploads being locked to private. Google covers it on its [quota and compliance audits page](https://developers.google.com/youtube/v3/guides/quota_and_compliance_audits), and [our review-times post](https://www.tryadeli.com/blog/social-media-api-review-times) covers how long it takes.

## Is there a faster way to handle YouTube OAuth?

**Yes: let an API that already owns the Google client run the flow.** [Adeli's YouTube API](https://www.tryadeli.com/product/youtube) holds the Google OAuth client, the client secret, and the callback, and its YouTube app **has passed the YouTube API Services audit**. You don't create a Google Cloud project, a consent screen, or a refresh job.

Your server starts a hosted connection with one request to [`POST /api/v1/profiles/{profileId}/connect`](https://www.tryadeli.com/docs/api/connect/start-connection):

```bash
curl -X POST "https://app.tryadeli.com/api/v1/profiles/$PROFILE_ID/connect" \
  -H "Authorization: Bearer $ADELI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"platform":"youtube","redirectUrl":"https://client.example/oauth/adeli"}'
```

The `201` response carries an `authUrl`. Open it in your customer's browser, and Google's account chooser lets them pick their own channel or a Brand Account channel. Adeli then sends the browser back to your registered `redirectUrl`, and your server polls the connection session until it's `connected`.

What Adeli handles from there, per its [connect docs](https://www.tryadeli.com/docs/api/connect):

- **Scopes.** Adeli requests `youtube.force-ssl`, which covers uploads, comments, and moderation, plus `yt-analytics.readonly` for analytics.
- **Consent failures.** A customer who unticks the YouTube permission gets `missing_scope`. An account with no channel gets `no_channel`. Nothing is stored in either case.
- **Token refresh.** Adeli refreshes tokens for every connected channel. [`POST /api/v1/profiles/{profileId}/accounts/refresh`](https://www.tryadeli.com/docs/api/accounts/refresh-tokens) forces a refresh and also re-reads the channel's title, avatar, and counts.
- **Revoked grants.** If a customer removes Adeli from their Google account, or the grant goes six months unused, the account shows `reconnect_required` and you send them through connect again. Reconnecting the same channel keeps the same `accountId`.

Once a channel is connected, the same [posting API](https://www.tryadeli.com/social-media-posting-api) uploads to YouTube and publishes to Instagram, TikTok, Facebook, X, and Bluesky. A YouTube channel is one connected account, and your **first 3 connected accounts are free**.

## Frequently asked questions

### What is the difference between a YouTube API key and OAuth?

An API key identifies your project and only unlocks public data, such as looking up a public video or running a search. OAuth 2.0 identifies a user who has granted your app access to their channel. Google's docs say any request that inserts, updates, or deletes a resource always requires user authorization, so uploads need OAuth.

### Why does my YouTube refresh token expire after 7 days?

Your OAuth consent screen is probably set to External with a publishing status of Testing. Google expires authorizations by test users seven days after consent, unless the app only asks for basic sign-in scopes. Move the app to In production to get long-lived refresh tokens.

### Can I use a service account with the YouTube Data API?

Usually not. Google supports the service account flow for YouTube only for content owners who manage many channels, and only on calls that accept onBehalfOfContentOwner. For a normal channel, a service account returns a NoLinkedYouTubeAccount error, so use the web server OAuth flow.

### How long does a YouTube access token last?

About an hour. The token response carries expires_in, the remaining lifetime in seconds, and Google's example shows 3920. Your app swaps the refresh token for a new access token when the old one expires.

## Related Adeli pages

- [YouTube API for Developers](https://www.tryadeli.com/product/youtube.md): Publish, schedule, and read analytics for YouTube through one API. Adeli owns the YouTube developer app, token refresh, and Google review. Free to start.
- [Social Media Posting & Scheduling API](https://www.tryadeli.com/social-media-posting-api.md): Adeli's social media posting and scheduling API publishes to Instagram, TikTok, YouTube, Facebook, X, and Bluesky in one request, now or at a set time. Free to start.
- [All posts on the Adeli blog](https://www.tryadeli.com/blog.md)

## About the author

Mika Reyes is the CEO behind Adeli and a tech and AI content creator. She was previously co-founder and CEO of Parallax, which was acquired by Phantom, and a product lead at LinkedIn. She is a Forbes 30 Under 30 honoree.
