Handle Token Refresh
Keep your Gmail API access alive by refreshing tokens before they expire.
When to refresh
Access tokens expire after 1 hour (3600 seconds). The token response includes an expires_in field that tells you exactly when.
You have two options:
- Proactive: Check if the token is expired before each API call
- Reactive: Catch 401 Unauthorized responses from Gmail and refresh then
Both work. Proactive is cleaner. Reactive handles edge cases like manual revocation.
Refresh API call
To get a new access token, POST to the token endpoint with grant_type: "refresh_token":
const response = await fetch("https://inbox.dog/oauth/token", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
grant_type: "refresh_token",
refresh_token: "YOUR_REFRESH_TOKEN",
client_id: "YOUR_CLIENT_ID",
client_secret: "YOUR_CLIENT_SECRET"
})
});
const data = await response.json(); Response shape
On success, you get a new access token:
{
"access_token": "ya29.a0ARrdaM...",
"token_type": "Bearer",
"expires_in": 3600
} The refresh_token stays the same. You don't get a new one.
Complete helper function
Here's a complete TypeScript function that handles token refresh automatically:
interface TokenCache {
access_token: string;
expires_at: number; // Unix timestamp in milliseconds
}
const tokens = new Map<string, TokenCache>();
async function callGmailAPI(
refreshToken: string,
gmailApiUrl: string,
clientId: string,
clientSecret: string
): Promise<any> {
// Check if we have a cached, valid token
let cached = tokens.get(refreshToken);
if (!cached || Date.now() >= cached.expires_at) {
// Token expired or missing — refresh it
const response = await fetch("https://inbox.dog/oauth/token", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
grant_type: "refresh_token",
refresh_token: refreshToken,
client_id: clientId,
client_secret: clientSecret
})
});
if (!response.ok) {
const error = await response.json();
throw new Error(`Token refresh failed: ${error.error}`);
}
const data = await response.json();
// Cache the new token (expires_in is in seconds)
cached = {
access_token: data.access_token,
expires_at: Date.now() + (data.expires_in * 1000) - 60000 // 1min buffer
};
tokens.set(refreshToken, cached);
}
// Make the Gmail API call with the valid token
const gmailResponse = await fetch(gmailApiUrl, {
headers: {
Authorization: `Bearer ${cached.access_token}`
}
});
return gmailResponse.json();
}
// Usage
const messages = await callGmailAPI(
userRefreshToken,
"https://gmail.googleapis.com/gmail/v1/users/me/messages",
process.env.CLIENT_ID!,
process.env.CLIENT_SECRET!
); Refresh is FREE
Token refresh does not cost credits. You only pay for the initial OAuth flow. Refresh as many times as you need.
Failure mode
If the user revokes access in their Google account settings, the refresh token becomes invalid. You'll get an error response:
{
"error": "TOKEN_EXCHANGE_FAILED"
} When this happens, the user needs to re-authenticate. Send them back through the OAuth flow. See error handling docs for details.