INVALID_SESSION_ID: Session expired or invalid
Your API call presented a session token Salesforce no longer accepts — it timed out, was logged out from elsewhere, was issued by a different org, or your IP fell outside the user's login restrictions. The fix depends on which of those it is.
Also seen asINVALID_SESSION_ID·Session expired or invalid·INVALID_SESSION_ID: Session expired
Every API request to Salesforce carries a session — either an OAuth access token or a SOAP sessionId. The platform invalidates sessions for several reasons, and the same error code covers all of them.
The five fingerprints
| Symptom | Likely cause |
|---|---|
| Worked yesterday, fails today | Token expired (default sessions live ~2 hours, refresh tokens last longer) |
| Works for a minute, then fails for hours | The user logged out via the UI, killing all their sessions |
| Always fails from one network, fine from another | IP login restriction on profile, or a security gate (My Domain, Real-time Event Monitoring) |
| Worked in dev sandbox, fails in production | Sandbox tokens don't work against production hostnames |
| Worked yesterday, fails after a "Salesforce maintenance" email | Forced session reset during an org migration |
OAuth: refresh, don't re-login
If you're using OAuth (Connected App), the access token has a short lifetime but the refresh token does not. Catch the 401/INVALID_SESSION_ID, exchange the refresh token for a new access token, retry once.
async function callSalesforce(url, opts, retries = 1) {
const res = await fetch(url, { ...opts, headers: { ...opts.headers, Authorization: `Bearer ${state.accessToken}` } });
if (res.status === 401 && retries > 0) {
state.accessToken = await refreshAccessToken();
return callSalesforce(url, opts, retries - 1);
}
return res;
}
Don't loop on retry — one refresh attempt, then surface the error. Looping on a bad refresh token logs the user out faster than a sledgehammer.
SOAP: re-login
The SOAP login() call returns a session that obeys the Session timeout setting on the user's profile. There's no refresh; on expiry, you call login() again.
For long-running back-end integrations, prefer OAuth's JWT bearer flow — no user, no interactive login, the JWT proves identity. Refresh tokens are unnecessary because every request is independently signed.
The hostname gotcha
A token issued by login.salesforce.com is bound to a specific instance hostname returned in the OAuth response (instance_url). If you call https://login.salesforce.com/services/data/... with a token bound to https://yourcompany.my.salesforce.com/..., you get this error. Always honour the instance_url returned at login.
When the integration itself didn't change but the org did
Salesforce regularly enables stricter session settings org-wide — IP restrictions, "lock sessions to current IP," shorter timeouts, MFA enforcement on connected apps. A deploy to production that suddenly breaks an old integration usually means a security setting changed, not the integration. Check the Setup → Audit Trail for recent session/login policy changes.
