← Docs

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.

Next steps