import { Tabs, Callout, Steps } from "nextra/components";
import ToggleContent from "@/app/_components/toggle-content";
import { SignupLink } from "@/app/_components/analytics";
# Salesforce
The Salesforce auth provider enables tools and agents to call Salesforce APIs on behalf of a user.
## What's documented here
This page describes how to use and configure Salesforce auth with Arcade.
This auth provider is used by:
- The [Arcade Salesforce MCP Server](/resources/integrations/sales/salesforce), which provides pre-built tools for interacting with Salesforce services
- Your [app code](#calling-salesforce-apis-directly) that needs to call Salesforce APIs
- Or, your [custom tools](#create-your-own-salesforce-tools) that need to call Salesforce APIs
## Create a Salesforce app
When using your own app credentials, make sure you configure your project to
use a [custom user
verifier](/guides/user-facing-agents/secure-auth-production#build-a-custom-user-verifier).
Without this, your end-users will not be able to use your app or agent in
production.
**Salesforce Spring '26 Update:** Starting Spring '26, Salesforce is recommending [External Client Apps](https://help.salesforce.com/s/articleView?id=xcloud.external_client_apps.htm&type=5) instead of Connected Apps for new OAuth integrations. If you haven't created an app yet, use an External Client App - the OAuth configuration is identical and works the same way with Arcade. Existing Connected Apps will continue to work without any changes.
Salesforce has two types of apps: **Connected App** and **Lightning App**. For this guide, we'll create a **Connected App**. Make sure to follow the instructions below while you [create your Connected App](https://help.salesforce.com/s/articleView?id=xcloud.connected_app_create.htm&type=5).
When creating your app, make sure to:
- Under "API (Enable OAuth Settings)", check the **Enable OAuth Settings** box
- Set the callback URL to the redirect URL generated by Arcade (see below)
- In the **Available OAuth Scopes**, add the two following scopes:
- "Manage User Data Via APIs (api)"
- "Perform requests at any time (refresh_token, offline_access)"
- Uncheck the **Require Proof Key for Code Exchange (PKCE)** option, unless you want to use PKCE (in which case, you'll also need to enable PKCE in the Salesforce auth configuration as [described below](#configuring-salesforce-auth))
- Check "Enable Token Exchange Flow"
- Check "Enable Refresh Token Rotation"
- Leave all other settings as default and save your app
Right after creating the app, Salesforce will redirect you to the app's page. In the "API (Enable OAuth Settings)" section, click on the **Manage Consumer Details** button and take note of the **API Key** and **Client Secret** values.
Then, go back to the App's page and click on the **Manage** button at the top, then click on the **Edit Policies** button, at the top of the Manage page, and follow the instructions below:
- In "IP Relaxation", select **Relax IP Restrictions**.
- In "Refresh Token Policy", make sure the option **Refresh token is valid until revoked** is checked.
With that, your Salesforce app is ready to be used with Arcade.
## Get your Salesforce Org Subdomain
Follow the steps below to find your Salesforce Org Subdomain:
1. In the Setup menu, click on **Quick Find** in the top left corner and type `"my domain"`.
1. In the search results, under **Company Settings**, click on **My Domain**.
1. Under **My Domain Details**, check the value of the **Current My Domain URL** field.
Your **Salesforce Org Subdomain** is the value before the `.my.salesforce.com` part. For example, if your Salesforce domain is `https://acme-inc.my.salesforce.com`, your Salesforce Org Subdomain is `acme-inc`. If you have a developer account, your URL might look like `https://acme-inc.develop.my.salesforce.com`. In this case, your Salesforce Org Subdomain is `acme-inc.develop`.
Take note of your Salesforce Org Subdomain. You will need this value in the next steps.
## Set the Salesforce Org Subdomain Environment Variable
{/* The step is right before, but the note is intended for people coming from different pages that link directly to this sub-heading */}
Refer to the [previous step](#get-your-salesforce-org-subdomain) to find your
Salesforce Org Subdomain.
Set the `SALESFORCE_ORG_SUBDOMAIN` environment variable in the same runtime where your Arcade Worker is executed:
```bash
export SALESFORCE_ORG_SUBDOMAIN={your-salesforce-subdomain}
```
## Create and Assign Custom Scopes to your Connected App
The Salesforce API requires the App developer to create [OAuth custom scopes](https://help.salesforce.com/s/articleView?id=xcloud.remoteaccess_oauth_customscopes.htm&type=5) defining granular permissions for their application users to authorize.
The custom scopes required by the [Arcade Salesforce MCP Server](/resources/integrations/sales/salesforce) are listed below, along with their descriptions:
The custom scopes listed below are only required if you are using the [Arcade Salesforce MCP Server](/resources/integrations/sales/salesforce).
If you're creating your own [custom Salesforce tools](/guides/create-tools/tool-basics/build-mcp-server) or using Arcade to authorize users and call Salesforce APIs directly, you are free to define custom scope(s) that fit best your application use cases. Observe that you must have at least one custom scope assigned to your Salesforce app in order to use the Salesforce API.
- `read_account`: Read access to account data.
- `read_contact`: Read access to contact data.
- `read_lead`: Read access to lead data.
- `read_note`: Read access to note data.
- `read_opportunity`: Read access to opportunity data.
- `read_task`: Read access to task data.
- `write_contact`: Write access to create contact.
Follow the [Create an OAuth Custom Scope](https://help.salesforce.com/s/articleView?id=xcloud.remoteaccess_oauth_customscopes_create.htm&type=5) and [Assign an OAuth Custom Scope to a Connected App](https://help.salesforce.com/s/articleView?id=xcloud.remoteaccess_oauth_customscopes_assign.htm&type=5) Salesforce documentation to understand how to define and assign these scopes to your Salesforce app.
The scope names aren't really attached to any endpoint or action. It's the
developer's job to honor the permissions communicated to the user when
authorizing the app. You could, in theory, assign one single scope (e.g.
`fullaccess`) and use it to query any Salesforce API endpoint.
## Configuring Salesforce Auth
### Configure Salesforce Auth Using the Arcade Dashboard GUI
#### Access the Arcade Dashboard
By default, the Arcade Dashboard will be available at http://localhost:9099/dashboard (if you're accessing it from the same machine where it's running). Change the host and port number to match your environment.
#### Navigate to the OAuth Providers page
- Under the **Connections** section of the Arcade Dashboard left-side menu, click **Connected Apps**.
- Click **Add OAuth Provider** in the top right corner.
- Select the **Custom Provider** tab at the top.
#### Enter the provider details
- Enter `salesforce` as the **ID** for your provider (the ID must be `salesforce` in order to use the [Arcade Salesforce MCP Server](/resources/integrations/sales/salesforce)).
- Optionally enter a **Description**.
- Enter your **Client ID** and **Client Secret** from your Salesforce app.
- Note the **Redirect URL** generated by Arcade. This must be set as your Salesforce app's callback URL.
#### Configure the auth endpoints
Replace `salesforce-org-subdomain` with your [Salesforce Org
Subdomain](#get-your-salesforce-org-subdomain).
- Enter the auth endpoints:
- **Authorization Endpoint**: `https://salesforce-org-subdomain.my.salesforce.com/services/oauth2/authorize`
- **Token Endpoint**: `https://salesforce-org-subdomain.my.salesforce.com/services/oauth2/token`
- Under **Refresh Token Settings**:
- Enter the **Refresh Token Endpoint**: `https://salesforce-org-subdomain.my.salesforce.com/services/oauth2/token`
- In **Response Content Type**, select `application/json`.
- Under **Token Introspection Settings**:
- Check the **Enable Token Introspection** option.
- Enter the **Token Introspection Endpoint**: `https://salesforce-org-subdomain.my.salesforce.com/services/oauth2/introspect`
- In **HTTP Method**, select `POST`
- In **Authentication Method**, select `Client Secret Basic`
- In **Request Content Type**, select `application/x-www-form-urlencoded`.
- Under **Request Parameters** section, add the following key-value pair:
- **Key**: `token`
- **Value**: `{{access_token}}`
- In **Response Content Type**, select `application/json`.
- In **Expiration Format**, select `Absolute Unix Timestamp`.
- Under the **Response Map** section:
- Set the **expires_in** field to `$.exp`
- Set the **scope** field to `$.scope`
- Leave the other fields as default
- Under **Triggers** section, enable the **On Token Grant** and **On Token Refresh** options.
#### Optional Auth Settings
- Under **PKCE Settings**, check the **Enable PKCE** option if you have enabled PKCE when creating your Salesforce app.
- Leave the **Authorization Settings** and **Token Settings** sections as default.
#### Create the provider
Click the **Create** button and the provider will be ready to be used in the Arcade Engine.
## Using the Arcade Salesforce MCP Server
The [Arcade Salesforce MCP Server](/resources/integrations/sales/salesforce) provides tools to interact with various Salesforce objects, such as accounts, contacts, leads, opportunities, notes, tasks, email messages, call logs, etc.
Refer to the [MCP Server documentation and examples](/resources/integrations/sales/salesforce) to learn how to use the MCP Server to build agents and AI apps that interact with Salesforce services.
Check our introductory documentation to understand what are tools and how
[tool calling works](/guides/tool-calling).
## Calling Salesforce APIs directly
Use the Salesforce auth provider to get a user authorization token and call Salesforce API endpoints directly, without the use of any tools. See [How Arcade helps with Agent Authorization](/get-started/about-arcade) to understand how this works.
### Prerequisites
1. Create an Arcade account
1. Get an [Arcade API key](/get-started/setup/api-keys).
1. Set the `ARCADE_API_KEY` environment variable with `export ARCADE_API_KEY=`.
1. Make sure to have Python 3.10+ or Node.js 18+ installed.
### Install the Arcade Python Client
```python
pip install arcadepy
```
### Import necessary modules and instantiate the client
Create a new script called `salesforce_example.py`. Import the necessary modules and instantiate the Arcade client:
The Arcade Engine service is available at `http://localhost:9099` by default.
Replace the host and port, if necessary, to match your environment.
```python
import requests
from arcadepy import Arcade
client = Arcade(base_url="http://localhost:9099") # Automatically finds the `ARCADE_API_KEY` env variable
```
### Set the values required for the Salesforce API call
```python
salesforce_provider_id = "salesforce"
salesforce_org_subdomain = "salesforce-org-subdomain"
user_id = "{arcade_user_id}"
scopes = ["read_account"]
```
Here's a break down of each value:
- **`salesforce_provider_id`**: the ID you entered when setting up the [Salesforce auth provider](#configuring-salesforce-auth);
- **`salesforce_org_subdomain`**: your [Salesforce Org Subdomain](#get-your-salesforce-org-subdomain);
- **`user_id`**: an internal identifier for your application user (it could be an email address, a username, UUID, etc); for demonstration purposes, in this example, enter your own email address;
- **`scopes`**: the list of scopes you want to request from the user; if you assigned the [custom scopes required by the Arcade Salesforce MCP Server](#create-and-assign-custom-scopes-to-your-connected-app) use `["read_account"]` in this example.
### Start the authorization process and wait for completion
The Arcade client will prompt the user to access a URL and authorize the app to access their Salesforce data. At the end of the auth process, you will have a token that can be used to call Salesforce APIs on behalf of that user.
```python
auth_response = client.auth.start(
user_id=user_id,
provider=salesforce_provider_id,
scopes=scopes,
)
if auth_response.status != "completed":
print("Please complete the authorization challenge in your browser:")
print(auth_response.url)
# Wait for the authorization to complete
auth_response = client.auth.wait_for_completion(auth_response)
token = auth_response.context.token
if not token:
raise ValueError("No token found in auth response")
```
If the same scopes have already been authorized by the user before and the
token is still valid, the auth process will be skipped and the token will be
returned immediately, without prompting with the authorization URL. The Arcade
Engine associates a previously authorized token with the `user_id` you
provided.
### Call the Salesforce API
We will now call the Salesforce `/parameterizedSearch` API endpoint to search and retrieve account data.
Replace the `q` value of `"acme"` with any keyword combination of your choice.
In a real-world scenario, this value would most likely come from a user's
input. Observe that the `q` argument must be a string with two or more
characters.
```python
response = requests.post(
f"https://{salesforce_org_subdomain}.my.salesforce.com/services/data/v63.0/parameterizedSearch",
headers={"Authorization": f"Bearer {token}"},
json={
"q": "acme",
"sobjects": [
{"name": "Account", "fields": ["Id", "Name", "Website", "Phone"]},
],
"in": "ALL",
"overallLimit": 10,
"offset": 0,
},
)
if not response.ok:
raise ValueError(
f"Failed to retrieve Salesforce data: {response.status_code} - {response.text}"
)
```
```python
import requests
from arcadepy import Arcade
client = Arcade(base_url="http://localhost:9099") # Automatically finds the `ARCADE_API_KEY` env variable
salesforce_provider_id = "salesforce"
salesforce_org_subdomain = "salesforce-org-subdomain"
user_id = "{arcade_user_id}"
scopes = ["read_account"]
# Start the authorization process
auth_response = client.auth.start(
user_id=user_id,
provider=salesforce_provider_id,
scopes=scopes,
)
if auth_response.status != "completed":
print("Please complete the authorization challenge in your browser:")
print(auth_response.url)
# Wait for the authorization to complete
auth_response = client.auth.wait_for_completion(auth_response)
token = auth_response.context.token
if not token:
raise ValueError("No token found in auth response")
# Use the Salesforce API
response = requests.post(
f"https://{salesforce_org_subdomain}.my.salesforce.com/services/data/v63.0/parameterizedSearch",
headers={"Authorization": f"Bearer {token}"},
json={
"q": "acme",
"sobjects": [
{"name": "Account", "fields": ["Id", "Name", "Website", "Phone"]},
],
"in": "ALL",
"overallLimit": 10,
"offset": 0,
},
)
if not response.ok:
raise ValueError(
f"Failed to retrieve Salesforce data: {response.status_code} - {response.text}"
)
print(response.json())
````
### Install the Arcade JavaScript Client
```javascript
npm install @arcadeai/arcadejs
````
### Import necessary modules and instantiate the client
Create a new script called `salesforce_example.js`. Import the necessary modules and instantiate the Arcade client:
Replace `http://localhost:9099` with the URL of your Arcade Engine.
```javascript
import { Arcade } from "@arcadeai/arcadejs";
const client = new Arcade((baseURL = "http://localhost:9099")); // Automatically finds the `ARCADE_API_KEY` env variable
```
### Set the values required for the Salesforce API call
```javascript
const salesforceProviderId = "salesforce";
const salesforceOrgSubdomain = "salesforce-org-subdomain";
const userId = "{arcade_user_id}";
const scopes = ["read_account"];
```
Here's a break down of each value:
- **`salesforceProviderId`**: the ID you entered when setting up the [Salesforce auth provider](#configuring-salesforce-auth);
- **`salesforceOrgSubdomain`**: your [Salesforce Org Subdomain](#get-your-salesforce-org-subdomain);
- **`userId`**: an internal identifier for your application user (it could be an email address, a username, UUID, etc); for demonstration purposes, in this example, enter your own email address;
- **`scopes`**: the list of scopes you want to request from the user; if you assigned the [custom scopes required by the Arcade Salesforce MCP Server](#create-and-assign-custom-scopes-to-your-connected-app) use `["read_account"]` in this example.
### Start the authorization process and wait for completion
The Arcade client will prompt the user to access a URL and authorize the app to access their Salesforce data. At the end of the auth process, you will have a token that can be used to call Salesforce APIs on behalf of that user.
```javascript
let authResponse = await client.auth.start(userId, {
provider: salesforceProviderId,
scopes: scopes,
});
if (authResponse.status !== "completed") {
console.log("Please complete the authorization challenge in your browser:");
console.log(authResponse.url);
}
// Wait for the authorization to complete
authResponse = await client.auth.waitForCompletion(authResponse);
if (!authResponse.context.token) {
throw new Error("No token found in auth response");
}
const token = authResponse.context.token;
if (!token) {
throw new Error("No token found in auth response");
```
If the same scopes have already been authorized by the user before and the
token is still valid, the auth process will be skipped and the token will be
returned immediately, without prompting with the authorization URL. The Arcade
Engine associates a previously authorized token with the `user_id` you
provide.
### Call the Salesforce API
We will now call the Salesforce `/parameterizedSearch` API endpoint to search and retrieve account data.
Replace the `q` value of `"acme"` with any keyword combination of your choice.
In a real-world scenario, this value would most likely come from a user's
input. Observe that the `q` argument must be a string with two or more
characters.
```javascript
// Use the Salesforce API
const response = await fetch(
`https://${salesforceOrgSubdomain}.my.salesforce.com/services/data/v63.0/parameterizedSearch`,
{
headers: {
Authorization: `Bearer ${token}`,
},
method: "POST",
body: JSON.stringify({
q: "acme",
sobjects: [
{ name: "Account", fields: ["Id", "Name", "Website", "Phone"] },
],
in: "ALL",
overallLimit: 10,
offset: 0,
}),
},
);
if (!response.ok) {
throw new Error(
`HTTP error! status: ${response.status} - ${await response.text()}`,
);
```
```javascript
import { Arcade } from "@arcadeai/arcadejs";
const client = new Arcade((baseURL = "http://localhost:9099")); // Automatically finds the `ARCADE_API_KEY` env variable
const salesforceProviderId = "salesforce";
const salesforceOrgSubdomain = "salesforce-org-subdomain";
const userId = "{arcade_user_id}";
const scopes = ["read_account"];
// Start the authorization process
let authResponse = await client.auth.start(userId, {
provider: salesforceProviderId,
scopes: scopes,
});
if (authResponse.status !== "completed") {
console.log("Please complete the authorization challenge in your browser:");
console.log(authResponse.url);
}
// Wait for the authorization to complete
authResponse = await client.auth.waitForCompletion(authResponse);
if (!authResponse.context.token) {
throw new Error("No token found in auth response");
}
const token = authResponse.context.token;
if (!token) {
throw new Error("No token found in auth response");
}
// Use the Salesforce API
const response = await fetch(
`https://${salesforceOrgSubdomain}.my.salesforce.com/services/data/v63.0/parameterizedSearch`,
{
headers: {
Authorization: `Bearer ${token}`,
},
method: "POST",
body: JSON.stringify({
q: "acme",
sobjects: [
{ name: "Account", fields: ["Id", "Name", "Website", "Phone"] },
],
in: "ALL",
overallLimit: 10,
offset: 0,
}),
},
);
if (!response.ok) {
throw new Error(
`HTTP error! status: ${response.status} - ${await response.text()}`,
);
}
console.log(await response.json());
```
## Create your own Salesforce Tools
If the pre-built tools in the [Arcade Salesforce MCP Server](/resources/integrations/sales/salesforce) don't meet your needs, you can create your own [custom tools](/guides/create-tools/tool-basics/build-mcp-server) that interact with the Salesforce APIs.
The code implemented in the Arcade Salesforce tools is the best guide for you to understand how to implement your own. Check the [Contact](https://github.com/ArcadeAI/arcade-ai/blob/main/resources/integrations/salesforce/arcade_salesforce/tools/crm/contact.py) and [Account](https://github.com/ArcadeAI/arcade-ai/blob/main/resources/integrations/salesforce/arcade_salesforce/tools/crm/account.py) tools in our public Github repository.
```