Not verified hands-on. This article was written from primary documentation rather than reproduced in an org, so it carries no tested-release badge. If you run it and something differs, say so in the comments and it gets tested and corrected.

The short answer: most integrations need api and nothing else. Add refresh_token only if your flow issues one, and identity scopes only if you consume the identity endpoint. full is the default on a large number of production apps because it makes the first test pass, and nobody goes back afterwards, because narrowing it later means every client re-authorises.

What a scope is and is not

A scope is a ceiling on what a token may be used for. It is not a grant. A token with api issued to a System Administrator can read and write every record in the org, because the scope restricts the surface, not the identity behind it. If you want less access, change the running user‘s profile and permission sets. The scope list only decides which doors the token fits.

Two rules follow. Scopes limit blast radius across product surfaces, not data. And a minimal-looking scope list on an app running as an admin is security theatre.

What each of the common scopes actually grants

  • api: the data APIs. REST, SOAP, Bulk, Tooling and Metadata all sit behind this one scope. It is what an integration needs, and it is more than people assume.
  • refresh_token (also written offline_access) — permission to issue a refresh token. Not included in full, which surprises people. JWT bearer and client credentials flows do not use one at all, so requesting it there is noise.
  • web: lets an access token open the Salesforce user interface through the frontdoor URL. A token with this scope is close to a browser session. Do not give it to a backend job.
  • visualforce: access to Visualforce pages. Same category of risk as web.
  • openid: returns an ID token and allows the identity endpoint. The correct scope for “log in with Salesforce” and the wrong one for data access.
  • profile, email, address, phone: individual claim groups on the identity response. Ask for the ones you read.
  • chatter_api: Connect REST API only. Useful precisely because it is narrow: a Chatter bot needs this and not api.
  • full: everything the user can do, across every API surface, including scopes Salesforce adds in future releases. That last part is the real objection. You are consenting to a list that changes without you.

Product-specific scopes also exist for CRM Analytics, Data Cloud, Pardot and the Agentforce model APIs. Request one only when you call that product.

Least-privilege sets worth copying

Server-to-server data integration (JWT or client credentials): api. That is the whole list.

User-facing app that reads Salesforce data (web server flow): api refresh_token. Add openid if you need to know who logged in.

Pure single sign-on, no data: openid profile email. No api. If the app never queries an object, do not let its tokens query objects.

Mobile app embedding Salesforce UI: api web refresh_token, and accept that this is a broad token deserving a short refresh token policy.

Checking what a token can really do

Do not trust the app configuration screen. Ask the token. The introspection endpoint returns the scopes a live token was issued with:

curl https://MyDomain.my.salesforce.com/services/oauth2/introspect \
  -d token=$ACCESS_TOKEN \
  -d client_id=$CONSUMER_KEY \
  -d client_secret=$CONSUMER_SECRET \
  -d token_type_hint=access_token

The response includes scope and active. That is the ground truth, and it sometimes differs from the configuration, because a token carries the scopes it was issued with. Narrowing the app’s list does not shrink tokens already out there.

Narrowing an app that is already live

Sequence it, because the failure is an outage.

  1. Work out what the app actually calls. Login History filtered to that app shows the API type in use, which tells you whether anything touches the UI surfaces.
  2. Create a second app with the narrow scope list and point one non-critical client at it.
  3. Move the remaining clients, then revoke the old app’s tokens through Connected Apps OAuth Usage.

Editing the live app instead works, and fails every client at an unpredictable moment when its current token expires. The parallel app costs an hour and removes the guesswork.

Frequently asked

Why does my token request fail with invalid_scope? The requested scope must be a subset of the scopes selected on the app. Requesting something the app does not offer is rejected outright rather than trimmed.

Does full include refresh_token? No. That combination is why so many apps list both, and it is the one legitimate reason to see full in a config.

Is Metadata API a separate scope? No, api covers it. The gate is the user’s Modify Metadata or Modify All Data permission, not the scope.