# Salesforce integration users (/integrations/salesforce-service-account)





Connect Salesforce through an External Client App and a dedicated integration user. Sim supports client credentials and JWT bearer authentication; API calls use the selected integration user's profile and permission sets.

An **API-only** integration user cannot complete interactive OAuth. Both server-to-server flows support that user type.

Sim supports both server-to-server flows. Pick one:

| Feature | **Client credentials** | **JWT bearer** |
| - | - | - |
| What Sim stores | Consumer key + **consumer secret** | Consumer key + **private key** |
| Who calls run as | The app's **Run As** user | The **username** you name in Sim |
| Salesforce setup | Enable Client Credentials Flow, set Run As | Upload a certificate, pre-authorize the user |
| Pick it when | You want the simplest setup | Your security policy forbids shared secrets, or you want one app to serve several integration users |

Both flows authenticate the Salesforce tools as an integration user. The user's license and permissions determine which operations can succeed.

## Prerequisites [#prerequisites]

You need a Salesforce **admin** to create the External Client App and the integration user. Every API call Sim makes runs with the integration user's permissions, so plan that user's profile and permission sets deliberately.

## Set up the External Client App [#setting-up-the-external-client-app]

### 1. Create a dedicated integration user [#1-create-a-dedicated-integration-user]

<Steps>
  <Step>
    In Salesforce, go to **Setup** → **Users** → **New User**. Set **License** to **Salesforce Integration** (an API-only license, where available in your org) and **Profile** to **Minimum Access - API Only Integrations**
  </Step>

  <Step>
    Create a permission set backed by the **Salesforce API Integration** permission set license, grant it the object and field permissions your workflows need (least privilege), and assign it to the user
  </Step>
</Steps>

If your org doesn't have Salesforce Integration licenses, any standard user with the **API Enabled** permission works too — but a dedicated API-only user keeps the credential's access explicit and auditable.

### 2. Create the External Client App [#2-create-the-external-client-app]

External Client Apps are Salesforce's current-generation connected apps and the default way to create new OAuth apps.

<Steps>
  <Step>
    Go to **Setup** → **External Client App Manager** → **New External Client App**, give the app a name and contact email, and keep it local to your org (not packaged for distribution)
  </Step>

  <Step>
    Open the app's **Edit Settings** → &#x2A;*API (Enable OAuth Settings)** section and turn on **Enable OAuth**
  </Step>

  <Step>
    Enter any placeholder **Callback URL** (e.g. `https://login.salesforce.com/services/oauth2/callback`) — it's required by the form but unused by this flow
  </Step>

  <Step>
    Add the OAuth scopes &#x2A;*Manage user data via APIs (api)** and &#x2A;*Access unique user identifiers (openid)** — `api` is required for the flow, and `openid` lets Sim look up the integration user's name via the userinfo endpoint (the instance URL comes back in the token response itself)
  </Step>

  <Step>
    **Client credentials only:** enable the **Client Credentials Flow** in the OAuth settings and acknowledge the warning. Skip this if you're setting up JWT bearer — that flow has its own toggle (see below)
  </Step>

  <Step>
    Create the app
  </Step>
</Steps>

### 3. Configure the Run As user [#3-configure-the-run-as-user]

<Steps>
  <Step>
    Open your app in **External Client App Manager** and edit its **Policies**
  </Step>

  <Step>
    Under **OAuth Policies** → **Client Credentials Flow**, set **Run As** to the integration user from step 1, then save. That page holds only the Run As picker — the **Enable Client Credentials Flow** checkbox itself lives in **Edit Settings** → **OAuth Settings**, where you set it in step 2
  </Step>
</Steps>

Every API call Sim makes executes with this user's permissions.

### 4. Copy the consumer key and secret [#4-copy-the-consumer-key-and-secret]

Open the app's **Settings** → **OAuth Settings** and click **Consumer Key and Secret** (Salesforce prompts for identity verification). Copy the **Consumer Key** and **Consumer Secret**.

<Callout type="warn">
  The Consumer Secret plus the Run As configuration is full API access as the integration user. Treat both values like passwords — do not commit them to source control or share them publicly. Sim encrypts them at rest.
</Callout>

### Use an existing Connected App [#using-an-existing-connected-app-legacy]

<Callout type="warn">
  Creating **new** Connected Apps is blocked by default: Salesforce restricted creation as of Spring '26 — re-enabling it requires a request to Salesforce Support. Use an External Client App for new setups.
</Callout>

If you already have a classic Connected App, it keeps working and the credential fields are identical — the token endpoint and Sim configuration don't change. Configure it the classic way:

<Steps>
  <Step>
    In **Setup** → **App Manager**, confirm the app has **Enable OAuth Settings**, the &#x2A;*Manage user data via APIs (api)** and &#x2A;*Access unique user identifiers (openid)** scopes, and **Enable Client Credentials Flow** checked
  </Step>

  <Step>
    From **App Manager**, open the app's **Manage** page, click **Edit Policies**, and set **Run As** under **Client Credentials Flow** to your integration user. Permitted Users policies don't apply to the Client Credentials Flow's execution user, so no pre-authorization is needed
  </Step>

  <Step>
    Open the app with **View** and click **Manage Consumer Details** to copy the **Consumer Key** and **Consumer Secret**
  </Step>
</Steps>

### Use JWT bearer authentication [#using-the-jwt-bearer-flow-instead]

The JWT Bearer Flow authenticates with an uploaded certificate rather than a shared secret, and runs as a username you name in Sim rather than the app's Run As user. Set this up **instead of** steps 3 and 4 above. Create the integration user and app as above, skipping the Client Credentials Flow toggle. Then configure JWT bearer below and find the My Domain host in step 5.

<Steps>
  <Step>
    Generate a key pair. `server.key` stays with Sim; `server.crt` is uploaded to Salesforce:

    ```bash
    openssl req -x509 -sha256 -nodes -days 3650 -newkey rsa:2048 \
      -keyout server.key -out server.crt -subj "/CN=sim-salesforce"
    ```

    Keep `server.key` somewhere safe — Sim stores it encrypted and never shows it again.

    <Callout type="warn">
      Salesforce requires an **RSA** key of at least 2048 bits for this flow; an ECDSA key is rejected. The uploaded certificate must also stay under 4 KB, which a 2048-bit self-signed cert comfortably is.
    </Callout>
  </Step>

  <Step>
    In **External Client App Manager** → your app → **Edit Settings** → **OAuth Settings**, turn on **Enable OAuth** (the JWT toggle does not appear until it is on), then **Enable JWT Bearer Flow**, and use **Upload Files** to upload `server.crt`. On a legacy Connected App the equivalent is **Use digital signatures** with the same file
  </Step>

  <Step>
    Edit the app's **Policies** → **OAuth Policies** and set **Permitted Users** to **Admin approved users are pre-authorized**

    <Callout type="warn">
      This step is mandatory. The JWT flow requires prior approval of the app, and an API-only integration user can never grant that approval interactively — there is no UI for them to log in to. Without pre-authorization every token request fails with `user hasn't approved this consumer`.
    </Callout>
  </Step>

  <Step>
    Assign the integration user's **profile** or a **permission set** to the app, so that user is covered by the pre-authorization

    <Callout type="warn">
      Assign it through the **profile**, or through a **second permission set that does *not* carry the Salesforce API Integration permission set license**. A permission set backed by that license cannot hold an **Assigned Connected Apps** / **Assigned External Client Apps** section at all, so the app can never be assigned from the same permission set that grants the user its object access. Getting this wrong produces exactly the `user hasn't approved this consumer` failure this step exists to prevent.
    </Callout>
  </Step>

  <Step>
    Copy the **Consumer Key** as in step 4. There is no consumer secret to copy — the JWT flow doesn't use one
  </Step>
</Steps>

### 5. Find your My Domain host [#5-find-your-my-domain-host]

Go to **Setup** and search for **My Domain**. The host is required — Salesforce rejects the Client Credentials Flow at `login.salesforce.com` and `test.salesforce.com`. Depending on your org type it looks like:

* **Production:** `yourorg.my.salesforce.com`
* **Sandbox:** `yourorg--sandboxname.sandbox.my.salesforce.com`
* **Developer Edition:** `yourorg.develop.my.salesforce.com` (Salesforce appends `-dev-ed` only when it generated the name for you)

Sim also accepts other partitioned My Domain hosts (`scratch`, `demo`, `patch`, `trailblaze`, `free`).

<Callout type="warn">
  Use the `my.salesforce.com` host, not the `my.salesforce-setup.com` host you see in the address bar while working in Setup, and not `lightning.force.com`. Government Cloud Plus domains (`*.my.salesforce.mil`) are not currently supported.
</Callout>

## Permissions and license limits [#permissions-instead-of-scopes]

There's no scope picking beyond the **api** and **openid** scopes on the app — what the credential can actually do is the integration user's profile plus permission sets. In particular:

* Object and field access for every object your workflows read or write, plus **API Enabled**
* **Customize Application** for tools that manage custom fields and custom objects (Tooling API) — a Minimum Access API-only user passes validation but fails these specific tools without it
* **Run Reports** and folder access for report and dashboard tools

A permissions gap surfaces at run time as a Salesforce API error; fix it on the integration user's permission sets — no changes are needed in Sim.

<Callout type="warn">
  On the **Salesforce Integration** (API-only) license specifically, SOQL and CRUD on standard objects are well supported, but two areas are not safe to assume:

  * **Reports and dashboards** are unverified on this license. Salesforce documents neither a grant nor a prohibition. Test them in a sandbox before depending on them, and remember the user also needs access to the report or dashboard **folder**.
  * **Apex-related access requires separate verification.** Do not assume this license or its permission-set license can grant the Apex Class Access required by an operation. Custom field and object tools have their own **Customize Application** requirement.

  If a workflow must run reports, verify them with the intended license in a sandbox, or use a standard-seat integration user with the required report and folder access.
</Callout>

## Add the integration user app to Sim [#adding-the-integration-user-app-to-sim]

<Steps>
  <Step>
    Open **Integrations** from your workspace sidebar
  </Step>

  <Step>
    Search for "Salesforce" and open it, then click **Add to Sim** and choose **Add integration user app**
  </Step>

  <Step>
    In the **Add Salesforce integration user app** dialog, pick the **Authentication method** you configured. The dialog then asks only for that flow's fields:

    * **Client credentials** — **Consumer key** and **Consumer secret**
    * **JWT bearer** — **Consumer key**, the **Private key** (paste the whole `server.key` file, `-----BEGIN` line included), and the **Run as username** (the integration user's Salesforce username, e.g. `integration.user@yourorg.com`)

    Then paste your **My Domain host** (e.g. `yourorg.my.salesforce.com`) and optionally set a display name and description
  </Step>

  <Step>
    Click **Add integration user app**. Sim verifies the credentials by minting a real access token against your My Domain host. A host that doesn't resolve gets its own error message; every other misconfiguration surfaces as a general authentication error — re-check the values and the app's OAuth policies.
  </Step>
</Steps>

## Use the credential in workflows [#using-the-credential-in-workflows]

Add a Salesforce block to your workflow. In the credential dropdown, select the saved Salesforce integration user app. Configure the selected operation.

The block calls your org's REST API with a freshly minted access token — the same requests as the OAuth flow, with access governed by the integration user's license and permissions.

## Token behavior [#token-behavior]

Neither flow returns a refresh token or `expires_in`. Opaque access-token lifetime follows Salesforce session policy; the documented default is two hours, with settings from 15 minutes to 24 hours. Sim caches tokens conservatively for at most 10 minutes and mints replacements. JWT-format access tokens can carry an `exp` claim that shortens that cache window. Connection or token failures can still stop a workflow.

<Callout type="warn">
  Deactivating or freezing the integration user — the Run As user for client credentials, or the run-as username for JWT bearer — stops all token minting with an `invalid_grant` error, halting every workflow that uses the credential. Password policies that expire the user's API access have the same effect. Treat the integration user as production infrastructure.
</Callout>

## Troubleshooting and rotation [#troubleshooting-and-rotation]

| Problem | What to check |
| - | - |
| JWT: `user hasn't approved this consumer` | Set **Permitted Users** to **Admin approved users are pre-authorized** and assign the run-as user's profile or permitted permission set to the app. An API-only user cannot grant interactive approval. |
| JWT: `app_not_found` or audience error | Use the My Domain host for the org containing the app and that app's consumer key. |
| JWT: invalid assertion or signature | Upload the certificate that matches the private key in Sim. Confirm that you pasted the private key rather than `server.crt`. |
| Passphrase-protected key | Sim has no passphrase field. Decrypt it first with `openssl rsa -in server.pass.key -out server.key`. PKCS#1 (`BEGIN RSA PRIVATE KEY`) and PKCS#8 (`BEGIN PRIVATE KEY`) PEM containers are accepted. |
| Client credentials: authentication rejected | Enable the flow in the app's settings and configure an active **Run As** user in its policies. Check the consumer key and secret. |
| A tool fails after connection succeeds | Grant the integration user the needed object/field permissions, **Customize Application** for schema tools, or **Run Reports** plus folder access for reports. |
| The integration user is frozen or deactivated | Reactivate it or choose an active integration user. Change the app's Run As policy for client credentials; for JWT, reconnect with the new username and pre-authorize that user. |

For client credentials, rotate the consumer secret in the app's OAuth settings (**Consumer Key and Secret**, or **Manage Consumer Details** for a legacy app). For JWT bearer, generate a matching key/certificate pair and replace the uploaded certificate and stored private key together. In Sim, open the saved credential under **Integrations**, select **Reconnect**, and restate the authentication method and required fields. Stored secrets are not returned to populate the dialog.

See Salesforce's [External Client App setup](https://developer.salesforce.com/docs/platform/sfdx-dev/guide/sfdx-dev-auth-eca.html) and [OAuth app guidance](https://developer.salesforce.com/docs/platform/api-rest/guide/intro-oauth-and-connected-apps.html).
