Skip to content

Token Lifecycle in Practice

Every OAuth flow guide mentions refresh_token, but a long-running integration (one that keeps running across many requests, workers, or days) needs more than “swap the token when it expires.” This page covers real token lifetimes, caching a token across workers, when to refresh, and what can invalidate a token before it would otherwise expire.


The access token’s lifetime is one fixed duration, the same for every flow. The refresh token’s lifetime is not: it depends on which OAuth flow issued it.

FlowAccess token TTLRefresh token TTL
Client CredentialsAbout 1 hourAbout 30 days
Native app (PKCE, loopback redirect)About 1 hourNo fixed expiry: stays valid until it’s rotated away or revoked
Authorization Code + custom HTTPS redirectAbout 1 hourNo fixed expiry: stays valid until it’s rotated away or revoked

Cache the token, don’t mint one per request

Section titled “Cache the token, don’t mint one per request”

Requesting a fresh access token for every API call, or independently in every worker process, works but wastes a round trip on every single request for a token that’s already good for about an hour. Request one token, cache it (in memory for a single process, or in a shared store such as Redis for a fleet of workers) keyed by the credential that issued it, and hand the cached token to every request until it’s close to expiry.

This also matters for the refresh token: the fewer processes that independently exchange it, the less likely you are to hit the race described below.

Because the access token TTL is fixed and known in advance, refresh proactively: track when you obtained the token (or decode its exp claim) and request a new one shortly before that time, rather than waiting for a request to fail first.

Still handle a failed request as a fallback, but branch on which error you got:

  • 401 with not_authenticated: the request didn’t carry a usable bearer token at all. This comes from a missing or malformed Authorization header on your side, not a signal to refresh.
  • 401 with invalid_token: the token’s signature or expiry check failed. Refresh and retry once.
  • 403: never a token problem. The token is valid; the caller doesn’t have the scope, content access, or role the operation requires. Refreshing will not help. See Error Codes: rate limiting, authentication, and access control for the full breakdown of what each code means.

A refresh token is single-use: exchanging it with grant_type=refresh_token invalidates that refresh token and issues a new access token and a new refresh token together. If several workers share one cached refresh token and two of them try to refresh at nearly the same moment, only one exchange succeeds; the other gets a generic 401, not a distinguishable “already used” error.

None of the events below revoke a live access token outright. Each one blocks something new (a fresh token request, or the next refresh) while the access token already in hand keeps working until its own TTL runs out.

TriggerEffect
The OAuth application’s client secret is rotatedNew token or refresh requests using the old secret are rejected immediately. An access token already issued is unaffected and keeps working until it naturally expires.
The OAuth application or its service user is removedBlocks minting new tokens for that application. Tokens already issued are not proactively revoked.
The OAuth application’s scopes are changedAn access token already issued keeps its original scopes for the rest of its life. The token only picks up the new scopes the next time it’s refreshed or reissued.
The grant is explicitly revokedBlocks the next refresh attempt immediately. The still-valid access token is not force-expired and keeps working until it naturally expires.