The integration ran for a year. Then one customer’s sync stopped — and only that customer’s. No deployment, no scope change, no revoked access, nothing in the logs but a refresh call that started coming back rejected.
There is a ceiling on how many refresh tokens Google will hold for one Google Account against one OAuth client ID. Cross it and Google does not reject the new token. It quietly destroys the oldest one, and it notifies nobody — not the user, not the developer, not the failing service.
The number Google publishes
Google states the rule in a single sentence on its OAuth 2.0 overview. The second half of that sentence is the part that costs teams weeks of debugging.
Three consequences follow, and none of them are visible from inside your application.
The counter is a pair, not a device
It counts one Google Account against one OAuth client ID. Every re-consent, every additional device, every environment pointed at the same client consumes a slot for that user.
The damage is retroactive
The request that causes the problem succeeds normally. What breaks is a token issued weeks or months earlier, in a completely different session. Cause and symptom are separated in time, which is why the pattern reads as random.
Service accounts are exempt
Google states plainly that this limit does not apply to service accounts. That exemption is one of the concrete reasons unattended server-to-server work does not belong on an end user’s credentials.
The second limit — the one with no number attached
Underneath the per-client rule sits a second constraint that Google acknowledges and then declines to quantify.
The same page repeats the warning in its token-refresh guidance: limits apply per client-user combination and per user across all clients, the two limits are different, and an application that requests enough refresh tokens to cross either one will find older refresh tokens stop working. Only the first of those two numbers is published.
Google’s own suggested mitigation tells you who the guidance was written for: if you need to authorise multiple programs, machines or devices, limit the number of clients you authorise per Google Account to 15 or 20. That is advice a developer can follow on their own test account. It is not an architecture you can impose on paying customers.
Why this gets misdiagnosed for weeks
What teams blame before they find it
- The user. “They must have removed our app.” A genuine revocation and a silent eviction arrive as the identical failure, with nothing in the response to tell them apart.
- The refresh logic. Retry, backoff and clock-skew handling get rewritten, none of which were ever wrong.
- The hosting environment. Because the failures cluster on whichever accounts happen to hold the oldest tokens, the pattern can look regional or infrastructural.
- The Testing publishing status. A separate and real seven-day expiry does apply to External projects still in Testing. Teams who know about that one frequently assume they have found the cause and stop investigating.
invalid_grant is not a diagnosis
When a refresh fails, Google’s documented meaning is deliberately broad: the token “may have expired or has been invalidated”, and the recommended response is to send the user back through consent. That single error code covers every one of the following, with no distinguishing detail.
Every documented reason a Google refresh token stops working
- The user revoked your app’s access.
- The refresh token has not been used for six months.
- The user changed passwords and the refresh token contains Gmail scopes.
- The user account has exceeded a maximum number of granted (live) refresh tokens.
- The user granted time-based access to your app and that access expired.
- An administrator set one of the services in your app’s scopes to Restricted — this one does surface distinctly, as
admin_policy_enforced. - For Google Cloud Platform APIs, the session length configured by the organisation’s administrator was exceeded.
Exactly one of those seven is caused by your own code. The Google Cloud session-control case is the only other one carrying a usable signal: an error_subtype field (for example invalid_rapt) separates a revoked token from a session-policy expiry, and those session durations can be as short as an hour. Everything else is the same opaque rejection.
The storage bug that imitates the same symptom
Google publishes maximum sizes for each credential type, and reserves the right to vary token size within those limits — applications are expected to accommodate that variation.
A storage column sized for yesterday’s token length truncates the long ones and stores the short ones intact. The result is a subset of users whose refresh silently fails while everyone else is fine — which is precisely what an eviction looks like from the outside. Two entirely different root causes, one indistinguishable symptom.
What actually has to be worked out
The hard part is not knowing the number. It is that the evidence you need to act on does not exist in Google’s error response, so it has to come from your own system.
Establish which ceiling you are actually hitting
Per-client eviction, the undocumented cross-client limit, or one of the other six causes. These are not separable from the error, so the determination is made from issuance history, not from the failure.
Audit issuance, not usage
The damaging event is token creation. Most systems log API failures in detail and log nothing at all about how many refresh tokens they have caused Google to mint per account over the app’s lifetime.
Find the loops minting tokens nobody needs
Offline access combined with a forced consent prompt on every sign-in is the classic multiplier. So is any environment that re-authorises on deploy, and any support process that resolves problems by asking the user to reconnect.
Decide whether the workload belongs on user credentials at all
This is an architecture decision, not a quota one. Google states directly that user credentials must not be used, or encouraged to be used, for server-to-server deployment — a long-running job on a user’s token will eventually fail with no way to re-authenticate it.
Make revocation deliberate rather than incidental
A token you have stopped using should be revoked on purpose. Left in place, it simply waits in the queue, and the eviction order ends up decided by whichever unrelated sign-in happens next.
Where these integrations most often lose tokens
Recurring causes
- One OAuth client shared across staging, production and a background worker, so three environments compete for the same per-user allowance.
- A consent prompt forced on every sign-in, minting a fresh refresh token for a session that already had a valid one.
- A developer or QA account used to exercise the flow hundreds of times, which reaches the undocumented cross-client limit long before any customer does.
- Tokens stored in a field too short for the published maximum, failing only on the longer values.
- Disconnected integrations abandoned rather than revoked, leaving dead tokens holding live slots.
- Unattended server jobs built on an end user’s credentials instead of a service account — the exact case the service-account exemption exists for.
Where this sits in the wider Google picture
Token lifetime problems rarely arrive alone, and they are routinely mistaken for one another. If your tokens are dying on a seven-day cycle rather than unpredictably, the cause is your publishing status and user type, covered in Internal vs External and what each one binds you to. If the whole client stopped working at once rather than one user at a time, the likelier explanation is Google’s automatic deletion of OAuth clients after six months of inactivity. And if the scopes you are requesting are what pulled you into a review in the first place, the sensitive versus restricted scope tiers explain what that costs.
Untangling which of these is actually happening — and then rebuilding the token lifecycle so it stops happening — is the work I do for clients as part of Google OAuth and API verification support: credential and issuance review, scope and identity-model decisions, and preparing the surrounding documentation so a token problem does not turn into a review problem.
Every figure and quotation above is taken from Google’s own OAuth 2.0 documentation at developers.google.com, read on 8 August 2026; the overview page carries a last-updated date of 26 May 2026. Google changes these mechanics without notice — verify against the current official pages before building around them. This is independent technical support and is not affiliated with, endorsed by, or a partner of Google. The service offered is review preparation, policy-aligned guidance and technical implementation support; no specific review outcome or timeline can be promised by any provider.