TL;DR
- Salesforce turns on Limit Idle Refresh Token Time-to-Live (TTL) to 30 Days for every connected app and external client app. You cannot turn it off, and Support can no longer change it for you.
- Sandboxes have been enforced on a rolling basis since 21 October 2026. Production, scratch and Developer Edition orgs follow on a rolling basis from 4 November 2026.
- On the day it lands, every refresh token already idle for 30 days or more expires immediately, along with its access token. After that, any refresh token expires once it goes 30 days without use.
- Anything that runs at least once a month keeps working, because each use resets the 30 days.
- The things that break are the ones nobody touches often: quarterly or annual jobs, a reporting tool someone opens once a term, and Salesforce CLI logins, including the sfdx auth URLs that many CI pipelines store as a secret.
- One SOQL query on
OauthTokenlists every token in your org that is already past the limit.
What You'll Learn
- What changes, when it reaches your org, and what each kind of integration sees when its token expires
- How to list every idle token in your org with one query, and how to read Login History for refresh activity
- Why the Salesforce CLI and CI pipelines using an sfdx auth URL are in scope, and what to switch them to
- How to bring connected app metadata back in line with what the org enforces
- What the control does and does not protect you from
The Problem
An OAuth refresh token lets an app get a new access token without anyone logging in again. That is what makes unattended integrations possible: you authorise once, and the integration renews its own access for months or years. Until now, a connected app with the policy Refresh token is valid until revoked handed out tokens that never expired unless someone revoked them.
That convenience is also the risk. A refresh token is a working credential, and a stolen one works for as long as it exists. Salesforce's reason for the change is that: threat actors use stolen OAuth tokens to reach Salesforce data, and expiring idle tokens shortens the window in which a stolen or leaked token is useful.
So Salesforce now caps how long a refresh token can sit unused. The cap is a sliding window of 30 days. Every use resets it. A token that goes 30 days without being used expires, and the app or user has to authorise again.
For an integration that syncs every night, nothing changes. The trouble is everything that runs rarely. The quarterly board report pull, the annual archive job, the tool a finance user connects in January and uses again in April. None of those fail when the control lands. They fail the next time they run, weeks later, when nobody connects the error to a security change.
Common questions this article answers:
- Which of my integrations will stop working when Salesforce enforces the 30-day idle limit?
- What does
invalid_grantwith "expired access/refresh token" mean, and how do I fix it? - Does this affect the Salesforce CLI or my CI pipeline?
- Can I stop Salesforce expiring my integration's refresh token?
Quick Answer
Salesforce enforces a 30-day idle limit on OAuth refresh tokens for all connected apps and external client apps. Sandboxes have been enforced since 21 October 2026 and production, scratch and Developer Edition orgs from 4 November 2026, both on a rolling basis. When it reaches your org, refresh tokens that have been idle for 30 days or more expire at once, and from then on any token expires after 30 days without use. To see what is exposed, run this query as an administrator:
sf data query --target-org my-org --query "SELECT AppName, User.Username, LastUsedDate, UseCount FROM OauthToken WHERE LastUsedDate < LAST_N_DAYS:30 ORDER BY AppName, LastUsedDate"
Every row is a token that will expire, or already has. For each one, decide whether the integration is still needed. If it is, make sure it can re-authorise when it gets invalid_grant with the description "expired access/refresh token", or move it to a flow that does not depend on a stored refresh token. Salesforce asks you not to fake activity just to keep a token alive.
Not sure about a particular integration? Answer two questions about it and the tree tells you whether it will lose its login and what to do.
How does it log in to Salesforce?
One integration at a time
The limit only matters for a refresh token that sits unused for 30 days. Two questions tell you whether this one does, and what to do about it.
- 21 October 2026Sandboxes, on a rolling basis
- 4 November 2026Production, scratch and Developer Edition orgs, on a rolling basis
What Changes, and When
Salesforce enforces the control by switching on Limit Idle Refresh Token Time-to-Live (TTL) to 30 Days in the OAuth settings of every connected app and external client app. That includes managed package apps installed in your org. The text under the setting still says you can ask Salesforce Customer Support to change it. Salesforce's release note says that option goes away at enforcement.
| Org type | Enforced |
|---|---|
| Sandboxes | Rolling, from 21 October 2026 |
| Production, scratch and Developer Edition orgs | Rolling, from 4 November 2026 |
Two groups of apps already had the limit before these dates: partner apps, and any connected app or external client app created in July 2026 or later.
"Rolling" means your org is enforced on some day after the start date, not exactly on it. Your sandboxes may already be enforced. If something there broke in the last two weeks for no obvious reason, this is a likely cause, and it is a preview of production.
At enforcement, three things happen:
- Every refresh token unused for 30 days or longer expires immediately, along with its access token.
- For flows that do not use refresh tokens, any access token whose last login was more than 30 days ago expires immediately.
- From then on, a refresh token expires when it reaches 30 days without use, and an access token from a flow without refresh tokens expires 30 days after its last login.
The second point matters less than it sounds. An integration that runs the JWT bearer flow or the client credentials flow logs in fresh on each run, so it gets a new token every time. It is only affected if it holds on to a single access token for more than 30 days.
What Each Integration Sees
Salesforce's preparation article splits integrations into three groups. This is what each sees when its token expires.
| Integration | What happens | What to do |
|---|---|---|
| Background integration with a refresh token | The refresh request fails with invalid_grant, description "expired access/refresh token" |
Re-authorise the app. Build this into the integration so it does not need a person |
| Interactive app with a refresh token | The user is logged out | The user logs in again |
| Flow without refresh tokens | An API call returns INVALID_SESSION_ID. The services/oauth2/singleaccess endpoint returns invalid_token, "this token is invalid, unknown, or malformed". The introspect endpoint returns "active": false |
Run the initial flow again to get a new access token |
The first row is the one that causes outages. An interactive user notices at once and logs in again. A background job fails silently on a schedule, often at night, and whoever reads its error log may never have heard of this change. If you only do one thing, search your integration logs for expired access/refresh token after enforcement, and make sure that error alerts someone.
Here is the same background job run twice: once inside its 30 days, and once after a quiet quarter.
A sliding window of 30 days
Every time the job uses its refresh token, the 30 days start again. Miss the window once and the token is gone.
Red hops are where the expired token stops the job.
Step 1: List Every Idle Token
The OauthToken object holds one record per OAuth token in the org, with the app name, the user, the date it was last used and how many times it has been used. That makes it the most direct list of what is about to expire.
Run this as an administrator. Replace my-org with the alias of the org you want to check:
sf data query --target-org my-org --query "SELECT AppName, User.Username, LastUsedDate, UseCount FROM OauthToken WHERE LastUsedDate < LAST_N_DAYS:30 ORDER BY AppName, LastUsedDate"
I ran it against my own Developer Edition org the day before production enforcement began. It holds nine tokens, and four of them were idle for more than 30 days. Two belonged to a test app I had not touched since August, and two to a gateway I had stopped using in early September. All four expire when the control reaches the org, and for those four that is the right outcome.
To see the whole picture rather than just the idle tokens, drop the WHERE clause and sort by LastUsedDate. A token used every day will sit at the bottom of the list. One with a recent CreatedDate and a UseCount of 0 was issued and never used again.
For each idle token, ask one question: is this integration still needed?
- No: let it expire, or revoke it now. From Setup, open Users, select the user, find the app in the OAuth Apps section and click Revoke. An unused token is a credential with no owner, and removing it is the point of this change.
- Yes, and it runs at least once a month: check why it looks idle. It may have broken already, or it may log in with a different flow and leave an old token behind.
- Yes, and it really does run less than once a month: it will need to re-authorise every time it runs. Step 3 covers what to do.
Step 2: Read Refresh Activity in Login History
Salesforce's preparation article suggests Login History as a second view. Every time an app uses its refresh token, Login History records a login with the subtype OAuth Refresh Token (or OAuth Refresh Token for Hybrid Apps).
In Setup, enter Login History in the Quick Find box and select it. Click Create New View, filter on Login Subtype equals OAuth Refresh Token and OAuth Refresh Token for Hybrid Apps, and add Login Subtype and Login Time to the columns.
If you prefer a query, this groups the last 90 days by type and application, so each app's last login sits on one line:
sf data query --target-org my-org --query "SELECT LoginType, LoginSubType, Application, COUNT(Id) logins, MAX(LoginTime) lastLogin FROM LoginHistory WHERE LoginTime = LAST_N_DAYS:90 GROUP BY LoginType, LoginSubType, Application ORDER BY MAX(LoginTime)"
Use the two views together. OauthToken tells you which tokens are idle. Login History tells you how each app authenticates, which tells you whether the idle limit applies to it at all. In my org, the CI pipeline shows up as OAuth JWT Bearer, so it logs in fresh on every run and is not affected. The Salesforce CLI shows up as OAuth Refresh Token, more than a hundred times.
Step 3: Check the Salesforce CLI and Your CI Pipelines
This is the part most admins will hit first, because the Salesforce CLI is an OAuth app like any other.
When you run sf org login web, the CLI stores a refresh token for that org on your machine. Every command after that uses the token to get fresh access, which resets the 30 days. So an org you use from the CLI every week is fine. A sandbox you logged into once and have not opened from the CLI for a month is not: its token expires, and commands against that org fail until you log in again.
The fix is one command, run with the same alias as before:
sf org login web --alias my-sandbox --instance-url https://test.salesforce.com
Use https://login.salesforce.com for a production or Developer Edition org, or your My Domain URL.
CI pipelines need more thought. A common pattern is to log in once, run sf org display --verbose, copy the Sfdx Auth Url into a pipeline secret, and log the pipeline in with sf org login sfdx-url. That URL contains a refresh token. A pipeline that runs every day keeps it alive. A deploy pipeline for an org that changes twice a year loses its login between runs and fails at the login step.
You have two options:
- Accept it and re-issue the secret whenever the pipeline has been quiet for a month: log in with
sf org login web, display the new auth URL, and update the secret. This is fine for a pipeline you run by hand and rarely. - Move the pipeline to the JWT bearer flow, which logs in with a certificate on every run and stores no refresh token. This is what the merge request validation workflow uses, and its setup steps work for any pipeline.
Salesforce's guidance rules out a third option. Do not add a scheduled job whose only purpose is to use the token so it never goes idle. The preparation article says plainly not to create artificial refresh activity to prevent expiry. A token kept alive by a ping is the kind of standing, unwatched credential this change exists to remove.
Step 4: Make Background Integrations Recover on Their Own
For anything that genuinely runs less than once a month, Salesforce's advice comes down to three things:
- Handle the error. When a refresh returns
invalid_grantwith "expired access/refresh token", the integration should run its OAuth flow again rather than retry the dead token or fail quietly. - Monitor token acquisition and the jobs themselves, so a failed re-authorisation reaches a person before the next business process needs the data.
- If it cannot re-authorise unattended, design a re-authorisation step someone owns, or move it to a flow without refresh tokens that meets its security requirements. For a server-to-server integration, that usually means JWT bearer or client credentials. The username-password retirement post compares both.
Salesforce also gives a safe way to rehearse expiry. Run the integration's OAuth flow to get a token, then revoke it: Setup, Users, the integration user, OAuth Apps, Revoke. Then run the integration and watch what it does. This tests revocation, not idle expiry, so it shows you how the integration reacts to a dead token rather than reproducing the 30-day timer exactly. It is still the quickest way to find a job that fails silently.
Step 5: Bring App Metadata in Line
Enforcement changes how refresh token policies behave, but Salesforce does not update every app's settings screen and metadata to match. If you keep connected apps in source control, your repository will describe behaviour the org no longer has.
| Refresh token policy | What changes | What to update |
|---|---|---|
| Refresh token is valid until revoked | No longer valid. The option is hidden and the app shows Expire refresh token if not used for 30 days | Metadata is not updated for you. In ConnectedAppOauthPolicy, set refreshTokenPolicy to specific_inactivity:30:DAYS |
| Immediately expire refresh token | Nothing | Nothing |
| Expire refresh token after a fixed time | The fixed limit still applies, and the 30-day idle limit applies as well. A one-year token used regularly lasts a year. An idle one expires after 30 days | Nothing |
| Expire refresh token if not used for a set time | Already a sliding window. The period can no longer exceed 30 days | Neither the UI nor metadata is updated for you. Set the period to 30 days in the app, and set refreshTokenPolicy to specific_inactivity:30:DAYS |
External client apps have their own version of these instructions in Salesforce Help, linked under Resources.
What This Control Does Not Do
The idle limit removes forgotten tokens. It does not limit a token that someone keeps using.
A refresh token under Refresh token is valid until revoked that is used at least once a month still never expires. That includes a stolen token, if the thief uses it often enough. Several of the 2025 and 2026 campaigns against Salesforce customers used OAuth tokens taken from integration vendors they had compromised, as the the Salesforce data theft timeline sets out. An attacker working through your data uses a token far more often than once a month.
So keep the controls that do limit an active token:
- a fixed Expire refresh token after period on apps that do not need indefinite access
- OAuth scopes narrowed to what each integration uses, instead of Full access
- an integration user with only the permissions the integration needs
- monitoring that shows when a token is used from somewhere unexpected
The idle limit helps here too. Every token it expires is one fewer credential an attacker could find, and cleaning up the idle list in Step 1 is worth doing whether or not anything would break.
Frequently Asked Questions
Q: Can I opt out or ask Salesforce to keep my integration's tokens valid?
A: No. Salesforce enables the setting for every connected app and external client app, and the release note says the option to have Customer Support change it is no longer available after enforcement. The supported answer is to make the integration re-authorise when its token expires, or to move it to a flow that does not depend on a long-lived refresh token.
Q: My integration runs every week. Am I affected?
A: Not by the ongoing limit. Each use resets the 30 days, so a weekly job never reaches it. Check OauthToken anyway. An integration that switched flows, or was re-authorised under a new user, can leave an old token behind that shows up as idle. That token expiring breaks nothing. It is just clutter to clean up.
Q: Does this affect the JWT bearer or client credentials flow?
A: Barely. Neither stores a refresh token, so the idle limit on refresh tokens does not apply. The access token cap does, at 30 days from the last login, but an integration using either flow normally logs in at the start of each run and gets a fresh token. It only matters if the integration caches one access token for more than a month.
Q: What do I tell a vendor whose integration stopped working?
A: Tell them the refresh token was revoked because it was idle for more than 30 days, under Salesforce's Refresh Token Idle TTL control. Ask whether their integration handles invalid_grant with "expired access/refresh token" by sending the user back through authorisation. If it does not, someone has to reconnect it each time it lapses, and you should know who that will be before the next quarterly run.
Q: Our sandbox is already enforced. Is that a reliable preview of production?
A: For the behaviour, yes: the same control, the same errors, the same policies. For the impact, only partly. Sandbox integrations often run less often than production ones, so a sandbox can show more breakage than production will, or less, if it was refreshed recently and has few tokens. Run the OauthToken query in production itself before its enforcement reaches it.
Key Takeaways
- Every connected app and external client app now has a 30-day idle limit on refresh tokens. Sandboxes have been enforced since 21 October 2026, and production, scratch and Developer Edition orgs from 4 November, both on a rolling basis.
- Tokens already idle for 30 days expire the moment it lands, so the first failures come from integrations that last ran weeks ago.
- A single query on
OauthTokenlists every token past the limit, with its app, user and last use. - The Salesforce CLI and sfdx auth URLs are in scope. Log in again for quiet orgs, and move rarely used pipelines to JWT bearer.
- Do not fake activity to keep tokens alive. Make integrations re-authorise on
invalid_grant, and updaterefreshTokenPolicyin any connected app metadata you keep in source control.
What's Next?
Run the OauthToken query in production today, before enforcement reaches your org. Revoke the tokens nobody needs, and give each remaining one an owner and a plan for re-authorising. Then search your integration logs for expired access/refresh token in the weeks after enforcement, since the rare jobs will only fail when they next run.
This is one of several OAuth changes landing in the next few months. The device flow restriction arrives on 30 November 2026. The username-password and user-agent flows retire on 20 February 2027. All connected apps must become external client apps by Summer '27. If you are reviewing integrations for one of these, check them against all four in the same pass.
Resources & References
- Refresh Token Idle TTL Is Limited to 30 Days, Winter '27 release note
- Security Change: Refresh Token Idle TTL Is Limited to 30 Days, Salesforce Help knowledge article 005461275, for the preparation steps and error messages
- Limit the Idle Refresh Token TTL in Connected Apps and in External Client Apps, for the policy changes and metadata fixes
- OAuth Security Controls Are Enforced for New External Client Apps and Connected Apps, for apps created from July 2026
- Salesforce Connected Apps & ECA Security Controls: Partner App FAQs
- Winter '27 Release Architect Highlights, Salesforce blog
- Salesforce Security Enforcement 2026: The Complete Guide, for the other changes landing this year
Responses
Checking your session.
Loading responses.