Salesforce connector setup
Operator·6 minutes to read
This page is the setup half — creating the OAuth app Salesforce needs in the org's own Setup and getting its consumer key and secret into this deployment. Authorising a connection afterwards is the Authenticate control on the plugin sheet, which is what actually sends you to Salesforce.
1. Create the OAuth app in Salesforce Setup
Salesforce blocks creating new Connected Apps since its Spring '26 release, so the app is an External Client App. Doing this needs the System Administrator profile (or the permissions to manage connected apps); a regular sales user cannot.
-
In Setup, open App Manager (Quick Find: App Manager) and click New External Client App — this is your own app, in the org your members will connect, not Botseon's.
-
Fill in the name and contact email.
-
Under API (Enable OAuth Settings), click Enable OAuth and paste the callback URL from
docs/connectors/README.md— with the quickstart's defaultBOTSEON_PUBLIC_URL, that is:http://127.0.0.1:3000/api/oauth/salesforce/callbackIt must match exactly.
-
Under OAuth Scopes, select the two in section 2.
-
Create the app, then copy its Consumer Key and Consumer Secret.
Salesforce takes up to about ten minutes to make a new or edited app's OAuth settings available everywhere. A connect attempt that fails in the first ten minutes after saving is not a configuration error: wait and try again.
If the app's Permitted Users policy is "Admin approved users are pre-authorized", only users whose profile or permission set is assigned to the app can connect.
2. Scopes
Request the narrowest set that covers the tools this connector exposes (F-PLUG-1: minimum scopes per capability):
api— "Manage user data via APIs": the REST API, as the connected user.refresh_token— "Perform requests at any time": stay connected without signing in again.
Salesforce has no read-only API scope — api covers reading and changing records alike — so the
plugin sheet shows one sentence for the grant rather than a scope list: Salesforce grants all or
nothing, so this account can read and change everything the connected user can. The separation
lives in the tools instead: every tool that writes raises an approval card first. What the
connected user can see and change is still bounded by their own Salesforce profile and sharing
rules.
3. Enter the app
One command, with the client secret on standard input — there is no flag that takes it and no
environment variable that holds it (see
docs/connectors/README.md):
printf %s "$CLIENT_SECRET" | botseon oauth-app set \
--org <organisation id> --slug salesforce \
--client-id <the consumer key from step 1> \
--auth-url https://<instance>.my.salesforce.com/services/oauth2/authorize \
--token-url https://<instance>.my.salesforce.com/services/oauth2/token \
--configured-by <your user id>
Both URLs are the org's own My Domain address — Salesforce recommends it over a generic login
host, and it is the same <instance> the plugin variable below names. Use the my.salesforce.com
address, never the lightning.force.com one.
It stores the secret encrypted and prints the redirect URL from step 1 back, so you can check the
two match. botseon oauth-app show --org <organisation id> --slug salesforce says whether an app
is configured and under which client id; nothing prints the secret back.
Then set the plugin's Salesforce instance: the part of your org's address before
.my.salesforce.com. For https://acme.my.salesforce.com enter acme; for a Developer Edition at
https://acme-dev-ed.develop.my.salesforce.com enter acme-dev-ed.develop; for a sandbox at
https://acme--uat.sandbox.my.salesforce.com enter acme--uat.sandbox.
Never put either value in a bot's description, a memory, or anywhere else a model reads as ordinary text.
4. What this connector does and does not do
salesforce.soql_query— runs a SOQLSELECT. The query must end withLIMIT n, at most 200, so a tool call reads one page.salesforce.get_record— one record by its id, all fields or the ones named.salesforce.search_records— finds records across up to five objects (Accounts, Contacts and Opportunities by default).salesforce.list_objects— lists the objects the connected user can read, standard and custom, by label and API name (Invoice__c), so a bot does not have to guess a custom object's name. It can narrow the list to custom objects or to a piece of the name.salesforce.describe_object— lists one object's fields with their types, whether the user may edit each one, and a picklist's active values. Salesforce leaves out any field the connected user's field-level security hides, so a bot never sees one. It also names each lookup's relationship (Account__r) and, up to 100, the child relationships a subquery walks (Contacts), so a bot can query across objects.salesforce.create_record— creates one record. Taggedwrite; the approval card names the object.salesforce.update_record— changes one record's fields. Taggedwrite; the approval card names the record.
This connector pins Salesforce REST API version 67.0 (Summer '26). Salesforce gives at least a year's notice before retiring a version.
API access depends on the Salesforce edition. It is not available on Starter Suite or Free Suite, is a paid add-on on Pro Suite, and is included from Enterprise Edition up. If calls fail with a message that the REST API is not enabled for the organization, the edition is the cause.
Each org has a rolling 24-hour API allocation shared by every integration it runs; when it is used up, a bot's Salesforce calls pause until the allocation recovers.
Where an org's data is stored — including the Hyperforce EU regions and the paid EU Operating Zone — is chosen when the org is provisioned; it is the org's own address this connector calls either way.
This connector's catalogue row is available: true since 2026-10-21, when its live smoke passed
against a real Developer Edition org (connector-live run 39571882193). Members can add it from
Plugins as soon as this deployment holds the app from section 3; until then, connecting stops
at the missing app.
5. The nightly live test (client credentials)
The connector-live workflow's Salesforce case (packages/connectors/src/salesforce.live.test.ts)
gets its access token with the client credentials flow, a fresh token on every run. That is
the supported path, for one reason: the test org's External Client App rotates refresh tokens, and
Salesforce turns that setting on by default and locks it, so it cannot be unticked. A refresh token
stored as a secret therefore works for exactly one run — the refresh hands back a new refresh token
the workflow cannot save — and every later run fails with invalid_grant.
The client credentials flow has no refresh token at all. Per Salesforce Help, "OAuth 2.0 Client Credentials Flow for Server-to-Server Integration" (retrieved 2026-10-20), the test POSTs to the org's own My Domain token endpoint:
POST https://<instance>.my.salesforce.com/services/oauth2/token
grant_type=client_credentials&client_id=<consumer key>&client_secret=<consumer secret>
Salesforce does not accept scopes on that request — they come from the app — and the token acts as the app's run-as user, so what the test can read is bounded by that user's profile.
To set it up on the test app: under the app's OAuth settings, enable Client Credentials Flow,
and in its policies pick a run-as user (an API-enabled user in the test org). The workflow then
needs only SALESFORCE_TEST_INSTANCE and SALESFORCE_TEST_CLIENT_ID (repository variables) and
SALESFORCE_TEST_CLIENT_SECRET (a secret). If the flow is refused, the test falls back to
SALESFORCE_TEST_REFRESH_TOKEN and then to a stored SALESFORCE_TEST_TOKEN, if either is set;
neither is needed any more. No token or secret is ever printed — a failed token request names only
the HTTP status and Salesforce's error code.
Last verified against build 331481c.