Google OAuth Refresh Token: Expiration, 7-Day Limit & Lifetime Explained (2026)
Google OAuth refresh tokens don't last forever. Understand every expiration condition - from the 7-day testing trap to the 6-month inactivity rule - and learn how to keep your Gmail API integration alive in production.
import requests
# Exchange refresh token for new access token
response = requests.post(
"https://oauth2.googleapis.com/token",
data={
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_SECRET",
"refresh_token": "1//0g...",
"grant_type": "refresh_token"
}
)
token = response.json()["access_token"]Unipile does not store OAuth tokens in a parallel archive or create independent data copies outside the authenticated session. Token storage and refresh operations are scoped exclusively to the session of each authenticated user who has explicitly granted access. No token data is shared across accounts or retained beyond the authorization scope defined by the user.
Unipile acts as an independent technical intermediary, performing Gmail API and OAuth token operations on behalf of each authenticated user who has individually authorized access. Unipile is not affiliated with, endorsed by, or sponsored by Google. No shared credentials are used. Each integration relies on the user's own Google OAuth consent, issued through their own Google Cloud project or via Unipile's CASA Tier 2 certified OAuth flow.
Unipile relays Google API rate limits and quota restrictions as defined by Google's policies. Request cadence, volume decisions, and usage patterns remain a customer-side decision. Developers are responsible for ensuring their integration complies with Google's Terms of Service, including OAuth token handling policies and data access scopes. Unipile provides infrastructure - policy compliance is the responsibility of each developer.
What is a Google OAuth refresh token?
Before diving into expiration rules, it helps to understand exactly what a Google OAuth refresh token is, what it does, and how it differs from an access token in the Google OAuth2 flow.
A Google OAuth refresh token is a long-lived credential issued by Google's authorization server that allows your application to obtain new access tokens without requiring the user to re-authenticate. Unlike access tokens (which expire after 3,600 seconds), a google oauth refresh token persists across sessions - subject to specific expiration conditions - and is issued only when the access_type=offline parameter is included in the authorization request.
The authenticated user approves your app's requested scopes on Google's consent screen. With access_type=offline, Google issues both an access token and a refresh token.
Access tokens have a fixed TTL of 3,600 seconds. Once expired, any API call returns a 401 Unauthorized error. Your app must exchange the refresh token for a new access token.
A POST to https://oauth2.googleapis.com/token with grant_type=refresh_token returns a new access token. The google oauth refresh token itself remains valid (unless one of the 6 expiration conditions fires).
access_type=offline must be set
ya29.
1//
Do Google refresh tokens expire? The 6 conditions
Yes, a Google OAuth refresh token can expire - but only under specific conditions. Understanding every case is critical for any Gmail API integration that needs to run unattended. Here are all six Google OAuth refresh token expiration scenarios you must handle in production.
| # | Condition | When it fires | Severity | Fix |
|---|---|---|---|---|
| 1 | App in testing mode - 7-day limit | OAuth consent screen is "Testing" and app is not verified by Google | Critical | Publish the app or use an internal Workspace app |
| 2 | 6 months of inactivity | Token has not been used to obtain an access token for 6 months | Medium | Implement keep-alive pings; use token at least every 6 months |
| 3 | User changes Google password | Only affects tokens with Gmail or sensitive mail scopes | Medium | Re-initiate OAuth flow; prompt re-authorization |
| 4 | 50 refresh tokens per client-user pair | User authorizes your app more than 50 times; oldest tokens silently revoked | Medium | Store tokens server-side; never re-prompt unless token is invalid |
| 5 | User explicitly revokes access | User visits Google Account settings and removes your app | Expected | Catch invalid_grant; remove stored token; prompt re-authorization |
| 6 | Sensitive/restricted scope - app not verified | App requests restricted scopes without passing Google verification | Critical | Complete Google OAuth verification or scope down to non-sensitive scopes |
Key insight: The 7-day limit (condition 1) is the most common cause of broken integrations during development. It only applies when your app's OAuth consent screen status is "Testing" and the app has NOT been submitted for Google verification. The Google OAuth verification process is the permanent fix - but it takes time. See section 3 for faster workarounds.
The 7-day testing trap: why it happens, how to escape
The Google OAuth refresh token 7-day expiration is the most disruptive issue developers face during integration. It catches teams off guard: everything works in development, tokens stop refreshing exactly 7 days later, and the error response often appears days after the user authorized the app.
When an app's OAuth consent screen status is set to "Testing" in Google Cloud Console, Google treats it as an unverified application. To protect users, Google automatically expires all refresh tokens issued by unverified apps after exactly 7 days. This policy is documented in Google's OAuth 2.0 documentation and applies regardless of how many times the user has authorized the app. The cap also applies to the 100 test user limit for apps in testing mode. Once a google oauth refresh token expires under this rule, any attempt to use it returns invalid_grant.
Change your app's OAuth consent screen status from "Testing" to "In Production" in Google Cloud Console. For apps requesting sensitive or restricted Gmail scopes, you must complete the full Google OAuth app verification process, including a security audit. Once published, google oauth refresh token lifetime becomes indefinite (subject to the remaining 5 conditions).
If your app is only used within a Google Workspace organization, set the OAuth consent screen user type to "Internal". Internal apps are not subject to the 7-day expiration or the 100 test user cap. Tokens issued to Workspace users under internal apps do not expire based on the testing mode rule. This is the fastest path for B2B SaaS products with Google Workspace customers.
Unipile operates as an independent technical intermediary on behalf of each authenticated user. Our OAuth flow is CASA Tier 2 certified. You can test with non-expiring tokens immediately while your own Google OAuth verification is in progress, then switch to Bring-Your-Own-Credentials (BYOC) once approved. No 7-day limit during your POC phase.
Build your Gmail integration today with tokens that don't expire after 7 days. Unipile handles token refresh on the server side.
Google OAuth refresh token lifetime in production
Once your app is published and verified, Google OAuth refresh token lifetime becomes effectively indefinite - but with important caveats. The two key production rules are the 50-token-per-client-user cap and the 6-month inactivity expiration.
A google oauth refresh token expires if it has not been used to obtain a new access token for 6 consecutive months. "Used" means a successful token refresh call - not an API call made with the resulting access token. Store refresh tokens and schedule periodic silent refreshes to keep them alive. A monthly ping to /token is sufficient.
For Google Workspace apps with user type "Internal", there is no 7-day expiration and no verification requirement. Tokens still observe the 6-month inactivity rule and the 50-token limit per client-user pair. Workspace admins can also revoke tokens organization-wide via the Admin Console, which overrides application-level token management.
Google allows a maximum of 50 refresh tokens per OAuth client ID + Google user account combination. If your app generates a new refresh token (by prompting the user again with prompt=consent) beyond this limit, Google silently invalidates the oldest token. This is a common source of invalid_grant errors in production when teams repeatedly re-authorize users during testing or re-onboarding flows. The fix is simple: store the refresh token server-side and never re-prompt unless the token is actually invalid.
What counts as "using" a google oauth2 refresh token:
Counts as use: POST to https://oauth2.googleapis.com/token with grant_type=refresh_token that returns a new access token
Does NOT count as use: making Gmail API calls with the current access token, even millions of them
Does NOT count as use: calling tokeninfo or introspection endpoints - only the token exchange endpoint resets the inactivity clock
How to refresh an access token: curl, Node.js, Python
When your access token expires, you need to exchange your Google OAuth refresh token for a new one. Here are production-ready code samples for three common environments. All three hit the same Google OAuth token endpoint.
# Refresh a Google OAuth access token using curl
curl -s -X POST \
"https://oauth2.googleapis.com/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "client_id=YOUR_CLIENT_ID" \
-d "client_secret=YOUR_CLIENT_SECRET" \
-d "refresh_token=YOUR_REFRESH_TOKEN" \
-d "grant_type=refresh_token"
# Response: { "access_token": "ya29.XXX", "expires_in": 3600, "token_type": "Bearer" }import requests
import json
def refresh_google_access_token(refresh_token: str) -> str:
"""Exchange a google oauth refresh token for a new access token."""
response = requests.post(
"https://oauth2.googleapis.com/token",
data={
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET",
"refresh_token": refresh_token,
"grant_type": "refresh_token",
},
)
data = response.json()
if "error" in data:
raise ValueError(f"Token refresh failed: {data['error']} - {data.get('error_description')}")
return data["access_token"]// Refresh a Google OAuth access token - Node.js (fetch)
async function refreshGoogleAccessToken(refreshToken) {
const params = new URLSearchParams({
client_id: "YOUR_CLIENT_ID",
client_secret: "YOUR_CLIENT_SECRET",
refresh_token: refreshToken,
grant_type: "refresh_token",
});
const res = await fetch("https://oauth2.googleapis.com/token", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: params.toString(),
});
const data = await res.json();
if (data.error) throw new Error(`${data.error}: ${data.error_description}`);
return data.access_token; // valid for 3600s
}Test interactively: Use the Google OAuth Playground to test the refresh token flow without writing any code. It lets you obtain tokens, inspect responses, and debug the full google oauth2 refresh token lifecycle in a browser UI.
invalid_grant: quick guide
When a Google OAuth refresh token is expired, revoked, or invalid, the token endpoint returns an invalid_grant error. This is the canonical sign that your google oauth refresh token is no longer usable. Here are the most common causes and their immediate fixes.
App is in "Testing" status. Token expired after 7 days. Fix: publish app or switch to internal Workspace.
Token not used for 6 months. Fix: implement keep-alive refresh; schedule monthly token exchange.
User consented too many times; oldest token silently revoked. Fix: store tokens server-side, never re-prompt unnecessarily.
User removed your app from Google Account settings. Fix: delete stored token; display re-authorization prompt to user.
User changed Google password while your app holds sensitive Gmail scopes. Fix: catch error, prompt re-authorization.
Mismatched client_id/secret, or token was issued by a different app. Fix: verify credentials; test with the OAuth Playground.
For a complete breakdown of every Google OAuth error code with HTTP status, cause table, and code-level fixes, see the full Google OAuth errors reference guide.
Managed refresh tokens with Unipile
Building and maintaining a robust Google OAuth refresh token lifecycle is non-trivial engineering work. Unipile acts as an independent technical intermediary on behalf of each authenticated user, handling token storage, refresh scheduling, and error recovery server-side - so your team ships features instead of debugging invalid_grant at 2 AM.
Unipile stores your users' refresh tokens encrypted server-side and proactively refreshes access tokens before they expire. No 401 errors reach your application.
Unipile's own OAuth application has passed CASA Tier 2 security assessment. During your POC, your users authorize via Unipile's verified flow - no 7-day testing limit applies.
When your own Google OAuth verification is approved, switch to BYOC mode: your users authorize via your own verified Google app, while Unipile continues handling token refresh infrastructure.
Unipile provides a single Gmail API abstraction layer that also covers Outlook (Microsoft 365 + Exchange Online) and IMAP - each with its own managed token lifecycle, so you never implement provider-specific refresh logic.
POC with Unipile key: Connect your first authenticated users immediately via Unipile's CASA Tier 2 flow. No 7-day expiration. Full Gmail API access via Unipile's unified endpoint.
Certify in parallel: Submit your own Google app for OAuth verification while running your integration in production. Unipile supports this parallel track.
Switch to BYOC: Once Google approves your app, activate Bring-Your-Own-Credentials mode. Your Google OAuth refresh token lifetime becomes indefinite in production. Unipile continues managing refresh infrastructure.
# List emails via Unipile - refresh token handled server-side
import requests
headers = {
"X-API-KEY": "YOUR_UNIPILE_API_KEY",
"accept": "application/json",
}
# account_id = authenticated user's linked account ID
response = requests.get(
"https://api7.unipile.com:13046/api/v1/emails",
params={"account_id": "acc_XXXXXXXX"},
headers=headers,
)
# Google OAuth refresh token is refreshed automatically by Unipile
emails = response.json()["items"]Google OAuth Refresh Token - FAQ
Common questions about Google OAuth refresh token expiration, lifetime, and the google oauth2 refresh token lifecycle.
Yes, under 6 specific conditions. The most common is the 7-day testing limit: if your app is in "Testing" status in Google Cloud Console, all refresh tokens expire after 7 days. In production with a verified app, tokens are effectively permanent unless: (1) unused for 6 months, (2) user revokes access, (3) password change with Gmail scopes, (4) 50-token cap exceeded, or (5) app loses verification for sensitive scopes. Understanding Google OAuth verification is key to avoiding unexpected expiration.
Google OAuth refresh token lifetime depends on your app's status. In testing mode: 7 days maximum, regardless of use. In production (verified app): no fixed expiration - tokens last indefinitely as long as they are used at least once every 6 months, the 50-token cap is not exceeded, and the user has not revoked access. Google Workspace internal apps also have no 7-day limit.
The google oauth refresh token 7-day expiration applies when your OAuth consent screen status is "Testing" in Google Cloud Console. Google enforces this limit on all unverified applications as a security measure. It also coincides with the 100 test user limit. The fix is to publish your app and complete Google OAuth verification (for production) or set the app to "Internal" if it's Workspace-only.
To prevent the google oauth2 refresh token from expiring due to 6-month inactivity: schedule a monthly POST to https://oauth2.googleapis.com/token with grant_type=refresh_token. This resets the inactivity clock. Note that making Gmail API calls with the existing access token does NOT count as "using" the refresh token - only the token exchange endpoint resets the timer. Store tokens server-side and never re-prompt users unnecessarily to stay under the 50-token cap.
invalid_grant means your google oauth refresh token expiration has been triggered or the token is invalid. Main causes: token expired (7-day testing limit or 6-month inactivity), user revoked access from Google Account settings, 50-token-per-client-user cap was exceeded and this token was displaced, password change with Gmail scopes, or mismatched client credentials. See the full Google OAuth errors guide for detailed remediation steps per cause.
You cannot obtain a new Google OAuth refresh token without user interaction - by design. Refresh tokens are only issued during the user-consent authorization flow. If your token is expired or revoked, you must redirect the user through the OAuth flow again. Adding prompt=consent forces a new consent screen and issues a new token, but counts against the 50-token cap. The best practice is to prevent expiration in the first place: store tokens securely, implement keep-alive refreshes, and handle invalid_grant errors gracefully by prompting re-authorization only when necessary.
POST to https://oauth2.googleapis.com/token with Content-Type: application/x-www-form-urlencoded and body params: client_id, client_secret, refresh_token, and grant_type=refresh_token. The response returns a new access_token valid for 3,600 seconds. If the response contains "error": "invalid_grant", the google api refresh token is no longer valid and user re-authorization is required. See the code samples in section 5 for curl, Python, and Node.js implementations.
Still have questions about Google OAuth refresh tokens? Our team is here to help.