# Agentforce — Full Documentation

> This file contains all Agentforce documentation in a single file for LLM consumption.
> For a curated navigation file, see /llms.txt instead.

## Getting started

1. Ask what the user wants to build or improve. Reuse their existing Salesforce project and intended org where possible.

2. Check `sf --version` and look for `sfdx-project.json`. If needed, follow the [Salesforce CLI installation guide](https://developer.salesforce.com/tools/salesforcecli) and create a project with `sf project generate --name <project-name>`.

3. Check `sf org list --json` privately and confirm the target org. To connect an existing org, use `sf org login web --alias <alias>`; for a Labs org, follow the optional flow below. Use `--target-org <alias>` on subsequent commands and preserve the user's default org.

4. Set up the skills:

Use agentforce-generate for building and agentforce-test for testing. Check installed skills first.

For Claude Code, use `claude plugin install agentforce-adlc@claude-plugins-official`. For portable skills, use `npx skills add forcedotcom/sf-skills` and select these skills for your coding tool. The standalone ADLC package provides other installation options: https://github.com/SalesforceAIResearch/agentforce-adlc.

Ask for approval before changing the global environment. Verify the running coding agent can discover and load agentforce-generate and agentforce-test before continuing.

5. Follow agentforce-generate to build with Agent Script (.agent files). For an existing agent, confirm its developer name, retrieve its AiAuthoringBundle, and inspect it before editing. Reuse existing actions where appropriate.

6. Follow agentforce-test to validate the change. Start with local checks; obtain authorization before tests that deploy metadata or run live actions. Report what changed, what passed, and any remaining work.

Keep credentials, device codes, and secret responses out of chat, logs, source files, and commits. Deploy, publish, or activate only when requested, against the confirmed org. Connecting an org does not authorize changes to it.

## Optional: connect through Labs

Use this only when the user wants a Labs connection. Labs chooses their active LabBox, otherwise their connected own org, otherwise provisions a LabBox after approval. This flow cannot select an arbitrary org.

### 1. Request approval

POST https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/api/cli/device/code with Content-Type: application/json:

```json
{"client_name":"Your coding agent"}
```

Show the returned verification_uri_complete to the user for sign-in and approval. If absent, show verification_uri and user_code. Keep device_code private; use the returned interval and expires_in for polling and its deadline.

### 2. Poll

POST https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/api/cli/device/token with Content-Type: application/json:

```json
{"device_code":"<returned device_code>"}
```

Run one poller and stop at the original expiry deadline.

| Response | Action |
| --- | --- |
| 400 authorization_pending | Wait the interval and retry, including while provisioning. |
| 400 slow_down | Add at least five seconds to the delay; retain the increased delay. |
| 429 | Honor Retry-After and the current polling delay. |
| Transient 502/503 | Back off; honor Retry-After when present. |
| access_denied, expired_token, byoo_unavailable, or another permanent error | Stop and explain the failure. |
| 200, status ready | Stop polling and consume the credentials once. The grant cannot be replayed. |

### 3. Authenticate

Read the response privately, without logging or shell tracing. Choose an alias with the user:

- If sf_cli.sfdx_auth_url exists, pipe it through stdin to `sf org login sfdx-url --sfdx-url-stdin --alias <alias>`.

- Otherwise, store sf_cli.instance_url privately in INSTANCE_URL, pass sf_cli.access_token through the login process's SF_ACCESS_TOKEN environment variable and run `sf org login access-token --instance-url "$INSTANCE_URL" --alias <alias> --no-prompt`.

Verify the resulting org privately with `sf data query --query "SELECT Id, Name, IsSandbox FROM Organization" --target-org <alias> --json` before making changes. Report org.expires_at when provided as the org's availability deadline. Return to the build-and-test steps above.

---

# GET STARTED

## Build

# Build

Choose where to build your agent.

## Salesforce Tools

Build and test agents directly in the browser with no local environment.

### [Agentforce Builder](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/docs/agentforce-builder>)

Visual, browser-based builder. Define topics, actions, and instructions with no local setup required.

### [Agentforce Vibes](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/docs/agentforce-vibes>)

AI-assisted Agent Script authoring inside Code Builder, directly connected to your org.

## CLI Agents

Terminal-based AI agents that write, edit, and deploy code from natural language.

### [Claude Code](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/docs/claude-code>)

Anthropic's CLI agent. Install Salesforce skills and build agents from natural language.

### [Codex](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/docs/codex>)

OpenAI's coding agent, available in desktop, CLI, and IDE workflows. This guide covers CLI setup.

### [Gemini CLI](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/docs/gemini-cli>)

Google's terminal-based AI agent for code generation, debugging, and reviews.

## Editors

### [Cursor](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/docs/cursor>)

AI-first editor built on VS Code with inline completions and chat.

### [Windsurf](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/docs/windsurf>)

AI editor with the Cascade coding assistant.

### [Antigravity](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/docs/antigravity>)

Google's AI-native IDE. Download it for macOS, Windows, or Linux.

[Compare Salesforce skills, plugins, and the ChatGPT app](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/docs/skills>).

## Agentforce Builder

# Agentforce Builder

The visual way to build agents. Describe what your agent should do, configure subagents and actions, test in preview, and deploy, all from a browser. No local setup required.
*(Open Agentforce Builder — available on the live page)*
![Agentforce Builder](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/images/products/agent-builder.png>)

## How it works

1. **Click the button above**: signs you in and drops you into a provisioned org
2. **Start building**: define subagents, add actions, write instructions, test in preview

Your org comes with Agentforce, Data Cloud, and all permissions pre-configured.

## What you can build

- **Subagents**: conversation flows your agent handles (order lookup, returns, scheduling)
- **Actions**: tools your agent calls (Apex, Flow, APIs)
- **Instructions**: reasoning logic that guides your agent's behavior
- **Guardrails**: safety rules and escalation paths

## Deploy

Once your agent works in preview, deploy to:

- **Experience Sites**: hosted Salesforce page with your agent embedded
- **Enhanced Chat**: embed on your own website
- **API**: call your agent programmatically from any client

## Want more control?

For code-first development with Agent Script, try [Claude Code](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/docs/claude-code>). Both paths produce the same thing: an Agentforce agent. Choose the one that fits how you work.

## Agentforce Vibes

# Agentforce Vibes

AI-assisted agent development inside Salesforce Code Builder. Write Agent Script with AI help, directly connected to your org, no local setup, no context switching.
*(Open Agentforce Vibes — available on the live page)*
![Agentforce Vibes](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/images/products/agentforce-vibes.png>)

## What makes Vibes different

- **Runs in the browser**: Code Builder inside your Salesforce org, nothing to install
- **AI-assisted**: describe what you want, get Agent Script generated in context
- **Org-connected**: direct access to your objects, permissions, and metadata as you build
- **Fast iteration**: edit, save, test without leaving the browser

## When to use Vibes

Vibes sits between Builder and a full IDE. Use it when you want to write Agent Script directly but don't need a local development environment:

- Exploring Agent Script syntax hands-on
- Quick iterations on agent logic
- Working against your org's data model in real time

## Other paths

- [Agentforce Builder](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/docs/agentforce-builder>): visual, no-code agent configuration
- [Claude Code](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/docs/claude-code>): full AI-assisted development with sf-skills and eval loops

## Setting Up Claude Code for Agentforce

# Getting Started with Claude Code

You need an org with Agentforce enabled. [Get a LabBox](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/docs/org-configuration>) if you need one.

## 1. Install Claude Code

#### curl

```bash
curl -fsSL https://claude.ai/install.sh | bash
```

#### Homebrew

```bash
brew install --cask claude-code
```

#### npm

```bash
npm install -g @anthropic-ai/claude-code
```

[Other platforms and installation options](https://code.claude.com/docs/en/setup).

## 2. Connect Salesforce

Install Salesforce CLI if needed:

#### Homebrew

```bash
brew install sf
```

#### npm

```bash
npm install -g @salesforce/cli
```

Requires Node.js 22+. See the [CLI installation guide](https://developer.salesforce.com/docs/platform/sfdx-setup/guide/sfdx-setup-install-cli.html).

✅ **Verify:**

```bash
sf --version
```

The output should start with `@salesforce/cli/`.

If the org is already authorized, skip sign-in and use its existing alias.

#### LabBox

Run the generated command:
**Authenticate SF CLI**: Log in on this page to get your pre-filled authentication command.

```bash
echo "<SFDX_AUTH_URL>" | sf org login sfdx-url --sfdx-url-stdin --set-default --alias afdx-lab
```
This sets `afdx-lab` as your default org. Remove `--set-default` to keep your current default.

✅ **Verify:**

```bash
sf data query --query "SELECT Id, Name, IsSandbox FROM Organization" --target-org afdx-lab --json
```

Confirm that the output shows your LabBox.

#### Your org

```bash
sf org login web --alias my-org
```

✅ **Verify:**

```bash
sf data query --query "SELECT Id, Name, IsSandbox FROM Organization" --target-org my-org --json
```

Confirm that the output shows your org.

Use your chosen alias with `--target-org` in later commands. If you're signed in to Labs, select that org before copying the build prompt.

## 3. Install the Agentforce plugin

In your project directory, run `claude` and follow the sign-in prompts. Then install the plugin inside Claude Code:

```text
/plugin install agentforce-adlc@claude-plugins-official
```

Requires Python 3.9+. Run `/plugin` to confirm the plugin is installed and enabled; restart Claude Code if its skills are missing.

For Apex, Flow, and other platform work, you can also install Salesforce Development. See [Skills and plugins](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/docs/skills>) for commands and prerequisites.

## 4. Build an agent

Paste this prompt into Claude Code.
```text
Check setup:

1. Check SF CLI: `sf --version`
   If missing, follow https://developer.salesforce.com/docs/platform/sfdx-setup/guide/sfdx-setup-install-cli.html for your OS.
2. Check that the agentforce-adlc plugin is installed and enabled with `claude plugin list`, or that the agentforce-generate, agentforce-test, and agentforce-observe skills are already available through a file-copy installation.
   If neither is available, install the plugin: `claude plugin install agentforce-adlc@claude-plugins-official`, then restart Claude Code.
   Plugin skills use the agentforce-adlc: prefix; file-copy skills use the same names without that prefix.
   Obtain authorization before changing the global environment.
   Verify the running coding agent can discover and load agentforce-generate and agentforce-test before continuing.
3. Select the intended org alias or username from the setup page or user request. Replace <org-alias> below with that value; if the intended org is unclear, ask before continuing.
   Verify the org privately: `sf data query --query "SELECT Id, Name, IsSandbox FROM Organization" --target-org <org-alias> --json`
   Confirm the returned org name and Id match the intended target. If authentication fails, reconnect this org with its Labs credential command or `sf org login web --alias <org-alias>`, then verify it again.
   Pass `--target-org <org-alias>` on every subsequent org command; do not rely on or change the default org.

Ask what the agent must do, which data and actions it can use, and how to test it. Wait for my answer before building.

Use agentforce-generate to implement the agent and its actions. Show me the changes before publishing. After publishing, run agentforce-test and report passing tests, failures, and remaining limitations.

Keep credentials out of chat, logs, and source files. Deploy only when the user's request explicitly asks; obtain authorization before tests that deploy metadata or run live actions.
```

## Agent API - Headless Communication

# Agent API - Headless Communication

Interact with your deployed Agentforce agents programmatically using REST APIs. Perfect for building custom integrations, testing workflows, or automating agent interactions.

> **Interactive Explorer (on agentforce.dev)**:
> The live page has an interactive Agent API explorer that lets you create
> sessions and send messages to your agent. If you are not using the live page,
> follow the curl/REST API examples below.

## API Usage Guide

Follow these steps to interact with your agent programmatically:

### Step 0: Get OAuth Access Token

First, get an OAuth access token using your credentials. Copy the `access_token` from the response.

```bash
curl -X POST "{{INSTANCE_URL}}/services/oauth2/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials" \
  -d "client_id={{CLIENT_ID}}" \
  -d "client_secret={{CLIENT_SECRET}}"
```

**Response example:**

```json
{
  "access_token": "00D5e0000...",
  "instance_url": "https://your-org.my.salesforce.com",
  "id": "https://login.salesforce.com/id/...",
  "token_type": "Bearer",
  "issued_at": "1234567890",
  "signature": "..."
}
```

### Step 1: Start Agent Session

Start a new agent session. Save the `sessionId` from the response.

```bash
curl -X POST "{{AGENT_API_BASE_URL}}/agents/{{AGENT_ID}}/sessions" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" \
  -H "Content-Type: application/json" \
  -d '{
    "externalSessionKey": "session_1234567890",
    "instanceConfig": {
      "endpoint": "{{INSTANCE_URL}}"
    },
    "tz": "America/Los_Angeles",
    "variables": [{
      "name": "$Context.EndUserLanguage",
      "type": "Text",
      "value": "en_US"
    }],
    "bypassUser": true
  }'
```

**Key parameters:**

- `externalSessionKey`: A unique identifier for this session (use timestamps or UUIDs)
- `instanceConfig.endpoint`: Your Salesforce instance URL
- `tz`: Timezone for the session
- `variables`: Context variables for the agent
- `bypassUser`: Set to `true` for headless API access

**Response example:**

```json
{
  "sessionId": "0Xx5e0000...",
  "createdDate": "2024-01-01T00:00:00.000Z",
  "status": "ACTIVE"
}
```

### Step 2: Send Message to Agent

Send a message to the agent. Increment `sequenceId` for each new message (1, 2, 3, ...).

```bash
curl -X POST "{{AGENT_API_BASE_URL}}/sessions/YOUR_SESSION_ID/messages" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" \
  -H "Content-Type: application/json" \
  -d '{
    "message": {
      "sequenceId": 1,
      "type": "Text",
      "text": "Hello! Can you help me?"
    }
  }'
```

**Important:** Track the `sequenceId` yourself - it must increment with each message in the session.

**Response example:**

```json
{
  "messages": [
    {
      "sequenceId": 1,
      "type": "Text",
      "text": "Hello! I'm here to help. What can I assist you with today?",
      "role": "Agent"
    }
  ]
}
```

### Step 3: End Agent Session

Close the session when done to free up resources.

```bash
curl -X DELETE "{{AGENT_API_BASE_URL}}/sessions/YOUR_SESSION_ID" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" \
  -H "x-session-end-reason: UserRequest"
```

**Response:** Returns HTTP 200 with a `SessionEnded` message.

**Note:** The `x-session-end-reason` header is required. Valid values: `UserRequest`, `Transfer`.

## Streaming Responses

For real-time streaming responses, add streaming capabilities to your session:

```json
{
  "featureSupport": "Streaming",
  "streamingCapabilities": {
    "chunkTypes": ["Text"]
  }
}
```

When streaming is enabled, responses will be sent as Server-Sent Events (SSE).

## Error Handling

Common error responses:

| Status Code | Description           | Solution                                     |
| ----------- | --------------------- | -------------------------------------------- |
| 401         | Unauthorized          | OAuth token expired - get a new one          |
| 404         | Not Found             | Check agent ID or session ID                 |
| 429         | Too Many Requests     | Rate limit exceeded - slow down requests     |
| 500         | Internal Server Error | Salesforce server issue - retry with backoff |

## Rate Limits

- **Sessions per hour**: 1000 per org
- **Messages per session**: 100
- **Concurrent sessions**: 50 per org

## SDK Examples

**Choose one of the following methods:**

#### JavaScript/Node.js

```javascript
const axios = require("axios");

const client = new AgentAPIClient(
  "{{INSTANCE_URL}}",
  "{{CLIENT_ID}}",
  "{{CLIENT_SECRET}}"
);

class AgentAPIClient {
  constructor(instanceUrl, clientId, clientSecret) {
    this.instanceUrl = instanceUrl;
    this.clientId = clientId;
    this.clientSecret = clientSecret;
    this.baseUrl = "https://api.salesforce.com/einstein/ai-agent/v1";
  }

  async getAccessToken() {
    const response = await axios.post(
      `${this.instanceUrl}/services/oauth2/token`,
      new URLSearchParams({
        grant_type: "client_credentials",
        client_id: this.clientId,
        client_secret: this.clientSecret,
      }),
      { headers: { "Content-Type": "application/x-www-form-urlencoded" } }
    );
    return response.data.access_token;
  }

  async startSession(agentId, accessToken) {
    const response = await axios.post(
      `${this.baseUrl}/agents/${agentId}/sessions`,
      {
        externalSessionKey: `session_${Date.now()}`,
        instanceConfig: { endpoint: this.instanceUrl },
        tz: "America/Los_Angeles",
        bypassUser: true,
      },
      { headers: { Authorization: `Bearer ${accessToken}` } }
    );
    return response.data.sessionId;
  }

  async sendMessage(sessionId, text, sequenceId, accessToken) {
    const response = await axios.post(
      `${this.baseUrl}/sessions/${sessionId}/messages`,
      {
        message: {
          sequenceId,
          type: "Text",
          text,
        },
      },
      { headers: { Authorization: `Bearer ${accessToken}` } }
    );
    return response.data;
  }

  async endSession(sessionId, accessToken) {
    await axios.delete(`${this.baseUrl}/sessions/${sessionId}`, {
      headers: {
        Authorization: `Bearer ${accessToken}`,
        "x-session-end-reason": "UserRequest",
      },
    });
  }
}

// Usage
(async () => {
  const token = await client.getAccessToken();
  const sessionId = await client.startSession("{{AGENT_ID}}", token);
  const response = await client.sendMessage(sessionId, "Hello!", 1, token);
  console.log(response);
  await client.endSession(sessionId, token);
})();
```

#### Python

```python
import requests
import time

client = AgentAPIClient(
    "{{INSTANCE_URL}}",
    "{{CLIENT_ID}}",
    "{{CLIENT_SECRET}}"
)

class AgentAPIClient:
    def __init__(self, instance_url, client_id, client_secret):
        self.instance_url = instance_url
        self.client_id = client_id
        self.client_secret = client_secret
        self.base_url = 'https://api.salesforce.com/einstein/ai-agent/v1'

    def get_access_token(self):
        response = requests.post(
            f"{self.instance_url}/services/oauth2/token",
            data={
                'grant_type': 'client_credentials',
                'client_id': self.client_id,
                'client_secret': self.client_secret
            }
        )
        return response.json()['access_token']

    def start_session(self, agent_id, access_token):
        response = requests.post(
            f"{self.base_url}/agents/{agent_id}/sessions",
            json={
                'externalSessionKey': f"session_{int(time.time())}",
                'instanceConfig': {'endpoint': self.instance_url},
                'tz': 'America/Los_Angeles',
                'bypassUser': True
            },
            headers={'Authorization': f'Bearer {access_token}'}
        )
        return response.json()['sessionId']

    def send_message(self, session_id, text, sequence_id, access_token):
        response = requests.post(
            f"{self.base_url}/sessions/{session_id}/messages",
            json={
                'message': {
                    'sequenceId': sequence_id,
                    'type': 'Text',
                    'text': text
                }
            },
            headers={'Authorization': f'Bearer {access_token}'}
        )
        return response.json()

    def end_session(self, session_id, access_token):
        requests.delete(
            f"{self.base_url}/sessions/{session_id}",
            headers={
                'Authorization': f'Bearer {access_token}',
                'x-session-end-reason': 'UserRequest'
            }
        )

# Usage
token = client.get_access_token()
session_id = client.start_session("{{AGENT_ID}}", token)
response = client.send_message(session_id, "Hello!", 1, token)
print(response)
client.end_session(session_id, token)
```

## Tips

- **Token expiration**: OAuth tokens expire after ~2 hours - implement token refresh logic
- **Session management**: Always end sessions when done to avoid hitting Salesforce limits
- **Sequence tracking**: Maintain sequence IDs on your side - they must increment sequentially
- **Error handling**: Implement retry logic with exponential backoff for transient errors
- **Rate limiting**: Respect rate limits and implement proper throttling

## Additional Resources

- [Official Salesforce Agent API Documentation](https://developer.salesforce.com/docs/einstein/genai/guide/agent-api.html)
- [OAuth 2.0 Client Credentials Flow](https://help.salesforce.com/s/articleView?id=sf.remoteaccess_oauth_client_credentials_flow.htm)
- [Einstein Platform Rate Limits](https://developer.salesforce.com/docs/einstein/genai/guide/rate-limits.html)

## Setting Up Codex for Agentforce

# Getting Started with Codex

You need an org with Agentforce enabled. [Get a LabBox](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/docs/org-configuration>) if you need one.

## 1. Install Codex CLI

#### npm

```bash
npm i -g @openai/codex
```

#### Homebrew

```bash
brew install codex
```

```bash
codex --version
```

## 2. Connect Salesforce

Install Salesforce CLI if needed:

#### Homebrew

```bash
brew install sf
```

#### npm

```bash
npm install -g @salesforce/cli
```

Requires Node.js 22+. See the [CLI installation guide](https://developer.salesforce.com/docs/platform/sfdx-setup/guide/sfdx-setup-install-cli.html).

✅ **Verify:**

```bash
sf --version
```

The output should start with `@salesforce/cli/`.

If the org is already authorized, skip sign-in and use its existing alias.

#### LabBox

Run the generated command:
**Authenticate SF CLI**: Log in on this page to get your pre-filled authentication command.

```bash
echo "<SFDX_AUTH_URL>" | sf org login sfdx-url --sfdx-url-stdin --set-default --alias afdx-lab
```
This sets `afdx-lab` as your default org. Remove `--set-default` to keep your current default.

✅ **Verify:**

```bash
sf data query --query "SELECT Id, Name, IsSandbox FROM Organization" --target-org afdx-lab --json
```

Confirm that the output shows your LabBox.

#### Your org

```bash
sf org login web --alias my-org
```

✅ **Verify:**

```bash
sf data query --query "SELECT Id, Name, IsSandbox FROM Organization" --target-org my-org --json
```

Confirm that the output shows your org.

Use your chosen alias with `--target-org` in later commands. Include your chosen org alias when you paste the build prompt.

## 3. Install Agentforce skills

Run this in your project directory:

```bash
npx skills add forcedotcom/sf-skills
```

Select your coding tool and the `agentforce-generate`, `agentforce-test`, and `agentforce-observe` skills. Skip this step if they are already available. Restart your coding tool if it does not discover them.

[Other installation options](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/docs/skills>).

## 4. Build an agent

In your project directory, run `codex` and follow the sign-in prompts. Then paste this prompt.
```text
Check setup:

1. Check SF CLI: `sf --version`
   If missing, follow https://developer.salesforce.com/docs/platform/sfdx-setup/guide/sfdx-setup-install-cli.html for your OS.
2. Check that agentforce-generate, agentforce-test, and agentforce-observe are available in this coding agent.
   If missing, install them: `npx skills add forcedotcom/sf-skills`, selecting this coding agent as the target. Restart the session if the skills are not discovered.
   Obtain authorization before changing the global environment.
   Verify the running coding agent can discover and load agentforce-generate and agentforce-test before continuing.
3. Select the intended org alias or username from the setup page or user request. Replace <org-alias> below with that value; if the intended org is unclear, ask before continuing.
   Verify the org privately: `sf data query --query "SELECT Id, Name, IsSandbox FROM Organization" --target-org <org-alias> --json`
   Confirm the returned org name and Id match the intended target. If authentication fails, reconnect this org with its Labs credential command or `sf org login web --alias <org-alias>`, then verify it again.
   Pass `--target-org <org-alias>` on every subsequent org command; do not rely on or change the default org.

Ask what the agent must do, which data and actions it can use, and how to test it. Wait for my answer before building.

Use agentforce-generate to implement the agent and its actions. Show me the changes before publishing. After publishing, run agentforce-test and report passing tests, failures, and remaining limitations.

Keep credentials out of chat, logs, and source files. Deploy only when the user's request explicitly asks; obtain authorization before tests that deploy metadata or run live actions.
```

## Setting Up Gemini CLI for Agentforce

# Getting Started with Gemini CLI

You need an org with Agentforce enabled. [Get a LabBox](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/docs/org-configuration>) if you need one.

## 1. Install Gemini CLI

#### npx (no install)

```bash
npx @google/gemini-cli
```

#### npm

```bash
npm install -g @google/gemini-cli
```

#### Homebrew

```bash
brew install gemini-cli
```

### Sign in

#### Google account

Start `gemini` (or `npx @google/gemini-cli`) and follow the browser sign-in prompt. [Account limits](https://geminicli.com/docs/resources/quota-and-pricing/).

#### API Key

Get a key from [Google AI Studio](https://aistudio.google.com/apikey), then:

```bash
export GEMINI_API_KEY="YOUR_KEY"
```

## 2. Connect Salesforce

Install Salesforce CLI if needed:

#### Homebrew

```bash
brew install sf
```

#### npm

```bash
npm install -g @salesforce/cli
```

Requires Node.js 22+. See the [CLI installation guide](https://developer.salesforce.com/docs/platform/sfdx-setup/guide/sfdx-setup-install-cli.html).

✅ **Verify:**

```bash
sf --version
```

The output should start with `@salesforce/cli/`.

If the org is already authorized, skip sign-in and use its existing alias.

#### LabBox

Run the generated command:
**Authenticate SF CLI**: Log in on this page to get your pre-filled authentication command.

```bash
echo "<SFDX_AUTH_URL>" | sf org login sfdx-url --sfdx-url-stdin --set-default --alias afdx-lab
```
This sets `afdx-lab` as your default org. Remove `--set-default` to keep your current default.

✅ **Verify:**

```bash
sf data query --query "SELECT Id, Name, IsSandbox FROM Organization" --target-org afdx-lab --json
```

Confirm that the output shows your LabBox.

#### Your org

```bash
sf org login web --alias my-org
```

✅ **Verify:**

```bash
sf data query --query "SELECT Id, Name, IsSandbox FROM Organization" --target-org my-org --json
```

Confirm that the output shows your org.

Use your chosen alias with `--target-org` in later commands. Include your chosen org alias when you paste the build prompt.

## 3. Install Agentforce skills

Run this in your project directory:

```bash
npx skills add forcedotcom/sf-skills
```

Select your coding tool and the `agentforce-generate`, `agentforce-test`, and `agentforce-observe` skills. Skip this step if they are already available. Restart your coding tool if it does not discover them.

[Other installation options](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/docs/skills>).

## 4. Build an agent

Start Gemini CLI in your project directory, then paste this prompt.
```text
Check setup:

1. Check SF CLI: `sf --version`
   If missing, follow https://developer.salesforce.com/docs/platform/sfdx-setup/guide/sfdx-setup-install-cli.html for your OS.
2. Check that agentforce-generate, agentforce-test, and agentforce-observe are available in this coding agent.
   If missing, install them: `npx skills add forcedotcom/sf-skills`, selecting this coding agent as the target. Restart the session if the skills are not discovered.
   Obtain authorization before changing the global environment.
   Verify the running coding agent can discover and load agentforce-generate and agentforce-test before continuing.
3. Select the intended org alias or username from the setup page or user request. Replace <org-alias> below with that value; if the intended org is unclear, ask before continuing.
   Verify the org privately: `sf data query --query "SELECT Id, Name, IsSandbox FROM Organization" --target-org <org-alias> --json`
   Confirm the returned org name and Id match the intended target. If authentication fails, reconnect this org with its Labs credential command or `sf org login web --alias <org-alias>`, then verify it again.
   Pass `--target-org <org-alias>` on every subsequent org command; do not rely on or change the default org.

Ask what the agent must do, which data and actions it can use, and how to test it. Wait for my answer before building.

Use agentforce-generate to implement the agent and its actions. Show me the changes before publishing. After publishing, run agentforce-test and report passing tests, failures, and remaining limitations.

Keep credentials out of chat, logs, and source files. Deploy only when the user's request explicitly asks; obtain authorization before tests that deploy metadata or run live actions.
```

## Setting Up Cursor for Agentforce

# Getting Started with Cursor

You need an org with Agentforce enabled. [Get a LabBox](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/docs/org-configuration>) if you need one.

## 1. Install Cursor

#### Homebrew (macOS)

```bash
brew install --cask cursor
```

#### Download

Download from [cursor.com](https://www.cursor.com) and install.

Open Cursor and sign in to your account.

## 2. Connect Salesforce

Install Salesforce CLI if needed:

#### Homebrew

```bash
brew install sf
```

#### npm

```bash
npm install -g @salesforce/cli
```

Requires Node.js 22+. See the [CLI installation guide](https://developer.salesforce.com/docs/platform/sfdx-setup/guide/sfdx-setup-install-cli.html).

✅ **Verify:**

```bash
sf --version
```

The output should start with `@salesforce/cli/`.

If the org is already authorized, skip sign-in and use its existing alias.

#### LabBox

Run the generated command:
**Authenticate SF CLI**: Log in on this page to get your pre-filled authentication command.

```bash
echo "<SFDX_AUTH_URL>" | sf org login sfdx-url --sfdx-url-stdin --set-default --alias afdx-lab
```
This sets `afdx-lab` as your default org. Remove `--set-default` to keep your current default.

✅ **Verify:**

```bash
sf data query --query "SELECT Id, Name, IsSandbox FROM Organization" --target-org afdx-lab --json
```

Confirm that the output shows your LabBox.

#### Your org

```bash
sf org login web --alias my-org
```

✅ **Verify:**

```bash
sf data query --query "SELECT Id, Name, IsSandbox FROM Organization" --target-org my-org --json
```

Confirm that the output shows your org.

Use your chosen alias with `--target-org` in later commands. Include your chosen org alias when you paste the build prompt.

## 3. Install Agentforce skills

Open your project folder in Cursor, then run this in its terminal:

```bash
npx skills add forcedotcom/sf-skills
```

Select your coding tool and the `agentforce-generate`, `agentforce-test`, and `agentforce-observe` skills. Skip this step if they are already available. Restart your coding tool if it does not discover them.

[Other installation options](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/docs/skills>).

## 4. Build an agent

Paste this prompt into Cursor's chat.
```text
Check setup:

1. Check SF CLI: `sf --version`
   If missing, follow https://developer.salesforce.com/docs/platform/sfdx-setup/guide/sfdx-setup-install-cli.html for your OS.
2. Check that agentforce-generate, agentforce-test, and agentforce-observe are available in this coding agent.
   If missing, install them: `npx skills add forcedotcom/sf-skills`, selecting this coding agent as the target. Restart the session if the skills are not discovered.
   Obtain authorization before changing the global environment.
   Verify the running coding agent can discover and load agentforce-generate and agentforce-test before continuing.
3. Select the intended org alias or username from the setup page or user request. Replace <org-alias> below with that value; if the intended org is unclear, ask before continuing.
   Verify the org privately: `sf data query --query "SELECT Id, Name, IsSandbox FROM Organization" --target-org <org-alias> --json`
   Confirm the returned org name and Id match the intended target. If authentication fails, reconnect this org with its Labs credential command or `sf org login web --alias <org-alias>`, then verify it again.
   Pass `--target-org <org-alias>` on every subsequent org command; do not rely on or change the default org.

Ask what the agent must do, which data and actions it can use, and how to test it. Wait for my answer before building.

Use agentforce-generate to implement the agent and its actions. Show me the changes before publishing. After publishing, run agentforce-test and report passing tests, failures, and remaining limitations.

Keep credentials out of chat, logs, and source files. Deploy only when the user's request explicitly asks; obtain authorization before tests that deploy metadata or run live actions.
```

## Setting Up Windsurf for Agentforce

# Getting Started with Windsurf

You need an org with Agentforce enabled. [Get a LabBox](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/docs/org-configuration>) if you need one.

## 1. Install Windsurf

#### Homebrew (macOS)

```bash
brew install --cask windsurf
```

#### Download

Download from [windsurf.com](https://windsurf.com/download) and install.

Open Windsurf and sign in to your account.

## 2. Connect Salesforce

Install Salesforce CLI if needed:

#### Homebrew

```bash
brew install sf
```

#### npm

```bash
npm install -g @salesforce/cli
```

Requires Node.js 22+. See the [CLI installation guide](https://developer.salesforce.com/docs/platform/sfdx-setup/guide/sfdx-setup-install-cli.html).

✅ **Verify:**

```bash
sf --version
```

The output should start with `@salesforce/cli/`.

If the org is already authorized, skip sign-in and use its existing alias.

#### LabBox

Run the generated command:
**Authenticate SF CLI**: Log in on this page to get your pre-filled authentication command.

```bash
echo "<SFDX_AUTH_URL>" | sf org login sfdx-url --sfdx-url-stdin --set-default --alias afdx-lab
```
This sets `afdx-lab` as your default org. Remove `--set-default` to keep your current default.

✅ **Verify:**

```bash
sf data query --query "SELECT Id, Name, IsSandbox FROM Organization" --target-org afdx-lab --json
```

Confirm that the output shows your LabBox.

#### Your org

```bash
sf org login web --alias my-org
```

✅ **Verify:**

```bash
sf data query --query "SELECT Id, Name, IsSandbox FROM Organization" --target-org my-org --json
```

Confirm that the output shows your org.

Use your chosen alias with `--target-org` in later commands. Include your chosen org alias when you paste the build prompt.

## 3. Install Agentforce skills

Open your project folder in Windsurf, then run this in its terminal:

```bash
npx skills add forcedotcom/sf-skills
```

Select your coding tool and the `agentforce-generate`, `agentforce-test`, and `agentforce-observe` skills. Skip this step if they are already available. Restart your coding tool if it does not discover them.

[Other installation options](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/docs/skills>).

## 4. Build an agent

Paste this prompt into Cascade.
```text
Check setup:

1. Check SF CLI: `sf --version`
   If missing, follow https://developer.salesforce.com/docs/platform/sfdx-setup/guide/sfdx-setup-install-cli.html for your OS.
2. Check that agentforce-generate, agentforce-test, and agentforce-observe are available in this coding agent.
   If missing, install them: `npx skills add forcedotcom/sf-skills`, selecting this coding agent as the target. Restart the session if the skills are not discovered.
   Obtain authorization before changing the global environment.
   Verify the running coding agent can discover and load agentforce-generate and agentforce-test before continuing.
3. Select the intended org alias or username from the setup page or user request. Replace <org-alias> below with that value; if the intended org is unclear, ask before continuing.
   Verify the org privately: `sf data query --query "SELECT Id, Name, IsSandbox FROM Organization" --target-org <org-alias> --json`
   Confirm the returned org name and Id match the intended target. If authentication fails, reconnect this org with its Labs credential command or `sf org login web --alias <org-alias>`, then verify it again.
   Pass `--target-org <org-alias>` on every subsequent org command; do not rely on or change the default org.

Ask what the agent must do, which data and actions it can use, and how to test it. Wait for my answer before building.

Use agentforce-generate to implement the agent and its actions. Show me the changes before publishing. After publishing, run agentforce-test and report passing tests, failures, and remaining limitations.

Keep credentials out of chat, logs, and source files. Deploy only when the user's request explicitly asks; obtain authorization before tests that deploy metadata or run live actions.
```

## Setting Up Antigravity for Agentforce

# Getting Started with Antigravity

You need an org with Agentforce enabled. [Get a LabBox](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/docs/org-configuration>) if you need one.

## 1. Install Antigravity

Download [Antigravity](https://antigravity.google/download), open it, and sign in.

## 2. Connect Salesforce

Install Salesforce CLI if needed:

#### Homebrew

```bash
brew install sf
```

#### npm

```bash
npm install -g @salesforce/cli
```

Requires Node.js 22+. See the [CLI installation guide](https://developer.salesforce.com/docs/platform/sfdx-setup/guide/sfdx-setup-install-cli.html).

✅ **Verify:**

```bash
sf --version
```

The output should start with `@salesforce/cli/`.

If the org is already authorized, skip sign-in and use its existing alias.

#### LabBox

Run the generated command:
**Authenticate SF CLI**: Log in on this page to get your pre-filled authentication command.

```bash
echo "<SFDX_AUTH_URL>" | sf org login sfdx-url --sfdx-url-stdin --set-default --alias afdx-lab
```
This sets `afdx-lab` as your default org. Remove `--set-default` to keep your current default.

✅ **Verify:**

```bash
sf data query --query "SELECT Id, Name, IsSandbox FROM Organization" --target-org afdx-lab --json
```

Confirm that the output shows your LabBox.

#### Your org

```bash
sf org login web --alias my-org
```

✅ **Verify:**

```bash
sf data query --query "SELECT Id, Name, IsSandbox FROM Organization" --target-org my-org --json
```

Confirm that the output shows your org.

Use your chosen alias with `--target-org` in later commands. Include your chosen org alias when you paste the build prompt.

## 3. Install Agentforce skills

Open your project folder in Antigravity, then run this in its terminal:

```bash
npx skills add forcedotcom/sf-skills
```

Select your coding tool and the `agentforce-generate`, `agentforce-test`, and `agentforce-observe` skills. Skip this step if they are already available. Restart your coding tool if it does not discover them.

[Other installation options](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/docs/skills>).

## 4. Build an agent

Paste this prompt into Antigravity.
```text
Check setup:

1. Check SF CLI: `sf --version`
   If missing, follow https://developer.salesforce.com/docs/platform/sfdx-setup/guide/sfdx-setup-install-cli.html for your OS.
2. Check that agentforce-generate, agentforce-test, and agentforce-observe are available in this coding agent.
   If missing, install them: `npx skills add forcedotcom/sf-skills`, selecting this coding agent as the target. Restart the session if the skills are not discovered.
   Obtain authorization before changing the global environment.
   Verify the running coding agent can discover and load agentforce-generate and agentforce-test before continuing.
3. Select the intended org alias or username from the setup page or user request. Replace <org-alias> below with that value; if the intended org is unclear, ask before continuing.
   Verify the org privately: `sf data query --query "SELECT Id, Name, IsSandbox FROM Organization" --target-org <org-alias> --json`
   Confirm the returned org name and Id match the intended target. If authentication fails, reconnect this org with its Labs credential command or `sf org login web --alias <org-alias>`, then verify it again.
   Pass `--target-org <org-alias>` on every subsequent org command; do not rely on or change the default org.

Ask what the agent must do, which data and actions it can use, and how to test it. Wait for my answer before building.

Use agentforce-generate to implement the agent and its actions. Show me the changes before publishing. After publishing, run agentforce-test and report passing tests, failures, and remaining limitations.

Keep credentials out of chat, logs, and source files. Deploy only when the user's request explicitly asks; obtain authorization before tests that deploy metadata or run live actions.
```

## LabBox

# LabBox

A LabBox is a Salesforce org provisioned through Agentforce Labs with every AI feature already enabled. Einstein, Agentforce, Data Cloud, OmniChannel routing, messaging channels, and an Experience Site with a chat widget are all configured and ready to use the moment you sign in.

No manual setup required. If you want to replicate the same configuration in your own org, each section below includes step-by-step instructions.

> **ℹ️ Already have a LabBox?**: You do not need to enable any of these settings yourself. This page documents what is pre-configured so you can understand the capabilities available to you.

## Core Platform

Einstein, Agentforce, and Data Cloud must be turned on before any AI features work. These are the foundational toggles. Everything else depends on them.

| Setting    | Setup Location         | What it does                                                                                                   |
| ---------- | ---------------------- | -------------------------------------------------------------------------------------------------------------- |
| Einstein   | Einstein Setup         | Enables the Einstein AI platform across the org                                                                |
| Agentforce | Einstein Copilot Setup | Enables the Agentforce agent runtime and Copilot features                                                      |
| Data Cloud | Org Provisioning       | Enables the Customer Data Platform for unified data access and grounding. Provisioning can take 30–60 minutes. |

### Enable it yourself

**Choose one of the following methods:**

#### SF CLI

```bash
# Verify Data Cloud is provisioned
sf data query --target-org {{ORG_ALIAS}} \
  --query "SELECT Id, Name, DurableId FROM Organization LIMIT 1"

# Verify Einstein limits are available
sf org display --target-org {{ORG_ALIAS}} --verbose --json | jq '.result'
```

#### REST API

```bash
# Verify Data Cloud
curl -s "{{INSTANCE_URL}}/services/data/v65.0/query?q=SELECT+Id,Name,DurableId+FROM+Organization+LIMIT+1" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}"

# Verify Einstein
curl -s "{{INSTANCE_URL}}/services/data/v65.0/limits" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" | jq '.EinsteinGenAiStandard'
```

#### Browser Agent

```text
1. Setup → Data Cloud Setup Home → verify "Home Org" shows Org Details
2. Setup → Quick Find → "Einstein Setup" → verify "Turn on Einstein" toggle is active
3. Setup → Quick Find → "Agentforce Agents" → click toggle in top-right to ON
4. Refresh page and confirm toggle persists
```

## API Access

| Setting        | Setup Location       | What it does                                                                   |
| -------------- | -------------------- | ------------------------------------------------------------------------------ |
| SOAP API Login | User Interface Setup | Allows programmatic login via SOAP API, used by CLI tools and backend services |

### Enable it yourself

**Choose one of the following methods:**

#### SF CLI

```bash
# No CLI equivalent. Use Browser Agent
echo "SOAP API Login must be enabled via Setup UI"
```

#### REST API

```bash
# No API equivalent. Use Browser Agent
echo "SOAP API Login must be enabled via Setup UI"
```

#### Browser Agent

```text
1. Setup → Quick Find → "User Interface"
2. Find "Enable SOAP API Login" checkbox
3. Check the box
4. Click "Save"
```

## Einstein Trust Layer

| Setting                       | Setup Location       | What it does                                                                                              |
| ----------------------------- | -------------------- | --------------------------------------------------------------------------------------------------------- |
| Large Language Model Data Masking | Einstein Trust Layer | Disabled to allow larger LLM context. Toggle is set to OFF so sensitive data patterns are not masked in prompts. |

### Enable it yourself

**Choose one of the following methods:**

#### SF CLI

```bash
# No CLI equivalent. Use Browser Agent
echo "Data Masking must be disabled via Setup UI"
```

#### REST API

```bash
# No API equivalent. Use Browser Agent
echo "Data Masking must be disabled via Setup UI"
```

#### Browser Agent

```text
1. Setup → Quick Find → "Einstein Trust"
2. Click "Einstein Trust Layer" (under Einstein → Einstein Generative AI)
3. Go to "Data Masking" tab
4. Turn OFF "Large Language Model Data Masking" toggle
5. Confirm if prompted
```

## Identity Verification

| Setting                                       | Setup Location        | What it does                                                                                 |
| --------------------------------------------- | --------------------- | -------------------------------------------------------------------------------------------- |
| Require identity verification for email changes | Identity Verification | Disabled so that org automation can update user email addresses without triggering verification flows. |

### Enable it yourself

**Choose one of the following methods:**

#### SF CLI

```bash
# No CLI equivalent. Use Browser Agent
echo "Identity Verification must be configured via Setup UI"
```

#### REST API

```bash
# No API equivalent. Use Browser Agent
echo "Identity Verification must be configured via Setup UI"
```

#### Browser Agent

```text
1. Setup → Quick Find → "Identity Verification"
2. Scroll to the "General" section at the bottom
3. Uncheck "Require identity verification for email address changes"
4. Click "Save"
```

## Permissions

A custom **AiAdmin** permission set is created with the following system permissions:

| Permission                | What it grants                               |
| ------------------------- | -------------------------------------------- |
| Manage Agentforce Grids   | Create and manage Agentforce Grid worksheets |
| Manage Agentforce Testing | Run Agent Eval Labs tests against agents     |
| Manage AI Agents          | Create, edit, and publish Agentforce agents  |
| API Enabled               | Programmatic API access for CLI tools and integrations |

The following permission sets are assigned to the org user:

| Permission Set                          | Purpose                                     |
| --------------------------------------- | ------------------------------------------- |
| AiAdmin                                 | AI agent management (see above)             |
| Data Cloud User                         | Access to Data Cloud objects and features   |
| Access Agentforce Optimization          | Agent optimization and performance features |
| Tableau Next Admin                      | Tableau analytics administration            |
| Tableau Next Platform Analyst           | Tableau analytics usage and dashboards      |
| Contact Center Admin                    | Contact center administration               |

> **💡 Tip**: The full list of enabled permission sets also includes AI Workbench, Access Agentforce Default Agent, Agent Presence Status Access, Agentforce Default Admin, Agentforce Reference App, Agentforce Service Agent Configuration, Code Builder User, Contact Center Agent, Contact Center Supervisor, Customer Orders, Data Cloud Architect, Einstein Sales Emails, Prompt Template Manager, Prompt Template User, and Tableau Next Consumer.

### Enable it yourself

**Choose one of the following methods:**

#### SF CLI

```bash
# Create AiAdmin permission set
EXISTING=$(sf data query --target-org {{ORG_ALIAS}} --json \
  --query "SELECT Id FROM PermissionSet WHERE Name='AiAdmin'" \
  | jq -r '.result.totalSize')

if [ "$EXISTING" = "0" ]; then
  sf data create record --target-org {{ORG_ALIAS}} \
    --sobject PermissionSet \
    --values "Name='AiAdmin' Label='AiAdmin' Description='Admin permissions for Agentforce'"
fi

# Deploy system permissions via Metadata API
mkdir -p /tmp/aiadmin-deploy/permissionsets
cat > /tmp/aiadmin-deploy/permissionsets/AiAdmin.permissionset-meta.xml << 'XMLEOF'
<?xml version="1.0" encoding="UTF-8"?>
<PermissionSet xmlns="http://soap.sforce.com/2006/04/metadata">
    <label>AiAdmin</label>
    <description>Admin permissions for Agentforce</description>
    <userPermissions>
        <enabled>true</enabled>
        <name>ManageAgentforceGrids</name>
    </userPermissions>
    <userPermissions>
        <enabled>true</enabled>
        <name>ManageAgentforceTesting</name>
    </userPermissions>
    <userPermissions>
        <enabled>true</enabled>
        <name>ManageAiAgents</name>
    </userPermissions>
    <userPermissions>
        <enabled>true</enabled>
        <name>ApiEnabled</name>
    </userPermissions>
</PermissionSet>
XMLEOF
cat > /tmp/aiadmin-deploy/package.xml << 'XMLEOF'
<?xml version="1.0" encoding="UTF-8"?>
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
    <types>
        <members>AiAdmin</members>
        <name>PermissionSet</name>
    </types>
    <version>65.0</version>
</Package>
XMLEOF
sf project deploy start --target-org {{ORG_ALIAS}} --source-dir /tmp/aiadmin-deploy

# Assign AiAdmin + Data Cloud User to admin
ADMIN_ID=$(sf data query --target-org {{ORG_ALIAS}} --json \
  --query "SELECT Id FROM User WHERE Profile.Name='System Administrator' AND IsActive=true ORDER BY CreatedDate ASC LIMIT 1" \
  | jq -r '.result.records[0].Id')

PS_ID=$(sf data query --target-org {{ORG_ALIAS}} --json \
  --query "SELECT Id FROM PermissionSet WHERE Name='AiAdmin'" \
  | jq -r '.result.records[0].Id')
sf data create record --target-org {{ORG_ALIAS}} \
  --sobject PermissionSetAssignment \
  --values "PermissionSetId='$PS_ID' AssigneeId='$ADMIN_ID'"

DC_PS_ID=$(sf data query --target-org {{ORG_ALIAS}} --json \
  --query "SELECT Id FROM PermissionSet WHERE Label='Data Cloud User' LIMIT 1" \
  | jq -r '.result.records[0].Id')
sf data create record --target-org {{ORG_ALIAS}} \
  --sobject PermissionSetAssignment \
  --values "PermissionSetId='$DC_PS_ID' AssigneeId='$ADMIN_ID'"
```

#### REST API

```bash
# Create AiAdmin permission set
curl -X POST "{{INSTANCE_URL}}/services/data/v65.0/sobjects/PermissionSet" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" \
  -H "Content-Type: application/json" \
  -d '{"Name":"AiAdmin","Label":"AiAdmin","Description":"Admin permissions for Agentforce","IsOwnedByProfile":false}'

# System permissions require Metadata API. Use SF CLI or Browser Agent

# Assign to admin user
ADMIN_ID=$(curl -s "{{INSTANCE_URL}}/services/data/v65.0/query?q=SELECT+Id+FROM+User+WHERE+Profile.Name='System+Administrator'+AND+IsActive=true+ORDER+BY+CreatedDate+ASC+LIMIT+1" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" | jq -r '.records[0].Id')

PS_ID=$(curl -s "{{INSTANCE_URL}}/services/data/v65.0/query?q=SELECT+Id+FROM+PermissionSet+WHERE+Name='AiAdmin'" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" | jq -r '.records[0].Id')

curl -X POST "{{INSTANCE_URL}}/services/data/v65.0/sobjects/PermissionSetAssignment" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" \
  -H "Content-Type: application/json" \
  -d "{\"PermissionSetId\":\"$PS_ID\",\"AssigneeId\":\"$ADMIN_ID\"}"
```

#### Browser Agent

```text
1. Setup → Quick Find → "Permission Sets" → New
2. Label: AiAdmin, API Name: AiAdmin → Save
3. Click "System Permissions" → Edit
4. Check: Manage Agentforce Grids, Manage Agentforce Testing, Manage AI Agents, API Enabled
5. Save (confirm if prompted)
6. Setup → Quick Find → "Users" → click admin user
7. Hover "Permission Set Assignments" → Edit Assignments
8. Add: AiAdmin, Data Cloud User
9. Save
```

> **✅ Success**: **Verify:** `SELECT Id, Assignee.Name, PermissionSet.Name FROM PermissionSetAssignment WHERE PermissionSet.Name='AiAdmin' AND Assignee.Profile.Name='System Administrator'`. This should return one record.

## Observability & Analytics

These settings enable the full observability stack for agents: monitoring, debugging, and optimization.

| Setting                                  | Setup Location                        | What it does                                           |
| ---------------------------------------- | ------------------------------------- | ------------------------------------------------------ |
| Einstein Audit and Feedback              | Einstein Audit Analytics & Monitoring | Audit logging for all Einstein AI interactions         |
| Knowledge / RAG Quality Data and Metrics | Einstein Audit Analytics & Monitoring | Quality metrics for RAG-based knowledge retrieval      |
| Agent Analytics                          | Einstein Audit Analytics & Monitoring | Agent performance dashboards and usage data            |
| Agentforce Session Tracing               | Einstein Audit Analytics & Monitoring | Session-level debugging traces for agent conversations |
| Agent Optimization                       | Einstein Audit Analytics & Monitoring | AI-driven optimization recommendations                 |
| Service Agent Analytics App              | Agent Analytics Setup                 | Pre-built analytics app for service agent performance  |

> **💡 Tip**: The Service Agent Analytics app can take up to 15 minutes to fully install after org provisioning because it depends on Agent Analytics being enabled first.

### Enable it yourself

**Choose one of the following methods:**

#### SF CLI

```bash
# Audit toggles require Setup UI. Use Browser Agent
# After enabling, assign Access Agentforce Optimization:
ADMIN_ID=$(sf data query --target-org {{ORG_ALIAS}} --json \
  --query "SELECT Id FROM User WHERE Profile.Name='System Administrator' AND IsActive=true LIMIT 1" \
  | jq -r '.result.records[0].Id')
OPT_PS_ID=$(sf data query --target-org {{ORG_ALIAS}} --json \
  --query "SELECT Id FROM PermissionSet WHERE Label='Access Agentforce Optimization' LIMIT 1" \
  | jq -r '.result.records[0].Id')
sf data create record --target-org {{ORG_ALIAS}} \
  --sobject PermissionSetAssignment \
  --values "PermissionSetId='$OPT_PS_ID' AssigneeId='$ADMIN_ID'"
```

#### REST API

```bash
# Audit toggles require Setup UI. Use Browser Agent
# After enabling, assign Access Agentforce Optimization:
ADMIN_ID=$(curl -s "{{INSTANCE_URL}}/services/data/v65.0/query?q=SELECT+Id+FROM+User+WHERE+Profile.Name='System+Administrator'+AND+IsActive=true+LIMIT+1" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" | jq -r '.records[0].Id')
OPT_PS_ID=$(curl -s "{{INSTANCE_URL}}/services/data/v65.0/query?q=SELECT+Id+FROM+PermissionSet+WHERE+Label='Access+Agentforce+Optimization'+LIMIT+1" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" | jq -r '.records[0].Id')
curl -X POST "{{INSTANCE_URL}}/services/data/v65.0/sobjects/PermissionSetAssignment" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" \
  -H "Content-Type: application/json" \
  -d "{\"PermissionSetId\":\"$OPT_PS_ID\",\"AssigneeId\":\"$ADMIN_ID\"}"
```

#### Browser Agent

```text
1. Setup → Quick Find → "Audit" → select "Einstein Audit, Analytics, and Monitoring Setup"
2. Enable: Audit and Feedback, Knowledge/RAG Quality Data and Metrics, Agent Analytics, Agentforce Session Tracing
3. Refresh Setup → navigate to Setup → Agent Analytics
4. Wait ~15 minutes if page is blank. Apps need time to appear
5. Once ready: install "Service Agent Analytics" (click down arrow → Install → Data Space: default → Ok)
6. Wait for "Installation Completed Successfully" notification
7. Setup → Users → admin user → Edit Assignments → add "Access Agentforce Optimization" → Save
```

## Digital Experiences

| Setting                    | Setup Location                                     | What it does                                                    |
| -------------------------- | -------------------------------------------------- | --------------------------------------------------------------- |
| Enable Digital Experiences | Feature Settings → Digital Experiences → Settings  | Allows creation of Experience Cloud sites for hosting chat widgets |

### Enable it yourself

**Choose one of the following methods:**

#### SF CLI

```bash
# No CLI equivalent. Use Browser Agent
echo "Digital Experiences must be enabled via Setup UI"
```

#### REST API

```bash
# No API equivalent. Use Browser Agent
echo "Digital Experiences must be enabled via Setup UI"
```

#### Browser Agent

```text
1. Setup → Feature Settings → Digital Experiences → Settings
2. Check "Enable Digital Experiences"
3. Click "Save"
4. A popup will appear asking to enable and register domain. Click "OK"
```

## Routing

OmniChannel routing allows agents to receive and handle conversations.

| Setting                                  | Setup Location       | What it does                                                        |
| ---------------------------------------- | -------------------- | ------------------------------------------------------------------- |
| Skills-Based and Direct-to-Agent Routing | OmniChannel Settings | Routes conversations to agents based on skills or direct assignment |
| Secondary Routing Priority               | OmniChannel Settings | Fallback routing when the primary agent is unavailable              |

A routing configuration and fallback queue are pre-created:

| Resource                          | Name                               | Details                                           |
| --------------------------------- | ---------------------------------- | ------------------------------------------------- |
| Routing Configuration             | Agentforce Service configuration   | Priority 1, Most Available model, capacity 2/3    |
| Queue                             | Agentforce Service Queue           | Supports Messaging Session, linked to routing config |

### Enable it yourself

**Choose one of the following methods:**

#### SF CLI

```bash
# Enable OmniChannel via Browser Agent first, then create routing config:
sf data create record --target-org {{ORG_ALIAS}} \
  --sobject ServiceChannel \
  --values "DeveloperName='Agentforce_Service_configuration' MasterLabel='Agentforce Service configuration' RoutingModel='MostAvailable' SecondaryRoutingPriority=1"

# Create queue via Metadata API
mkdir -p /tmp/queue-deploy/queues
cat > /tmp/queue-deploy/queues/Agentforce_Service_Queue.queue-meta.xml << 'XMLEOF'
<?xml version="1.0" encoding="UTF-8"?>
<Queue xmlns="http://soap.sforce.com/2006/04/metadata">
    <fullName>Agentforce_Service_Queue</fullName>
    <name>Agentforce Service Queue</name>
    <queueSobject>
        <sobjectType>MessagingSession</sobjectType>
    </queueSobject>
</Queue>
XMLEOF
cat > /tmp/queue-deploy/package.xml << 'XMLEOF'
<?xml version="1.0" encoding="UTF-8"?>
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
    <types>
        <members>Agentforce_Service_Queue</members>
        <name>Queue</name>
    </types>
    <version>65.0</version>
</Package>
XMLEOF
sf project deploy start --target-org {{ORG_ALIAS}} --source-dir /tmp/queue-deploy
```

#### REST API

```bash
# Create routing configuration
curl -X POST "{{INSTANCE_URL}}/services/data/v65.0/sobjects/ServiceChannel" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" \
  -H "Content-Type: application/json" \
  -d '{"DeveloperName":"Agentforce_Service_configuration","MasterLabel":"Agentforce Service configuration","RoutingModel":"MostAvailable","SecondaryRoutingPriority":1}'

# Create queue
curl -X POST "{{INSTANCE_URL}}/services/data/v65.0/sobjects/Group" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" \
  -H "Content-Type: application/json" \
  -d '{"Name":"Agentforce Service Queue","DeveloperName":"Agentforce_Service_Queue","Type":"Queue"}'

# Add MessagingSession as supported object
QUEUE_ID=$(curl -s "{{INSTANCE_URL}}/services/data/v65.0/query?q=SELECT+Id+FROM+Group+WHERE+DeveloperName='Agentforce_Service_Queue'+AND+Type='Queue'" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" | jq -r '.records[0].Id')
curl -X POST "{{INSTANCE_URL}}/services/data/v65.0/sobjects/QueueSobject" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" \
  -H "Content-Type: application/json" \
  -d "{\"QueueId\":\"$QUEUE_ID\",\"SobjectType\":\"MessagingSession\"}"
```

#### Browser Agent

```text
1. Setup → Feature Settings → Service → Omni-Channel Settings
2. Check "Enable Skills-Based and Direct-to-Agent Routing" and "Enable Secondary Routing Priority" → Save
3. Setup → Feature Settings → Service → Messaging → Messaging Settings → toggle ON
4. Setup → Quick Find → "Routing Configuration" → New
5. Name: Agentforce Service configuration, Priority: 1, Model: Most Available, Capacity: 2/3 → Save
6. Setup → Quick Find → "Queues" → New
7. Label: Agentforce Service Queue, Supported Objects: Messaging Session, Routing Config: Agentforce Service configuration, Members: add admin → Save
```

> **✅ Success**: **Verify:** `SELECT Id, DeveloperName FROM ServiceChannel WHERE DeveloperName='Agentforce_Service_configuration'` and `SELECT Id, DeveloperName FROM Group WHERE Type='Queue' AND DeveloperName='Agentforce_Service_Queue'`. Both should return one record.

## Messaging

| Setting           | Setup Location                                         | What it does                                                     |
| ----------------- | ------------------------------------------------------ | ---------------------------------------------------------------- |
| Enable Messaging  | Feature Settings → Service → Messaging → Messaging Settings | Enables the Messaging platform for agent chat conversations |

A messaging channel is pre-created:

| Field             | Value                        |
| ----------------- | ---------------------------- |
| Channel Name      | Salesforce Agent Channel     |
| Developer Name    | Salesforce_Agent             |
| Type              | Enhanced Chat                |
| Deployment Type   | Web                          |
| Routing Type      | Agentforce Service Agent     |
| Fallback Queue    | Agentforce Service Queue     |

### Enable it yourself

**Choose one of the following methods:**

#### SF CLI

```bash
BOT_ID=$(sf data query --target-org {{ORG_ALIAS}} --json \
  --query "SELECT Id FROM BotDefinition WHERE DeveloperName='{{AGENT_NAME}}'" \
  | jq -r '.result.records[0].Id')
QUEUE_ID=$(sf data query --target-org {{ORG_ALIAS}} --json \
  --query "SELECT Id FROM Group WHERE Type='Queue' AND DeveloperName='Agentforce_Service_Queue'" \
  | jq -r '.result.records[0].Id')

sf apex run --target-org {{ORG_ALIAS}} << EOF
MessagingChannel channel = new MessagingChannel();
channel.DeveloperName = 'Salesforce_Agent';
channel.MasterLabel = 'Salesforce Agent Channel';
channel.MessageType = 'EmbeddedMessaging';
channel.MessagingPlatformKey = 'Salesforce_Agent-' + String.valueOf(Crypto.getRandomLong());
channel.IsActive = true;
channel.SessionHandlerId = '$BOT_ID';
channel.FallbackQueueId = '$QUEUE_ID';
insert channel;
System.debug('Created MessagingChannel: ' + channel.Id);
EOF
```

#### REST API

```bash
BOT_ID=$(curl -s "{{INSTANCE_URL}}/services/data/v65.0/query?q=SELECT+Id+FROM+BotDefinition+WHERE+DeveloperName='{{AGENT_NAME}}'" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" | jq -r '.records[0].Id')
QUEUE_ID=$(curl -s "{{INSTANCE_URL}}/services/data/v65.0/query?q=SELECT+Id+FROM+Group+WHERE+Type='Queue'+AND+DeveloperName='Agentforce_Service_Queue'" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" | jq -r '.records[0].Id')

curl -X POST "{{INSTANCE_URL}}/services/data/v65.0/sobjects/MessagingChannel" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" \
  -H "Content-Type: application/json" \
  -d "{\"DeveloperName\":\"Salesforce_Agent\",\"MasterLabel\":\"Salesforce Agent Channel\",\"MessageType\":\"EmbeddedMessaging\",\"MessagingPlatformKey\":\"Salesforce_Agent-$(date +%s)\",\"IsActive\":true,\"SessionHandlerId\":\"$BOT_ID\",\"FallbackQueueId\":\"$QUEUE_ID\"}"
```

#### Browser Agent

```text
1. Setup → Feature Settings → Service → Messaging → Messaging Settings
2. Click "New Channel" → Select "Enhanced Chat" → Next
3. Channel Name: Salesforce Agent Channel, Developer Name: Salesforce_Agent
4. Deployment Type: Web, Domain: <your site domain>
5. Routing Type: Agentforce Service Agent, Agent: {{AGENT_NAME}}
6. Fallback Queue: Agentforce Service Queue → Save
```

## Embedded Service Deployment

An Embedded Service Deployment container is pre-created to host the chat widget.

| Field                           | Value                                |
| ------------------------------- | ------------------------------------ |
| Embedded Service Deployment Name | Salesforce Agent Web Deployment     |
| API Name                        | Salesforce_Agent                     |
| Type                            | Enhanced Chat                        |
| Messaging Channel               | Salesforce Agent Channel             |

> **ℹ️ Info**: After creation the deployment is switched to V2 and published so the chat widget is immediately available.

### Enable it yourself

**Choose one of the following methods:**

#### SF CLI

```bash
# Embedded Service Deployments are best created via the Setup UI wizard
echo "Use Browser Agent for Embedded Service Deployment creation"
```

#### REST API

```bash
# Embedded Service Deployments are best created via the Setup UI wizard
echo "Use Browser Agent for Embedded Service Deployment creation"
```

#### Browser Agent

```text
1. Setup → Embedded Service → Embedded Service Deployments → New Deployment
2. Select "Enhanced Chat" → Select "Web"
3. Name: Salesforce Agent Web Deployment, API Name: Salesforce_Agent
4. Domain: <your site domain>, Messaging Channel: Salesforce Agent Channel → Save
5. Click "Switch to V2" (top right)
6. Click "Publish" (top right)
```

## Experience Site

An Experience Cloud site is pre-created with the chat widget embedded.

| Field            | Value                        |
| ---------------- | ---------------------------- |
| Site Name        | Salesforce Agent             |
| Template         | Build Your Own (LWR)         |
| Public Access    | Enabled                      |
| Enhanced Chat | Placed in Footer, linked to Salesforce Agent Web Deployment |

### Enable it yourself

**Choose one of the following methods:**

#### SF CLI

```bash
# Create site
sf community create --target-org {{ORG_ALIAS}} \
  --name "Salesforce Agent" \
  --template-name "Build Your Own (LWR)" \
  --url-path-prefix "salesforceagent"

# After adding Enhanced Chat component via Builder, publish:
sf community publish --target-org {{ORG_ALIAS}} \
  --name "Salesforce Agent"
```

#### REST API

```bash
# Publish via Connect API (after creating site and adding component via Builder)
NETWORK_ID=$(curl -s "{{INSTANCE_URL}}/services/data/v65.0/query?q=SELECT+Id+FROM+Network+WHERE+Name='Salesforce+Agent'" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" | jq -r '.records[0].Id')
curl -X POST "{{INSTANCE_URL}}/services/data/v65.0/connect/communities/$NETWORK_ID/publish" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" \
  -H "Content-Type: application/json"
```

#### Browser Agent

```text
1. Setup → Digital Experiences → All Sites → New
2. Select "Build Your Own (LWR)" → Get Started → Name: Salesforce Agent → Create
3. In Experience Builder: Settings → General → enable "Public Access"
4. Drag "Enhanced Chat" component into Footer → select "Salesforce Agent Web Deployment"
5. Click "Publish" → confirm
```

> **✅ Success**: **Verify:** `SELECT Id, Name, Status FROM Network WHERE Name='Salesforce Agent'`. This should return one record with Status = 'Live'.

## Site Domain & Trusted Domains

The Salesforce Site domain is registered and trusted domains are configured so the chat widget loads without CORS errors.

| Setting                | Setup Location                         | What it does                                                  |
| ---------------------- | -------------------------------------- | ------------------------------------------------------------- |
| Site Domain            | User Interface → Sites & Domains → Sites | Registered so Experience Sites have a public URL             |
| Trusted Domain         | Sites & Domains → Sites                | `*.my.site.com` added to trusted domains for inline frames   |

> **⚠️ Trusted domains are critical**: Without the trusted domain entry, the chat widget will fail silently. No button appears and no errors are shown to the user. This is the most common cause of "chat doesn't work" issues.

### Enable it yourself

**Choose one of the following methods:**

#### SF CLI

```bash
# Register domain (check if already exists)
sf data query --target-org {{ORG_ALIAS}} \
  --query "SELECT Id, Domain FROM Domain WHERE DomainType='DNS' LIMIT 1"

# Add trusted domain
sf data create record --target-org {{ORG_ALIAS}} \
  --sobject CorsWhitelistEntry \
  --values "UrlPattern='https://*.my.site.com'"

# Republish site after adding trusted domain
sf community publish --target-org {{ORG_ALIAS}} \
  --name "Salesforce Agent"
```

#### REST API

```bash
curl -X POST "{{INSTANCE_URL}}/services/data/v65.0/sobjects/CorsWhitelistEntry" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" \
  -H "Content-Type: application/json" \
  -d '{"UrlPattern":"https://*.my.site.com"}'

# Republish site
NETWORK_ID=$(curl -s "{{INSTANCE_URL}}/services/data/v65.0/query?q=SELECT+Id+FROM+Network+WHERE+Name='Salesforce+Agent'" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" | jq -r '.records[0].Id')
curl -X POST "{{INSTANCE_URL}}/services/data/v65.0/connect/communities/$NETWORK_ID/publish" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" \
  -H "Content-Type: application/json"
```

#### Browser Agent

```text
1. Setup → User Interface → Sites and Domains → Sites
2. Accept terms → Click "Register My Salesforce Site Domain" → accept popup
3. Find "ESW_Salesforce_Agent_*" → "Trusted Domains for Inline Frames" → Add Domain: *.my.site.com
4. Republish Experience Site: Setup → Digital Experiences → All Sites → Salesforce Agent → Builder → Publish
```

## Agent Deployment

A default Agentforce Service Agent is deployed, compiled, and activated in the org.

| Step               | What happens                                                      |
| ------------------ | ----------------------------------------------------------------- |
| Metadata Deploy    | Agent bundle deployed via Metadata API                            |
| Script Compilation | AgentScript compiled via SFAP authoring endpoint                  |
| Version Publish    | Agent version published and linked to the org instance            |
| Activation         | Bot version set to Active so the agent can receive conversations  |

### Enable it yourself

**Choose one of the following methods:**

#### SF CLI

```bash
# 1. Deploy agent metadata
sf project deploy start --target-org {{ORG_ALIAS}} \
  --source-dir data/AgentforceDotCom

# 2. Get session and instance
SESSION_ID=$(sf org display --target-org {{ORG_ALIAS}} --json | jq -r '.result.accessToken')
INSTANCE=$(sf org display --target-org {{ORG_ALIAS}} --json | jq -r '.result.instanceUrl')

# 3. Get NamedUser JWT
JWT=$(curl -s "$INSTANCE/agentforce/bootstrap/nameduser" \
  -H "Cookie: sid=$SESSION_ID" \
  -H "Accept: application/json" | jq -r '.access_token')

# 4. Compile AgentScript
SCRIPT=$(cat data/AgentforceDotCom/aiAuthoringBundles/{{AGENT_NAME}}/{{AGENT_NAME}}.agent)
COMPILED=$(curl -s -X POST "https://api.salesforce.com/einstein/ai-agent/v1.1/authoring/scripts" \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" \
  -H "x-client-name: org-recipe" \
  -d "{\"assets\":[{\"type\":\"AFScript\",\"name\":\"AFScript\",\"content\":$(echo "$SCRIPT" | jq -Rs .)}],\"afScriptVersion\":\"1.0.1\"}")
ARTIFACT=$(echo "$COMPILED" | jq '.compiledArtifact')

# 5. Publish agent version
PUBLISH=$(curl -s -X POST "https://api.salesforce.com/einstein/ai-agent/v1.1/authoring/agents" \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" \
  -d "{\"agentDefinition\":$ARTIFACT,\"agentVersion\":\"1\",\"instanceConfig\":{\"endpoint\":\"$INSTANCE\"}}")
BOT_VERSION_ID=$(echo "$PUBLISH" | jq -r '.botVersionId')

# 6. Activate
curl -X POST "$INSTANCE/services/data/v65.0/connect/bot-versions/$BOT_VERSION_ID/activation" \
  -H "Authorization: Bearer $SESSION_ID" \
  -H "Content-Type: application/json" \
  -d '{"status":"ACTIVE"}'
```

#### REST API

```bash
# Same flow as SF CLI. Deploy ZIP via SOAP Metadata API, then:
# GET {instance}/agentforce/bootstrap/nameduser → JWT
# POST https://api.salesforce.com/einstein/ai-agent/v1.1/authoring/scripts → compile
# POST https://api.salesforce.com/einstein/ai-agent/v1.1/authoring/agents → publish
# POST /services/data/v65.0/connect/bot-versions/{botVersionId}/activation → activate
```

#### Browser Agent

```text
1. Setup → Agents → Agent Builder
2. Import or create the agent from metadata
3. Click "Activate" or "Publish"
4. Verify status shows "Active"
```

## MCP Servers

MCP (Model Context Protocol) servers expose Salesforce data and actions to agents as callable tools. Each server bundles a set of related tools an agent can invoke at runtime.

| Setting                   | Setup Location                                    | What it does                            |
| ------------------------- | ------------------------------------------------- | --------------------------------------- |
| Enable MCP Service (Beta) | Setup → User Interface                            | Turns on the MCP Service for the org    |
| Activate servers          | Setup → Integrations → API Catalog → MCP Servers  | Exposes each server's tools to agents   |

MCP is available on **Enterprise, Developer, and Unlimited editions** (GA, April 2026), so a freshly provisioned LabBox already has it. To turn it on, check the **"Enable MCP Service (Beta)"** preference under **Setup → User Interface**, then activate the servers you want.

Standard MCP servers ship **Inactive by default** — each one must be activated individually in Setup before its tools become available to agents. There is **no Metadata API type or Tooling-API field** to activate a server programmatically — activation is UI-only.

| Server                  | Tools                  | What it exposes                                                       |
| ----------------------- | ---------------------- | --------------------------------------------------------------------- |
| sobject-reads           | 6 tools                | Read access to standard and custom sObject records                    |
| sobject-mutations       | 9 tools                | Create and update sObject records                                     |
| sobject-deletes         | 8 tools                | Delete sObject records                                                |
| sobject-all             | 11 tools + 2 prompts   | Combined read, mutate, and delete access with guided prompts          |
| data-cloud-queries      | 2 tools                | Query Data Cloud objects and unified profiles                         |
| metadata-experts        | 1 tool                 | Inspect org metadata                                                  |
| salesforce-api-context  | 6 tools                | Salesforce API reference and context for grounding agent responses    |

### Enable it yourself

**Choose one of the following methods:**

#### SF CLI

```bash
# No CLI equivalent. MCP server activation is UI-only.
# There is no Metadata API type or Tooling-API field to activate a server.
echo "MCP Servers must be activated via Setup UI"
```

#### REST API

```bash
# No API equivalent. MCP server activation is UI-only — there is no supported
# Tooling/Metadata API object to query or toggle a server's activation state.
echo "MCP Servers must be activated via Setup UI"
```

#### Browser Agent

```text
1. Setup → User Interface → check "Enable MCP Service (Beta)"
2. Setup → Integrations → API Catalog → MCP Servers
3. Open the "Salesforce Servers" tab
4. Click a server (e.g. sobject-reads) → "Activate" → confirm
5. Repeat step 4 for each server your agents need
```

> **ℹ️ Client auth (External Client App)**: External MCP clients authenticate with an **External Client App (ECA)** using OAuth scopes `api, sfap_api, refresh_token, einstein_gpt_api` (keep `refresh_token` or clients disconnect frequently). Runtime endpoints are `https://api.salesforce.com/platform/mcp/v1/platform/<server>` for standard servers and `/v1/custom/<name>` for custom ones.

## Agentforce Coworker

Agentforce Coworker is a one-click enablement that provisions an end-to-end coworker experience: Agentforce Studio, CRM connectivity, search infrastructure, the coworker agent, personalization, and Data 360 user ingestion with identity resolution.

| Setting                    | Setup Location                                     | What it does                                                          |
| -------------------------- | -------------------------------------------------- | -------------------------------------------------------------------- |
| Enable Agentforce Coworker | Setup → Agentforce Coworker → Get Started → Turn On | Provisions the end-to-end coworker experience for the org            |

| Component             | What it sets up                                                       |
| --------------------- | --------------------------------------------------------------------- |
| Agentforce Studio     | The authoring and management surface for the coworker agent           |
| CRM connectivity      | Connects the coworker to your CRM data                                |
| Search infrastructure | AI search indexing that grounds coworker responses                    |
| Coworker agent        | The pre-built Agentforce Coworker agent                               |
| Personalization       | Per-user tailoring of coworker behavior                               |
| Data 360 ingestion    | User ingestion and identity resolution via Data 360                   |

**Prerequisites:**

| Prerequisite                    | Notes                                                                                                       |
| ------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| `AgenticEnterpriseSearchAddOn`  | The binding add-on license (paired with `EinsteinAgentPlatform`). Auto-provisions the AI Search permission set licenses. Bundled on Flex Foundations / A1E / A4X / Agentforce for Service or Sales; otherwise add it (internally via Black Tab, customers via their SKU). |
| Data 360 / Data Cloud           | A Data Cloud data space is required. Verify a Data 360 app appears in the App Launcher.                      |
| Beta GenAI Models               | Setup → Einstein Setup → "Beta Generative AI Models" enabled                                                 |
| Role                            | Data 360 Admin or Data 360 Architect                                                                         |

> **ℹ️ The license is the gate, not the PSLs**: The `AgenticEnterpriseSearchAddOn` license **auto-provisions** the permission set licenses behind Coworker — `AI Search Permission Set License` (`AISearchUserPsl`) and `AI Search Setup Permission Set License` (`AISearchAdminPsl`). Admins do **not** request these PSLs directly; provisioning the add-on license grants them. Note that the add-on is a SKU/provisioning-level grant — it does **not** appear as a row in `PermissionSetLicense`; the queryable artifacts are the two AI Search PSLs named above.

**Permissions:**

| To do this          | Assign                                                                                          |
| ------------------- | ----------------------------------------------------------------------------------------------- |
| Configure Coworker  | The **`Agentforce Coworker Admin`** permission set. (The `Agentforce Coworker Setup` PSL is consumed automatically — no manual step.) |
| Use Coworker        | Grant end-user access via the **"Manage Access to Agentforce Coworker"** section of Coworker Setup (it assigns the AI Search access permission set group created during setup — confirm the exact name in your org). Do **not** assign the bare "Agentforce Coworker User" / "Ask Agentforce User" permission *set*; it grants the UI but not the agent. |

> **ℹ️ Legacy \**: On orgs not yet on the 262 release, this feature and its permission sets may still appear under the legacy **"Ask Agentforce"** label — it is the same product (the rename rolled out around early June). The "Get Started with Agentforce Coworker" Setup node only appears **after** the `Agentforce Coworker Admin` permission set is assigned and the page is refreshed.

### Enable it yourself

**Choose one of the following methods:**

#### SF CLI

```bash
# The add-on license is provisioned via Black Tab (internal) or your SKU,
# then Coworker is turned on via the Setup UI. But you can verify the AI Search
# permission set licenses landed in the org:
sf data query --target-org {{ORG_ALIAS}} \
  --query "SELECT DeveloperName, MasterLabel FROM PermissionSetLicense WHERE DeveloperName IN ('AISearchUserPsl','AISearchAdminPsl')"
```

#### REST API

```bash
# Enablement is UI-only. Verify the AI Search permission set licenses are present:
curl -s "{{INSTANCE_URL}}/services/data/v65.0/query?q=SELECT+DeveloperName,MasterLabel+FROM+PermissionSetLicense+WHERE+DeveloperName+IN+('AISearchUserPsl','AISearchAdminPsl')" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}"
```

#### Browser Agent

```text
1. Confirm prerequisites: qualifying edition (EE/Unlimited/A1),
   AgenticEnterpriseSearchAddOn license present, a Data Cloud data space,
   and Beta Generative AI Models enabled
2. Assign the "Agentforce Coworker Admin" permission set to yourself,
   then refresh the page
3. Setup → Quick Find → "Agentforce Coworker" (or "Ask Agentforce" on
   pre-262 orgs)
4. Set Up Data Space → pick your Data Cloud data space
5. Click "Get Started" → "Turn On" → confirm
6. Enable the Global Search Bar
7. Grant end users access via "Manage Access to Agentforce Coworker"
   (assigns the AI Search access permission set group)
```

> **ℹ️ Reference**: Salesforce's public guide: [Turn On Agentforce Coworker Infrastructure](https://developer.salesforce.com/docs/data/agentforce-coworker/guide/agentforce-coworker-turn-on-infrastructure.html).

## Agentforce Voice

Agentforce Voice brings agent conversations to voice channels.

| Setting             | Setup Location                          | What it does                                              |
| ------------------- | --------------------------------------- | --------------------------------------------------------- |
| Add a voice connection | Agent Builder → open agent → Voice Settings | Unlocks Voice for the agent (Telephony or Enhanced Chat v2) |

You unlock **Voice Settings** in Agent Builder by adding a voice **connection** to your agent. There are two enablement paths:

| Path                    | What it gives you                                                  | Setup effort                                                                                                                                                                          |
| ----------------------- | ------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Telephony connection    | Full voice agent over PSTN/SIP (phone calls)                       | Separate manual setup — AWS Contact Center, call flows, and procuring phone numbers (not automated). OrgFarm offers an "AgentforceVoice" checkout template for voice-enabled orgs.    |
| Enhanced Chat v2 (ECv2) | "Voice in chat" — microphone input within the Enhanced Chat v2 window | Lighter path; "Voice-Enabled Agents in Enhanced Chat v2" (Summer '26 release)                                                                                                        |

> **⚠️ Full telephony is a separate manual setup**: Telephony voice (AWS Contact Center, call flows, and phone numbers) is **not pre-configured in a LabBox** and is not automated — it is a separate manual setup that takes roughly 40 minutes end to end. For a ready-made voice-enabled org, use the OrgFarm "AgentforceVoice" checkout template instead of building telephony by hand.

### Required licenses and permission

Agentforce Voice requires two licenses in the org: an **Agentforce** license and a **Service Cloud Voice** license. Agentforce Voice is the AI voice-agent layer that runs on top of Service Cloud Voice telephony, so both must be present.

> **ℹ️ Reference**: See Salesforce's [Agentforce Voice prerequisites](https://help.salesforce.com/s/articleView?id=ai.agentforce_voice_setup_prereqs.htm&type=5) for the current license and permission requirements.

### Enable it yourself

**Choose one of the following methods:**

#### SF CLI

```bash
# The Agentforce + Service Cloud Voice licenses are provisioned commercially
# (Black Tab internal, or your Voice SKU), not via CLI. But you can verify
# whether Service Cloud Voice is switched on in the org:
sf data query --use-tooling-api --target-org {{ORG_ALIAS}} \
  --query "SELECT IsServiceCloudVoiceEnabled FROM ServiceCloudVoiceSettings"
```

#### REST API

```bash
# Licenses are provisioned commercially, not via API. Verify whether
# Service Cloud Voice is enabled in the org:
curl -s "{{INSTANCE_URL}}/services/data/v65.0/tooling/query?q=SELECT+IsServiceCloudVoiceEnabled+FROM+ServiceCloudVoiceSettings" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}"
```

#### Browser Agent

```text
1. Ensure the org has an Agentforce license and a Service Cloud Voice license
2. Open the agent in Agent Builder
3. Add a Voice connection (Telephony or Enhanced Chat v2) to unlock Voice Settings
4. For full telephony, complete the separate AWS Contact Center / phone-number
   setup, or check out an OrgFarm "AgentforceVoice" template org
```

## Vibes

Vibes is Salesforce's in-org agentic coding experience. It comes in two surfaces: **Vibes Chat**, which runs in **VS Code Desktop**, and **Vibes IDE**, which is **browser-based and launched from within the org**.

| Setting | Setup Location                                                   | What it does                          |
| ------- | ---------------------------------------------------------------- | ------------------------------------- |
| Vibes   | Launched in-org (Vibes IDE) or VS Code Desktop (Vibes Chat)      | In-org agentic coding experience      |

| Aspect            | Detail                                                                                  |
| ----------------- | --------------------------------------------------------------------------------------- |
| Surfaces          | Vibes Chat (VS Code Desktop) and Vibes IDE (browser, launched from within the org)      |
| Pricing           | Free in Developer Edition — no purchase required                                        |
| Model             | Anthropic Claude Sonnet (current generation)                                            |
| Lifetime limit    | 110 requests / 1.5M tokens per org — one-time, does not refresh, no fallback model      |

_Model and limits last verified June 2026; both are subject to change — confirm the current values in your org._

> **⚠️ Developer Edition limits and exclusions**: The Developer Edition allowance is a **one-time lifetime cap of 110 requests / 1.5M tokens per org**. It does **not** refresh, and once it is exhausted there is **no fallback model** — Vibes simply stops. Developer Edition orgs **cannot** receive the unmetered per-user-per-month (PUPM) license because there is no billing mechanism in DE; ISVs likewise cannot get unmetered access in DE orgs. Vibes is **not available in the EU Operating Zone or Government Cloud**.

### Enable it yourself

**Choose one of the following methods:**

#### SF CLI

```bash
# No CLI equivalent. Vibes is available in-org by edition and region,
# not provisioned via API.
echo "Vibes availability is determined by org edition and region"
```

#### REST API

```bash
# No API equivalent. Vibes is available in-org by edition and region,
# not provisioned via API.
echo "Vibes availability is determined by org edition and region"
```

#### Browser Agent

```text
1. Vibes IDE: launch it from within the org
2. Vibes Chat: use the Salesforce extension in VS Code Desktop
```

> **✅ Ready to build**: Your LabBox comes pre-configured with all of the above, including a working agent, messaging channel, and Experience Site with a chat widget. If any of these settings appear disabled, contact the Agentforce Labs team.

## Agent Script

# Agent Script

Agent Script is a programming language purpose-built for AI agents. It gives you deterministic control over workflows while letting the LLM reason where it matters. One file defines your entire agent: subagents, actions, transitions, and guardrails.

### Readable by Anyone

Syntax designed so product managers, architects, and developers can all understand what an agent does at a glance.

### Deterministic Where It Counts

Business-critical logic runs before the LLM. Conditionals, transitions, and action chains execute exactly as written.

### Source-Controlled

`.agent` files live in your repo. Version, diff, review, and deploy agents the same way you ship code.

## Your First Agent in 30 Lines

Here's a complete agent that routes customer requests, verifies identity, and manages orders:

```yaml
config:
  developer_name: "Support_Agent"
  agent_type: "AgentforceServiceAgent"

system:
  instructions: "You are a customer support agent. Be concise and helpful."

start_agent topic_selector:
  description: "Welcome the user and route to the right subagent"

  reasoning:
    instructions: ->
      | Welcome the user and determine what they need help with.
      | Use the available actions to route them appropriately.

    actions:
      go_to_orders: @utils.transition to @subagent.Order_Management
        description: "Handles order lookup, refunds, and updates"

      go_to_faq: @utils.transition to @subagent.General_FAQ
        description: "Answers common questions about products and policies"

      go_to_escalation: @utils.transition to @subagent.Escalation
        description: "Connects user with a human representative"
```

That's it. The agent greets the user, the LLM classifies their intent, and they're routed to the right subagent. No framework, no boilerplate, no deployment pipeline to configure.

## Two Types of Instructions

Agent Script's power comes from separating **what's deterministic** from **what needs AI reasoning**. Two arrow types make this explicit:

#### Logic (Deterministic)

```yaml
# -> means "run this code exactly"
reasoning:
  instructions: ->
    run @actions.lookup_order
      with email=@variables.customer_email
      set @variables.order=@outputs.order_data

    if @variables.order is None:
      | We couldn't find an order with that email. Can you double-check?
    else:
      | Here's what we found for your order: {!@variables.order}
```

Logic instructions execute **before** the LLM sees anything. Actions run, variables are set, conditionals branch — all deterministically.

#### Prompt (AI Reasoning)

```yaml
# | means "send this to the LLM"
reasoning:
  instructions: ->
    | The customer is asking about their order.
    | Review the order details in {!@variables.order} and help them
      with whatever they need — tracking, modifications, or returns.
    | If they seem frustrated, proactively offer to escalate
      using {!@actions.go_to_escalation}.
```

Prompt instructions are natural language sent to the LLM. The AI interprets them, reasons about context, and decides how to respond.

#### Mixed (Both Together)

```yaml
reasoning:
  instructions: ->
    # Deterministic: always fetch the customer's tier
    run @actions.get_loyalty_tier
      with customer_id=@variables.customer_id
      set @variables.tier=@outputs.tier

    # AI reasoning: let the LLM decide what to do with it
    | The customer's loyalty tier is {!@variables.tier}.
    | Adjust your tone and offers accordingly — Platinum members
      get priority treatment and exclusive offers.
```

Mix both freely. Run an action deterministically, then hand the results to the LLM for interpretation.

## Patterns That Ship

#### Action Chaining

Run multiple actions in guaranteed sequence. No hoping the LLM remembers step 2.

```yaml
reasoning:
  instructions: ->
    # Step 1: Get the order
    run @actions.lookup_current_order
      with member_email=@variables.member_email
      set @variables.order_summary=@outputs.order_summary

    # Step 2: Check return eligibility (uses step 1's output)
    run @actions.check_return_eligibility
      with order_id=@variables.order_summary
      set @variables.eligible=@outputs.eligible

    | Show the order summary and return eligibility to the customer.
```

#### Conditional Routing

Route users based on state — not LLM guesswork.

```yaml
reasoning:
  instructions: ->
    if @variables.loyalty_tier == "Platinum VIP":
      transition to @subagent.vip_support

    if @variables.is_verified == False:
      transition to @subagent.identity_verification

    | Welcome back! How can I help you today?
```

Transitions happen before the LLM processes any other instructions.

#### Data Hydration

Load data before the LLM starts thinking. Use `before_reasoning` to hydrate context on every turn:

```yaml
before_reasoning: ->
  if @variables.customer_profile == "":
    run @actions.get_customer_profile
      with user_id=@variables.user_id
      set @variables.customer_profile=@outputs.profile

reasoning:
  instructions: ->
    | You're helping {!@variables.customer_profile}.
    | Reference their history when making recommendations.
```

The `before_reasoning` block runs before every reasoning turn — the LLM always has fresh data.

#### Filtered Visibility

Control which tools the LLM can see based on state. Unverified users can't access order management.

```yaml
reasoning:
  actions:
    go_to_identity: @utils.transition to @subagent.Identity
      description: "Verifies user identity"
      available when @variables.verified == False

    go_to_order: @utils.transition to @subagent.Order_Management
      description: "Handles order management"
      available when @variables.verified == True
```

#### Action + Transition

Define follow-up actions that fire automatically when the LLM calls a tool:

```yaml
reasoning:
  instructions: ->
    | If the user wants information, use {!@actions.my_action}.

  actions:
    my_action: @actions.my_action
      with foo=@variables.Foo
      set @variables.status = @outputs.status
      run @actions.other_action
        set @variables.some_other_result=@outputs.data
```

Whenever the LLM calls `my_action`, `other_action` fires automatically afterward. No extra prompt needed.

## Building Blocks

#### Variables

Variables track state across subagents and conversation turns. Three types cover every use case:

```yaml
variables:
  # Linked: populated from external context (messaging session, API)
  user_id: linked string
    description: "User identifier from the messaging session"

  # Mutable: your agent's working memory
  verified: mutable boolean = False
    description: "Whether identity verification passed"

  order_data: mutable string
    description: "Current order details"

  # Slot-fill: LLM extracts from conversation
  customer_email: mutable string = ...
    description: "Customer's email address"
```

The `...` token tells the LLM to extract the value from conversation. When the user says "my email is jane@example.com", the agent captures it automatically.

#### Subagents

Each subagent is a self-contained unit with its own instructions, actions, and transitions. Think of them as microservices for conversation:

```yaml
subagent Identity_Verification:
  description: "Verify the customer's identity before accessing account data"

  reasoning:
    instructions: ->
      | Ask the customer for their email address.
      | Then use {!@actions.send_code} to send a verification code.
      | Once they provide the code, use {!@actions.verify_code}.

    actions:
      send_code: @actions.Send_Verification_Code
        with email=@variables.customer_email
        set @variables.auth_key=@outputs.auth_key

      verify_code: @actions.Verify_Code
        with code=@variables.customer_code
        with auth_key=@variables.auth_key
        set @variables.verified=@outputs.is_verified

  after_reasoning: ->
    if @variables.verified == True:
      transition to @subagent.topic_selector
```

The `after_reasoning` block runs after the LLM completes its turn — perfect for cleanup, state updates, and automatic transitions.

#### System & Personas

Override global behavior with per-subagent system instructions. VIP customers get a completely different personality without affecting other subagents:

```yaml
system:
  instructions: "You are a helpful customer support agent for Acme Corp."

subagent VIP_Support:
  description: "Premium support for VIP customers"

  system:
    instructions: ->
      | You are a premium concierge for Acme Corp's VIP program.
      | Address the customer by name. Offer proactive solutions.
      | You have authority to issue credits up to $500.

  reasoning:
    instructions: ->
      | Provide white-glove service to {!@variables.customer_name}.
```

#### Actions

Actions are how your agent interacts with the world. Agent Script supports many target protocols:

```yaml
actions:
  # Call an Apex class
  get_profile: @actions.Get_Customer_Profile
    target: "apex://c__CustomerActions.getProfile"

  # Trigger a Flow
  process_refund: @actions.Issue_Refund
    target: "flow://Issue_Refund"

  # Call an external API via External Services
  lookup_orders: @actions.Lookup_Orders
    target: "externalService://OrdersAPI.listOrders"

  # Use a Prompt Template
  summarize: @actions.Summarize_Case
    target: "prompt://Case_Summary_Template"
```

Apex, Flows, External Services, Prompt Templates, and more — all invoked with the same `@actions` syntax. Your agent doesn't care where the logic lives.

## Deploy from Your IDE

Agent Script files are standard source — build, test, and deploy using the tools you already know:

#### CLI

```bash
# Publish your agent to a Salesforce org
sf agent generate -d my-agent.agent
sf agent publish -d force-app

# Run automated tests
sf agent test run --name "My_Agent_Tests"
```

#### Claude Code

```bash
# Install the Agentforce lifecycle plugin
claude plugin install agentforce-adlc@claude-plugins-official

# Build, publish, and test — all from natural language
claude "Build a support agent that handles order tracking
       and returns. Deploy it to my org and run tests."

# Or run the CLI directly
sf agent publish authoring-bundle --api-name Support_Agent
sf agent test run --api-name Support_Agent_Tests --wait 10
```

## What's Coming Next

> **ℹ️ On the Roadmap**: Agent Script is evolving fast. Here's what's shipping soon:
>
> - **MCP Integration** — Connect agents directly to Model Context Protocol servers. Your agent will be able to call external tools, databases, and APIs through the standard MCP interface, making it a first-class citizen in the broader AI tooling ecosystem.
> - **Enhanced Flexibility** — More expressive control flow, richer variable types, and deeper integration with Salesforce data models. Write less configuration, get more capability.
> - **Cross-Agent Orchestration** — Agents that can invoke other agents as tools, enabling multi-agent architectures for complex enterprise workflows.

## Quick Reference

| Symbol | Meaning | Example |
|--------|---------|---------|
| `->` | Logic instructions (deterministic) | `instructions: ->` |
| `\|` | Prompt instructions (sent to LLM) | `\| Help the customer with their order` |
| `@variables.` | Reference a variable | `@variables.customer_email` |
| `@actions.` | Reference an action | `@actions.Get_Order` |
| `@subagent.` | Delegate to another subagent | `@subagent.Order_Management` |
| `@outputs.` | Reference action output | `@outputs.order_data` |
| `@utils.` | Utility functions | `@utils.transition to @subagent.FAQ` |
| `{!expr}` | Interpolate in prompts | `{!@variables.name}` |
| `...` | LLM slot-fill token | `with order_id = ...` |
| `#` | Comment | `# This is a comment` |

## Language Reference

### Variable Types

| Type | Notes |
|------|-------|
| `string` | Alphanumeric text |
| `number` | IEEE 754 double-precision (integers and decimals) |
| `boolean` | `True` / `False` (case-sensitive, capitalize first letter) |
| `object` | JSON object — regular variables only, not linked |
| `date` | Any valid date format |
| `id` | Salesforce record ID |
| `list[<type>]` | List of any primitive type — regular variables only, not linked |

Variables come in three kinds: **regular** (mutable, with optional default), **linked** (read-only, tied to an external source like `@session.sessionID`), and **system** (predefined, read-only — currently only `@system_variables.user_input`).

### Action Targets

Actions support several target protocols:

| Target | Format | Example |
|--------|--------|---------|
| Apex | `apex://<class>.<method>` | `apex://c__CustomerActions.getProfile` |
| Flow | `flow://<flow_name>` | `flow://Issue_Refund` |
| Prompt | `prompt://<template_name>` | `prompt://Case_Summary_Template` |

Action parameter types: `string`, `number`, `integer`, `long`, `boolean`, `object`, `date`, `datetime`, `time`, `currency`, `id`, `list[<type>]`.

### Operators

| Category | Operators |
|----------|----------|
| Comparison | `==`, `!=`, `<`, `<=`, `>`, `>=`, `is`, `is not` |
| Logical | `and`, `or`, `not` |
| Arithmetic | `+`, `-` |

Conditionals support `if` and `else` only (no `else if`).

### Utils

| Utility | Purpose |
|---------|---------|
| `@utils.transition to @subagent.<name>` | One-way transfer to another subagent. No return to caller. Discards any accumulated prompt from the source subagent. |
| `@utils.setVariables` | Lets the LLM set variable values from conversation context. Use `...` token for LLM-determined values. |
| `@utils.escalate` | Hands off to a human service rep via Omni-Channel. Requires an active `connection messaging` block. |

### Flow of Control

1. Every utterance starts at the `start_agent` subagent (the router).
2. The router classifies intent and transitions to the appropriate `subagent`.
3. Reasoning instructions are parsed sequentially, top-to-bottom.
4. Logic instructions (`->`) execute deterministically — actions run, variables set, conditions evaluate.
5. Prompt instructions (`|`) are concatenated into a single prompt sent to the LLM.
6. `after_reasoning` runs after the LLM responds.
7. The next utterance returns to `start_agent`.

Transitions via `@utils.transition to` are **one-way** — the caller's context is discarded. Use `@subagent.<name>` as a tool reference for **round-trip** delegation that returns to the caller.

## Keep Going

### [Official Language Reference](<https://developer.salesforce.com/docs/ai/agentforce/guide/ascript-reference.html>)

Full syntax reference, operators, and block types.

### [Patterns Library](<https://developer.salesforce.com/docs/ai/agentforce/guide/ascript-patterns.html>)

10+ battle-tested patterns for common agent workflows.

### [Agent Script Recipes](<https://developer.salesforce.com/sample-apps/agent-script-recipes>)

15 official open-source recipes you can clone and modify.

## Salesforce Development Skills

# Salesforce Development Skills

| Your workflow | Install |
| --- | --- |
| Build Agentforce agents in Claude Code | Agentforce ADLC plugin |
| Build Apex, Flow, and platform metadata in Claude Code | Salesforce Development plugin |
| Build with Codex, Cursor, or another skills-compatible tool | Salesforce Skills Library |

## Claude Code

Run the command for your workflow inside Claude Code. Both plugins are in [Anthropic's official marketplace](https://github.com/anthropics/claude-plugins-official/blob/main/.claude-plugin/marketplace.json).

#### Agentforce ADLC

Author, deploy, test, and improve Agentforce agents. Requires Salesforce CLI, Python 3.9+, and an Agentforce-enabled org.

```text
/plugin install agentforce-adlc@claude-plugins-official
```

Skills: `/agentforce-adlc:agentforce-generate`, `/agentforce-adlc:agentforce-test`, and `/agentforce-adlc:agentforce-observe`. [Setup and updates](https://github.com/SalesforceAIResearch/agentforce-adlc).

#### Salesforce Development

Develop Apex, Flow, metadata, and other platform features. Requires Claude Code 2.1.222+, Node.js LTS, Salesforce CLI, and Python 3.8+.

```text
/plugin install salesforce-development@claude-plugins-official
```

[Setup and other installation options](https://developer.salesforce.com/docs/platform/salesforce-skills-plugins/guide/install-salesforce-development.html).

Run `/plugin` to confirm the plugin is installed and enabled. Restart Claude Code if its skills are missing.

For Agentforce lifecycle work in Claude Code, install Agentforce ADLC. [Migration notes](https://github.com/forcedotcom/sf-skills/blob/c217b703b3e5a3c279f1a510d8703161b14bd0a5/plugins/builder/salesforce-development/CHANGELOG.md#200--2026-08-26).

## Codex, Cursor, and other coding tools

Install portable skills from the [Salesforce Skills Library](https://github.com/forcedotcom/sf-skills):

```bash
npx skills add forcedotcom/sf-skills
```

Select your coding tool and the skills you need. For Agentforce, select `agentforce-generate`, `agentforce-test`, and `agentforce-observe`. **These are included in SF Skills**; you do not need a second installation.

For just the Agentforce lifecycle skills, you can use the [standalone ADLC source](https://github.com/SalesforceAIResearch/agentforce-adlc#installation) instead. ADLC requires Salesforce CLI, Python 3.9+, and an Agentforce-enabled org:

```bash
npx skills add SalesforceAIResearch/agentforce-adlc
```

Restart your coding tool if the skills are missing. These commands install skills only, without the Claude Code plugin's agents, hooks, or MCP settings. Agentforce Vibes manages its Salesforce Skills Library installation automatically.

## ChatGPT

The Salesforce-published [Agentforce Sales app (Beta)](https://help.salesforce.com/s/articleView?id=sales.sales_chatgpt_app_parent.htm&language=en_US&type=5) connects ChatGPT to CRM records and sales workflows. Access requires an eligible Salesforce org and permission from your ChatGPT workspace. Follow the linked setup guide.

## Experience Site

# Experience Site (45 min)

## Quick Deploy an Agent to an Experience Site

> **Quick Deploy (on agentforce.dev)**:
> The live page has an interactive widget that selects your agent, updates the
> MessagingChannel routing to point to it, and returns your Experience Site URL.
> If you are not using the live page, follow the manual SF CLI or REST API
> steps below to configure the same routing.

## Outcome

A working chat widget on your Experience Site where visitors can message your Agentforce Service Agent in real-time.

When complete, you'll have:

- An Experience Cloud site with the Enhanced Chat component
- A Messaging Channel routed directly to your Service Agent
- Verified end-to-end message flow from widget to agent

> **ℹ️ Info**: This guide uses SF CLI commands as the primary method. REST API and browser fallbacks are included for each step.

## Architecture

When a visitor opens the chat widget on your Experience Site, the message flows through a chain of Salesforce objects:

*(Architecture diagram — see the live page for the visual)*

The key routing link is `SessionHandlerId` on the MessagingChannel — it points directly to your agent's `BotDefinition.Id`. When a message arrives, Salesforce routes it to the handler specified by this ID.

### Configure Your Variables

**Variable Reference** — replace these placeholders in commands below:

| Variable | Description | Example |
|----------|-------------|---------|
| `{{ORG_ALIAS}}` | Salesforce org alias or username | `<your-org-alias>` |
| `{{AGENT_NAME}}` | Service Agent developer name | `Reservation_Agent` |
| `{{SITE_NAME}}` | Experience Cloud site name | `AgentforceSite` |
| `{{CHANNEL_NAME}}` | Messaging Channel developer name | `AgentforceChannel` |
| `{{DEPLOYMENT_NAME}}` | Embedded Service Deployment name | `AgentforceDeployment` |
| `{{DOMAIN_PREFIX}}` | Experience Site domain prefix | `mycompany` |
| `{{INSTANCE_URL}}` | Salesforce instance URL | `https://myorg.my.salesforce.com` |
| `{{ACCESS_TOKEN}}` | OAuth access token | `YOUR_ACCESS_TOKEN` |
| `{{CLIENT_ID}}` | OAuth client ID (Consumer Key) | `YOUR_CLIENT_ID` |
| `{{CLIENT_SECRET}}` | OAuth client secret | `YOUR_CLIENT_SECRET` |

## 1. Verify Your Starting Point

Confirm your agent exists and your org is ready.

### Confirm your Service Agent exists and is active

Before building the routing chain, verify your Agentforce Service Agent is deployed and active in the target org.

**Choose one of the following methods:**

#### SF CLI

```bash
sf data query \
  --target-org {{ORG_ALIAS}} \
  --query "SELECT Id, DeveloperName, MasterLabel, AgentType FROM BotDefinition WHERE DeveloperName='{{AGENT_NAME}}' AND IsActive=true"
```

#### REST API

```bash
curl -X GET "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id,DeveloperName,MasterLabel,AgentType+FROM+BotDefinition+WHERE+DeveloperName='{{AGENT_NAME}}'+AND+IsActive=true" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" \
  -H "Content-Type: application/json"
```

#### Browser Agent

```text
1. Navigate to Setup → Agents → Agent Builder
2. Find your agent by name: {{AGENT_NAME}}
3. Verify the status shows Active
4. Copy the Agent ID for later use
```

**Verify the agent is active:**

✅ **Verify:**

Expected result: One record returned with a valid Id.

If no record is returned, activate your agent in Agent Builder before proceeding.

> **⚠️ Warning**: The agent must have `AgentType = 'Bot'` or `AgentType = 'ServiceAgent'`. Other agent types cannot be routed via Enhanced Chat.

### Authenticate and verify org access

Ensure you have admin access to the target org and the CLI is connected.

**Choose one of the following methods:**

#### SF CLI

```bash
sf org login web --alias {{ORG_ALIAS}} --instance-url https://login.salesforce.com
sf org display --target-org {{ORG_ALIAS}} --verbose
```

#### REST API

```bash
# Use OAuth 2.0 web server flow to obtain access token
# Then verify with:
curl -X GET "{{INSTANCE_URL}}/services/data/v64.0/limits" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}"
```

#### Browser Agent

```text
1. Log into your Salesforce org at https://login.salesforce.com
2. Verify you see Setup in the gear menu (admin access required)
3. Navigate to Setup → Company Information to confirm org details
```

**Verify org access:**

✅ **Verify:**

Expected result: Org display shows your username and instance URL without errors.

## 2. Build the Routing Chain

Create the queue, channel, and deployment that route messages to your agent.

### Create a messaging queue for fallback routing

The queue handles messages when the agent is unavailable. Even with direct agent routing, a fallback queue is required.

**Choose one of the following methods:**

#### SF CLI

```bash
# Check if queue exists
sf data query --target-org {{ORG_ALIAS}} \
  --query "SELECT Id FROM Group WHERE Type='Queue' AND DeveloperName='{{CHANNEL_NAME}}_Queue'"

# If not found, create via Metadata API
cat > queue-metadata.xml << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<Queue xmlns="http://soap.sforce.com/2006/04/metadata">
    <fullName>{{CHANNEL_NAME}}_Queue</fullName>
    <name>{{CHANNEL_NAME}} Queue</name>
    <queueSobject>
        <sobjectType>MessagingSession</sobjectType>
    </queueSobject>
</Queue>
EOF

sf project deploy start --target-org {{ORG_ALIAS}} --metadata Queue:{{CHANNEL_NAME}}_Queue
```

#### REST API

```bash
# Create queue
curl -X POST "{{INSTANCE_URL}}/services/data/v64.0/sobjects/Group" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" \
  -H "Content-Type: application/json" \
  -d '{
    "Name": "{{CHANNEL_NAME}} Queue",
    "DeveloperName": "{{CHANNEL_NAME}}_Queue",
    "Type": "Queue"
  }'

# Add MessagingSession to queue
QUEUE_ID=$(curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id+FROM+Group+WHERE+DeveloperName='{{CHANNEL_NAME}}_Queue'" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" | jq -r '.records[0].Id')

curl -X POST "{{INSTANCE_URL}}/services/data/v64.0/sobjects/QueueSobject" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" \
  -H "Content-Type: application/json" \
  -d "{\"QueueId\": \"$QUEUE_ID\", \"SobjectType\": \"MessagingSession\"}"
```

#### Browser Agent

```text
1. Navigate to Setup → Queues
2. Click New
3. Set Label: {{CHANNEL_NAME}} Queue
4. Set Queue Name: {{CHANNEL_NAME}}_Queue
5. In Supported Objects, add Messaging Session
6. Click Save
```

**Verify queue exists:**

✅ **Verify:**

Expected result: One queue record returned.

### Create a Messaging Channel routed to your agent

The Messaging Channel is the key routing component. Setting SessionHandlerId to your agent's BotDefinition.Id enables direct routing.

**Choose one of the following methods:**

#### SF CLI

```bash
# Get the Bot ID and Queue ID
BOT_ID=$(sf data query --target-org {{ORG_ALIAS}} --json \
  --query "SELECT Id FROM BotDefinition WHERE DeveloperName='{{AGENT_NAME}}'" \
  | jq -r '.result.records[0].Id')

QUEUE_ID=$(sf data query --target-org {{ORG_ALIAS}} --json \
  --query "SELECT Id FROM Group WHERE Type='Queue' AND DeveloperName='{{CHANNEL_NAME}}_Queue'" \
  | jq -r '.result.records[0].Id')

# Create or update MessagingChannel via Apex
sf apex run --target-org {{ORG_ALIAS}} << EOF
MessagingChannel channel = new MessagingChannel();
channel.DeveloperName = '{{CHANNEL_NAME}}';
channel.MasterLabel = '{{CHANNEL_NAME}}';
channel.MessageType = 'EmbeddedMessaging';
channel.MessagingPlatformKey = '{{CHANNEL_NAME}}-' + String.valueOf(Crypto.getRandomLong());
channel.IsActive = true;
channel.SessionHandlerId = '$BOT_ID';
channel.FallbackQueueId = '$QUEUE_ID';
insert channel;
System.debug('Created MessagingChannel: ' + channel.Id);
EOF
```

#### REST API

```bash
BOT_ID=$(curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id+FROM+BotDefinition+WHERE+DeveloperName='{{AGENT_NAME}}'" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" | jq -r '.records[0].Id')

QUEUE_ID=$(curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id+FROM+Group+WHERE+Type='Queue'+AND+DeveloperName='{{CHANNEL_NAME}}_Queue'" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" | jq -r '.records[0].Id')

curl -X POST "{{INSTANCE_URL}}/services/data/v64.0/sobjects/MessagingChannel" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" \
  -H "Content-Type: application/json" \
  -d "{
    \"DeveloperName\": \"{{CHANNEL_NAME}}\",
    \"MasterLabel\": \"{{CHANNEL_NAME}}\",
    \"MessageType\": \"EmbeddedMessaging\",
    \"MessagingPlatformKey\": \"{{CHANNEL_NAME}}-$(date +%s)\",
    \"IsActive\": true,
    \"SessionHandlerId\": \"$BOT_ID\",
    \"FallbackQueueId\": \"$QUEUE_ID\"
  }"
```

#### Browser Agent

```text
1. Navigate to Setup → Messaging Settings
2. Click New Channel
3. Select Enhanced Chat as the type
4. Set Channel Name: {{CHANNEL_NAME}}
5. Under Routing, select Agent and choose your agent: {{AGENT_NAME}}
6. Under Fallback Queue, select {{CHANNEL_NAME}} Queue
7. Click Activate, then Save
```

**Verify channel configuration:**

✅ **Verify:**

Expected result: One record with IsActive=true and SessionHandlerId matching your BotDefinition.Id.

> **💡 Tip**: If `SessionHandlerId` is not available in your org's API version, you'll need to configure routing through a Flow instead. See the Troubleshooting section.

### Create an Embedded Service Deployment

The deployment packages the channel configuration and generates the code snippet for your site.

**Choose one of the following methods:**

#### SF CLI

```bash
# Create EmbeddedServiceConfig via Tooling API
cat > esd-metadata.json << 'EOF'
{
  "FullName": "{{DEPLOYMENT_NAME}}",
  "Metadata": {
    "masterLabel": "{{DEPLOYMENT_NAME}}",
    "deploymentFeature": "EmbeddedMessaging",
    "deploymentType": "Web",
    "isEnabled": true,
    "embeddedServiceMessagingChannel": {
      "isEnabled": true,
      "messagingChannel": "{{CHANNEL_NAME}}"
    }
  }
}
EOF

sf data create record --target-org {{ORG_ALIAS}} --use-tooling-api \
  --sobject EmbeddedServiceConfig --values "$(cat esd-metadata.json)"
```

#### REST API

```bash
curl -X POST "{{INSTANCE_URL}}/services/data/v64.0/tooling/sobjects/EmbeddedServiceConfig" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" \
  -H "Content-Type: application/json" \
  -d '{
    "FullName": "{{DEPLOYMENT_NAME}}",
    "Metadata": {
      "masterLabel": "{{DEPLOYMENT_NAME}}",
      "deploymentFeature": "EmbeddedMessaging",
      "deploymentType": "Web",
      "isEnabled": true,
      "embeddedServiceMessagingChannel": {
        "isEnabled": true,
        "messagingChannel": "{{CHANNEL_NAME}}"
      }
    }
  }'
```

#### Browser Agent

```text
1. Navigate to Setup → Embedded Service Deployments
2. Click New Deployment
3. Select Enhanced Chat
4. Set Deployment Name: {{DEPLOYMENT_NAME}}
5. Select your Messaging Channel: {{CHANNEL_NAME}}
6. Click Save
7. Click Publish to activate the deployment
```

**Verify deployment exists:**

✅ **Verify:**

Expected result: One deployment record returned.

## 3. Create the Experience Site

Stand up the site and add the chat widget component.

### Create an Experience Cloud site

The Experience Site hosts your public-facing pages. We'll use the Customer Service template which includes Enhanced Chat support.

**Choose one of the following methods:**

#### SF CLI

```bash
sf community create \
  --target-org {{ORG_ALIAS}} \
  --name "{{SITE_NAME}}" \
  --template-name "Customer Service" \
  --url-path-prefix "{{DOMAIN_PREFIX}}"
```

#### REST API

```bash
# Experience Sites are typically created via Metadata API
# Use sf CLI or browser for initial creation
```

#### Browser Agent

```text
1. Navigate to Setup → Digital Experiences → All Sites
2. Click New
3. Select Customer Service template
4. Set Name: {{SITE_NAME}}
5. Set URL: {{DOMAIN_PREFIX}}
6. Click Create
```

**Verify site exists:**

✅ **Verify:**

Expected result: One site record returned.

### Add the Enhanced Chat component to your site

The component renders the chat widget. It connects to your deployment configuration.

**Choose one of the following methods:**

#### SF CLI

```bash
# Retrieve the site bundle
sf project retrieve start \
  --target-org {{ORG_ALIAS}} \
  --metadata "DigitalExperienceBundle:site/{{SITE_NAME}}" \
  --target-metadata-dir ./site-bundle

# The component must be added via Experience Builder (browser)
# See Browser method below
```

#### REST API

```bash
# Component placement requires Experience Builder
# See Browser method below
```

#### Browser Agent

```text
1. Navigate to Setup → Digital Experiences → All Sites
2. Click Builder next to {{SITE_NAME}}
3. Select the Home page from the page dropdown
4. From the Components panel, drag Enhanced Chat to the footer region
5. In the component properties:
   - Set Deployment: {{DEPLOYMENT_NAME}}
   - Leave other settings as default
6. Click Publish in the top right
7. Confirm and publish
```

**Verify component placement:**

✅ **Verify:**

Expected result: The Enhanced Chat component appears in Experience Builder on the Home page.

### Publish the Experience Site

Publishing makes the site accessible at its public URL.

**Choose one of the following methods:**

#### SF CLI

```bash
sf community publish \
  --target-org {{ORG_ALIAS}} \
  --name "{{SITE_NAME}}"
```

#### REST API

```bash
# Publish via Connect API
NETWORK_ID=$(curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id+FROM+Network+WHERE+Name='{{SITE_NAME}}'" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" | jq -r '.records[0].Id')

curl -X POST "{{INSTANCE_URL}}/services/data/v64.0/connect/communities/$NETWORK_ID/publish" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" \
  -H "Content-Type: application/json"
```

#### Browser Agent

```text
1. In Experience Builder, click Publish in the top right
2. Click Publish again in the confirmation dialog
3. Wait for the publish process to complete
```

**Verify site is published:**

✅ **Verify:**

Expected result: Status = 'Live'.

## 4. Deploy & Verify

Test the full flow from widget to agent.

### Get your site URL

Find the public URL where your chat widget is live.

**Choose one of the following methods:**

#### SF CLI

```bash
sf org display --target-org {{ORG_ALIAS}} --verbose --json | jq -r '.result.instanceUrl' | \
  sed 's/\.my\.salesforce\./.my.site./'
# Append: /{{DOMAIN_PREFIX}}
```

#### REST API

```bash
# Get domain from org info
curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Domain+FROM+Domain+WHERE+DomainType='DNS'" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}"
# Site URL format: https://{domain}.my.site.com/{{DOMAIN_PREFIX}}
```

#### Browser Agent

```text
1. Navigate to Setup → Digital Experiences → All Sites
2. Click on {{SITE_NAME}}
3. Copy the Site URL shown in the details
```

**Verify site URL:**

✅ **Verify:**

Expected result: You have a URL like https://yourorg.my.site.com/{{DOMAIN_PREFIX}}.

### Test the chat widget

Open your site and verify the widget appears and can send messages.

**Choose one of the following methods:**

#### SF CLI

```bash
# Open the site in your browser
open "https://$(sf org display --target-org {{ORG_ALIAS}} --json | jq -r '.result.instanceUrl' | sed 's/https:\/\///' | sed 's/\.my\.salesforce\./.my.site./').com/{{DOMAIN_PREFIX}}"
```

#### REST API

```bash
# Manual browser test required - see Browser method
```

#### Browser Agent

```text
1. Open your site URL in a new browser tab (incognito recommended)
2. Look for the chat widget icon (usually bottom-right corner)
3. Click to open the chat window
4. Type a test message: "Hello, I need help"
5. Send the message
6. Wait for the agent to respond
```

**Verify chat functionality:**

✅ **Verify:**

Expected result: Chat widget visible, message sends successfully, agent responds.

### Verify message routing to agent

Confirm messages are being routed to your agent by checking MessagingSession and AgentWork records.

**Choose one of the following methods:**

#### SF CLI

```bash
# Check recent MessagingSessions
sf data query --target-org {{ORG_ALIAS}} \
  --query "SELECT Id, Status, AgentType, OwnerId, CreatedDate FROM MessagingSession ORDER BY CreatedDate DESC LIMIT 5"

# Check AgentWork records
sf data query --target-org {{ORG_ALIAS}} \
  --query "SELECT Id, WorkItemId, BotId, RoutingType, CreatedDate FROM AgentWork ORDER BY CreatedDate DESC LIMIT 5"
```

#### REST API

```bash
curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id,Status,AgentType,OwnerId,CreatedDate+FROM+MessagingSession+ORDER+BY+CreatedDate+DESC+LIMIT+5" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}"

curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id,WorkItemId,BotId,RoutingType,CreatedDate+FROM+AgentWork+ORDER+BY+CreatedDate+DESC+LIMIT+5" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}"
```

#### Browser Agent

```text
1. Navigate to Setup → Object Manager → Messaging Session
2. Click Recent Messaging Sessions tab
3. Look for a session created in the last few minutes
4. Click to view details
5. Verify Owner matches your agent
```

**Verify messaging session:**

✅ **Verify:**

Expected result: Recent MessagingSession exists with Status = 'Active' or 'Ended'.

## Troubleshooting

### Widget doesn't appear on site

1. Verify the Embedded Service Deployment is published
2. Check that the Enhanced Chat component is on the Home page
3. Republish the Experience Site after adding the component
4. Clear browser cache and try incognito mode

### Messages not reaching agent

1. Verify `SessionHandlerId` is set on the MessagingChannel
2. Confirm the agent is Active (not Draft)
3. Check that the Messaging Channel is Active
4. Verify the fallback queue is properly configured

### SessionHandlerId field not available

Some org configurations don't expose this field via API. Options:

1. Configure routing via Omni-Channel Flow instead
2. Use Setup UI to configure direct agent routing
3. Contact Salesforce support for API access

### Site publish fails

1. Check for unpublished components or missing required fields
2. Review the publish error in Setup → Digital Experiences
3. Ensure you have admin permissions

## Definition of Done

**Definition of Done:**

✅ **Verify:**

- Chat widget launcher visible on Experience Site
- User can open chat and send a message
- Agent responds to the message
- MessagingSession record created with correct routing
- AgentWork record shows message assigned to agent

## Deploy

# Deploy

Once your agent is built and tested, deploy it to the channels your customers already use. Each guide walks through end-to-end setup: configuration, routing, and verification.

## Custom Client

Interact with your agent programmatically from any application.

### [Agent API](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/docs/agent-api>)

Control your agent over REST. Build custom integrations, run automated tests, or embed agent conversations in any app.

## Web

Embed a chat widget on your website or Experience Cloud site.

### [Experience Site](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/docs/salesforce-site>)

Enhanced Chat widget on an Experience Cloud site.

### [Enhanced Chat](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/docs/salesforce-chat>)

Enhanced Chat widget on any external website. No Experience Cloud required.

## Messaging Channels

Connect your agent to the platforms your customers already message on.

### [WhatsApp](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/docs/whatsapp-setup>)

Connect WhatsApp Business via Meta Cloud API and route messages to your agent.

### [SMS](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/docs/sms-setup>)

Provision a phone number and route text messages to your agent.

### [Messenger](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/docs/messenger-setup>)

Connect your Facebook Page and route Messenger conversations to your agent.

### [Apple Messages](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/docs/apple-messages-setup>)

Route iMessage conversations from Apple Maps, Safari, and Spotlight to your agent.

### [LINE](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/docs/line-setup>)

Connect a LINE Official Account and route messages to your agent.

### [Custom Channel (BYOC)](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/docs/byoc-setup>)

Bring any messaging platform via managed package and the Interaction Service API.

## Enhanced Chat

# Enhanced Chat (30 min)

## Quick Setup: Generate Embed Snippet

Select an agent, enter your website domains, and generate the JavaScript embed snippet. Domains are whitelisted automatically.

> **Quick Setup (on agentforce.dev)**:
> The live page has an interactive widget that auto-generates the embed snippet.
> It selects your agent, whitelists your domains, configures the MessagingChannel,
> and outputs a ready-to-paste `<script>` tag. If you are not using the live page,
> follow the manual SF CLI or REST API steps below to do the same thing.

## Outcome

A working Enhanced Chat widget on your external website that routes conversations to your Agentforce Service Agent. No Experience Cloud site required.

When complete, you'll have:

- A MessagingChannel (type `EmbeddedMessaging`) routed to your Agentforce agent
- An EmbeddedServiceConfig (deployment) configured for external web delivery
- A JavaScript embed snippet added to your website's HTML
- Verified end-to-end message flow from the chat widget to your agent and back

> **ℹ️ Info**: This guide deploys Enhanced Chat on **any external website** using a JavaScript snippet. If you want to deploy on an **Experience Cloud site** instead, see [Experience Site](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/docs/salesforce-site>) which places the widget as a Lightning component inside Experience Builder.

## Architecture

When a visitor clicks the chat bubble on your website, the message flows through the following chain:

*(Architecture diagram — see the live page for the visual)*

The key metadata components are:

- **MessagingChannel**: `messagingChannelType = EmbeddedMessaging`, with `sessionHandlerType = AgentforceServiceAgent` and `sessionHandlerAsa` pointing to the BotDefinition DeveloperName
- **EmbeddedServiceConfig**: `deploymentFeature = EmbeddedMessaging`, `deploymentType = Web`, `clientVersion = WebV2`
- **ServiceChannel**: `sfdc_livemessage` (auto-created with Messaging)
- **Queue**: SObject type `MessagingSession` for fallback routing

The JavaScript snippet loads asynchronously, connects to your org's SCRT2 endpoint, and renders a chat bubble in the bottom-right corner of the page. Both authenticated and guest (unauthenticated) users are supported.

> **💡 Tip**: **No Digital Engagement license needed.** Enhanced Chat is included with Service Cloud. Unlike WhatsApp, SMS, or other third-party channels, no additional add-on license is required.

### Configure Your Variables

**Variable Reference** — replace these placeholders in commands below:

| Variable | Description | Example |
|----------|-------------|---------|
| `{{ORG_ALIAS}}` | Salesforce org alias or username | `<your-org-alias>` |
| `{{AGENT_NAME}}` | Service Agent developer name | `Reservation_Agent` |
| `{{CHANNEL_NAME}}` | Messaging Channel developer name | `AgentforceChannel` |
| `{{DEPLOYMENT_NAME}}` | Embedded Service Deployment name | `AgentforceDeployment` |
| `{{INSTANCE_URL}}` | Salesforce instance URL | `https://myorg.my.salesforce.com` |
| `{{ACCESS_TOKEN}}` | OAuth access token | `YOUR_ACCESS_TOKEN` |

## Implementation Steps

## 1. Verify Your Starting Point

Confirm your agent exists, your org is authenticated, and Messaging is enabled.

### Confirm your Service Agent exists and is active

Before building the routing chain, verify your Agentforce Service Agent is deployed and active in the target org.

**Choose one of the following methods:**

#### SF CLI

```bash
sf data query \
  --target-org {{ORG_ALIAS}} \
  --query "SELECT Id, DeveloperName, MasterLabel, AgentType FROM BotDefinition WHERE DeveloperName='{{AGENT_NAME}}' AND IsActive=true"
```

#### REST API

```bash
curl -X GET "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id,DeveloperName,MasterLabel,AgentType+FROM+BotDefinition+WHERE+DeveloperName='{{AGENT_NAME}}'+AND+IsActive=true" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" \
  -H "Content-Type: application/json"
```

#### Browser Agent

```text
1. Navigate to Setup -> Agents -> Agent Builder
2. Find your agent by name: {{AGENT_NAME}}
3. Verify the status shows Active
4. Copy the Agent ID for later use
```

**Verify the agent is active:**

✅ **Verify:**

Expected result: One record returned with a valid Id.

If no record is returned, activate your agent in Agent Builder before proceeding.

### Authenticate and verify org access

Ensure you have admin access to the target org and the CLI is connected.

**Choose one of the following methods:**

#### SF CLI

```bash
sf org login web --alias {{ORG_ALIAS}} --instance-url https://login.salesforce.com
sf org display --target-org {{ORG_ALIAS}} --verbose
```

#### REST API

```bash
# Use OAuth 2.0 web server flow to obtain access token
# Then verify with:
curl -X GET "{{INSTANCE_URL}}/services/data/v64.0/limits" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}"
```

#### Browser Agent

```text
1. Log into your Salesforce org at https://login.salesforce.com
2. Verify you see Setup in the gear menu (admin access required)
3. Navigate to Setup -> Company Information to confirm org details
```

**Verify org access:**

✅ **Verify:**

Expected result: Org display shows your username and instance URL without errors.

## 2. Enable Messaging

Messaging and Omni-Channel must be enabled at the org level before any messaging channels can be created.

**Choose one of the following methods:**

#### SF CLI

```bash
# Deploy Omni-Channel settings via metadata
mkdir -p /tmp/sf-messaging-setup/force-app/main/default/settings

cat > /tmp/sf-messaging-setup/sfdx-project.json << 'EOF'
{
  "packageDirectories": [{ "path": "force-app", "default": true }],
  "namespace": "",
  "sourceApiVersion": "64.0"
}
EOF

cat > /tmp/sf-messaging-setup/force-app/main/default/settings/OmniChannel.settings-meta.xml << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<OmniChannelSettings xmlns="http://soap.sforce.com/2006/04/metadata">
    <enableOmniChannel>true</enableOmniChannel>
</OmniChannelSettings>
EOF

sf project deploy start \
  --target-org {{ORG_ALIAS}} \
  --source-dir /tmp/sf-messaging-setup/force-app/main/default/settings/OmniChannel.settings-meta.xml
```

#### REST API

```bash
# Messaging and Omni-Channel settings are typically enabled via
# Metadata API or Setup UI. See Browser method.
```

#### Browser Agent

```text
1. Navigate to Setup -> Messaging Settings
2. If you see a toggle for Messaging, enable it
3. Navigate to Setup -> Omni-Channel Settings
4. Check "Enable Omni-Channel"
5. Click Save
```

**Verify messaging is enabled:**

✅ **Verify:**

Expected result: Messaging Settings page loads without errors and shows channel management options. Omni-Channel Settings shows "Omni-Channel is enabled."

## 3. Create the Messaging Channel

Create an `EmbeddedMessaging` type MessagingChannel and route it to your Agentforce Service Agent. This also creates the required Queue with `MessagingSession` as the supported SObject.

**Choose one of the following methods:**

#### SF CLI

```bash
# Step 3a: Create a Queue for MessagingSession fallback routing
sf data query \
  --target-org {{ORG_ALIAS}} \
  --query "SELECT Id, DeveloperName FROM Group WHERE Type='Queue' AND DeveloperName='{{CHANNEL_NAME}}_Queue' LIMIT 1"

# If no queue exists, create one:
sf data create record \
  --target-org {{ORG_ALIAS}} \
  --sobject Group \
  --values "Name='{{CHANNEL_NAME}} Queue' DeveloperName='{{CHANNEL_NAME}}_Queue' Type='Queue'"

# Get the Queue ID from the output, then map it to MessagingSession:
sf data create record \
  --target-org {{ORG_ALIAS}} \
  --sobject QueueSobject \
  --values "QueueId='QUEUE_ID_FROM_ABOVE' SobjectType='MessagingSession'"

# Step 3b: Get the BotDefinition Id for your agent
sf data query \
  --target-org {{ORG_ALIAS}} \
  --query "SELECT Id, DeveloperName FROM BotDefinition WHERE DeveloperName='{{AGENT_NAME}}'"

# Step 3c: Create the MessagingChannel with direct agent routing
# Note: Use the Python/REST approach below for creating MessagingChannel,
# as sf data create record does not support all required fields.
```

#### REST API

```bash
# Step 3a: Create Queue
curl -X POST "{{INSTANCE_URL}}/services/data/v64.0/sobjects/Group" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" \
  -H "Content-Type: application/json" \
  -d '{
    "Name": "{{CHANNEL_NAME}} Queue",
    "DeveloperName": "{{CHANNEL_NAME}}_Queue",
    "Type": "Queue"
  }'

# Map Queue to MessagingSession (replace QUEUE_ID with the Id from above)
curl -X POST "{{INSTANCE_URL}}/services/data/v64.0/sobjects/QueueSobject" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" \
  -H "Content-Type: application/json" \
  -d '{
    "QueueId": "QUEUE_ID",
    "SobjectType": "MessagingSession"
  }'

# Step 3b: Get BotDefinition Id
curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id,DeveloperName+FROM+BotDefinition+WHERE+DeveloperName='{{AGENT_NAME}}'" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}"

# Step 3c: Create MessagingChannel with direct routing
# Replace BOT_ID and QUEUE_ID with values from previous steps
curl -X POST "{{INSTANCE_URL}}/services/data/v64.0/sobjects/MessagingChannel" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" \
  -H "Content-Type: application/json" \
  -d '{
    "DeveloperName": "{{CHANNEL_NAME}}",
    "MasterLabel": "{{CHANNEL_NAME}}",
    "MessageType": "EmbeddedMessaging",
    "MessagingPlatformKey": "'"$(python3 -c 'import uuid; print(uuid.uuid4())')"'",
    "IsActive": true,
    "SessionHandlerId": "BOT_ID",
    "FallbackQueueId": "QUEUE_ID"
  }'
```

#### Browser Agent

```text
1. Navigate to Setup -> Messaging Settings
2. Click "New Channel"
3. Select "Enhanced Chat" as the channel type
4. Enter a channel name: {{CHANNEL_NAME}}
5. Under Routing:
   a. Set "Route to" to your Agentforce Service Agent: {{AGENT_NAME}}
   b. Set a Fallback Queue (create one if needed with SObject = MessagingSession)
6. Activate the channel
7. Click Save
```

**Verify channel configuration:**

✅ **Verify:**

Query the channel to confirm it is active and routed to your agent:

```bash
sf data query --target-org {{ORG_ALIAS}} --query "SELECT Id, DeveloperName, IsActive, MessageType, SessionHandlerId FROM MessagingChannel WHERE DeveloperName='{{CHANNEL_NAME}}'"
```

Expected result: One record returned with `IsActive = true` and `SessionHandlerId` matching your BotDefinition Id.

## 4. Create an Embedded Service Deployment

The EmbeddedServiceConfig is the deployment wrapper that connects the MessagingChannel to the JavaScript snippet. It defines the deployment type as Web and links to your channel.

**Choose one of the following methods:**

#### SF CLI

```bash
# EmbeddedServiceConfig is a Tooling API object.
# Create it via REST/Tooling API (see REST API tab) or Setup UI (see Browser tab).
# SF CLI does not have a direct command for creating EmbeddedServiceConfig.
```

#### REST API

```bash
curl -X POST "{{INSTANCE_URL}}/services/data/v64.0/tooling/sobjects/EmbeddedServiceConfig" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" \
  -H "Content-Type: application/json" \
  -d '{
    "FullName": "{{DEPLOYMENT_NAME}}",
    "Metadata": {
      "masterLabel": "{{DEPLOYMENT_NAME}}",
      "deploymentFeature": "EmbeddedMessaging",
      "deploymentType": "Web",
      "isEnabled": true,
      "isTermsAndConditionsEnabled": false,
      "isTermsAndConditionsRequired": false,
      "shouldHideAuthDialog": false,
      "areGuestUsersAllowed": true,
      "embeddedServiceMessagingChannel": {
        "isEnabled": true,
        "messagingChannel": "{{CHANNEL_NAME}}",
        "shouldShowDeliveryReceipts": true,
        "shouldShowEmojiSelection": true,
        "shouldShowReadReceipts": true,
        "shouldShowTypingIndicators": true,
        "shouldStartNewLineOnEnter": false
      },
      "embeddedServiceCustomComponents": [],
      "embeddedServiceCustomLabels": [],
      "embeddedServiceFlows": [],
      "embeddedServiceLayouts": []
    }
  }'
```

#### Browser Agent

```text
1. Navigate to Setup -> Embedded Service Deployments
2. Click "New Deployment"
3. Select "Enhanced Chat" as the deployment type
4. Enter a name: {{DEPLOYMENT_NAME}}
5. Under Channel, select the MessagingChannel you created: {{CHANNEL_NAME}}
6. Under Settings:
   a. Set Deployment Type to "Web"
   b. Enable "Allow Guest Users" if unauthenticated visitors should be able to chat
7. Click Save
8. Click the "Publish" button to make the deployment active
```

> **⚠️ Warning**: After creating the EmbeddedServiceConfig, you must **publish** it before the snippet will work. In the Setup UI, navigate to the deployment record and click **Publish**. Via API, publishing may require a separate step depending on your org version.

**Verify deployment exists:**

✅ **Verify:**

Confirm the deployment exists:

```bash
sf data query --target-org {{ORG_ALIAS}} --use-tooling-api --query "SELECT Id, DeveloperName, MasterLabel, DeploymentType, DeploymentFeature FROM EmbeddedServiceConfig WHERE DeveloperName='{{DEPLOYMENT_NAME}}'"
```

Expected result: One record returned with `DeploymentFeature = EmbeddedMessaging` and `DeploymentType = Web`.

## 5. Generate the Embed Snippet

After publishing the deployment, Salesforce generates a JavaScript snippet you can paste into any website. The snippet includes your org-specific SCRT2 URL, site endpoint, and deployment name.

**Choose one of the following methods:**

#### SF CLI

```bash
# The embed snippet is available in Setup UI after publishing.
# Retrieve org details to construct the snippet manually:
sf org display --target-org {{ORG_ALIAS}} --verbose --json

# The snippet URLs follow this pattern:
# SCRT2 URL: https://<your-domain>.my.salesforce-scrt.com
# Site Endpoint: https://<your-domain>.my.site.com (or your custom domain)
# ESW file: https://<your-domain>.my.salesforce-scrt.com/assets/js/bootstrap.min.js
```

#### REST API

```bash
# Query for the published deployment snippet components
# Get the org domain:
curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id,Domain+FROM+Domain+WHERE+Type='OrgDomain'" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}"

# The snippet structure uses these values:
# - orgId: Your 15-character Org ID
# - eswConfigDevName: {{DEPLOYMENT_NAME}}
# - baseCoreURL: https://<domain>.my.salesforce.com
# - scrt2URL: https://<domain>.my.salesforce-scrt.com
# - baseLiveAgentContentURL: https://<domain>.my.salesforce-scrt.com/assets
```

#### Browser Agent

```text
1. Navigate to Setup -> Embedded Service Deployments
2. Click on your deployment: {{DEPLOYMENT_NAME}}
3. Look for the "Get Code" or "Code Snippet" section
4. Click "Copy to Clipboard" to get the full JavaScript snippet
5. The snippet will look similar to the example below
```

The generated snippet follows this structure. Replace the placeholder values with your org-specific values:

```html
<script type="text/javascript">
  function initEmbeddedMessaging() {
    try {
      embeddedservice_bootstrap.settings.language = "en_US";
      embeddedservice_bootstrap.init(
        "YOUR_ORG_ID",
        "{{DEPLOYMENT_NAME}}",
        "https://YOUR_DOMAIN.my.site.com/ESWDeploymentName",
        {
          scrt2URL: "https://YOUR_DOMAIN.my.salesforce-scrt.com",
        }
      );
    } catch (err) {
      console.error("Error loading Enhanced Chat: ", err);
    }
  }
</script>
<script
  type="text/javascript"
  src="https://YOUR_DOMAIN.my.salesforce-scrt.com/assets/js/bootstrap.min.js"
  onload="initEmbeddedMessaging()"
></script>
```

> **ℹ️ Info**: The exact snippet is auto-generated by Salesforce and includes your org-specific values. Always use the snippet from Setup rather than constructing it manually, since the URLs and parameters vary by org and environment.

## 6. Add the Snippet to Your Website

Paste the JavaScript snippet into your website's HTML. The snippet should be placed just before the closing `</body>` tag for optimal loading.

**Choose one of the following methods:**

#### SF CLI

```bash
# This step is performed on your external website, not in Salesforce.
# Example: Add to an HTML file

cat >> /path/to/your/website/index.html << 'SNIPPET'

<!-- Salesforce Enhanced Chat Widget -->
<script type='text/javascript'>
  function initEmbeddedMessaging() {
    try {
      embeddedservice_bootstrap.settings.language = 'en_US';
      embeddedservice_bootstrap.init(
        'YOUR_ORG_ID',
        'YOUR_DEPLOYMENT_NAME',
        'https://YOUR_DOMAIN.my.site.com/ESWDeploymentName',
        {
          scrt2URL: 'https://YOUR_DOMAIN.my.salesforce-scrt.com'
        }
      );
    } catch (err) {
      console.error('Error loading Enhanced Chat: ', err);
    }
  }
</script>
<script
  type='text/javascript'
  src='https://YOUR_DOMAIN.my.salesforce-scrt.com/assets/js/bootstrap.min.js'
  onload='initEmbeddedMessaging()'>
</script>
SNIPPET
```

#### REST API

```bash
# This step is performed on your external website, not via Salesforce API.
# Paste the snippet from Step 5 into your site's HTML.
#
# For React/Next.js apps, add the script in your layout component:
# - Use next/script with strategy="lazyOnload"
# - Or add to public/index.html
#
# For static sites:
# - Add before </body> in your HTML template
```

#### Browser Agent

```text
1. Open your website's HTML source (index.html or equivalent)
2. Paste the JavaScript snippet from Step 5 before the closing </body> tag
3. Save and deploy your website
4. For single-page applications (React, Angular, Vue):
   a. Add the bootstrap script to your index.html
   b. Call initEmbeddedMessaging() after the DOM loads
5. For CMS platforms (WordPress, etc.):
   a. Use a custom HTML widget or footer script injection
   b. Paste the snippet in the footer scripts section
```

> **⚠️ Warning**: The domain serving your website must be added to the **CORS allowlist** in Salesforce Setup. Navigate to **Setup -> CORS -> New**, and add your website's origin (e.g., `https://www.example.com`). Without this, the chat widget will fail to connect.

> **💡 Tip**: For development and testing, you can use a simple local HTML file served via `python3 -m http.server 8000` and add `http://localhost:8000` to your CORS allowlist.

## 7. Test and Verify

Open your website, click the chat widget, and send a test message to confirm the full routing chain works.

**Choose one of the following methods:**

#### SF CLI

```bash
# After sending a test message through the chat widget,
# verify a MessagingSession was created:
sf data query \
  --target-org {{ORG_ALIAS}} \
  --query "SELECT Id, Status, AgentType, OwnerId, CreatedDate FROM MessagingSession ORDER BY CreatedDate DESC LIMIT 5"

# Verify AgentWork was created (shows the agent handled the conversation):
sf data query \
  --target-org {{ORG_ALIAS}} \
  --query "SELECT Id, WorkItemId, BotId, RoutingType, CreatedDate FROM AgentWork ORDER BY CreatedDate DESC LIMIT 5"
```

#### REST API

```bash
# Check for recent MessagingSession records
curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id,Status,AgentType,OwnerId,CreatedDate+FROM+MessagingSession+ORDER+BY+CreatedDate+DESC+LIMIT+5" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}"

# Check for AgentWork records
curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id,WorkItemId,BotId,RoutingType,CreatedDate+FROM+AgentWork+ORDER+BY+CreatedDate+DESC+LIMIT+5" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}"
```

#### Browser Agent

```text
1. Open your website in a browser
2. Look for the chat bubble in the bottom-right corner
3. Click the bubble to open the chat window
4. Type a test message (e.g., "Hello, I need help")
5. Verify the agent responds within a few seconds
6. To verify in Salesforce:
   a. Navigate to Setup -> Messaging Sessions
   b. Confirm a new session was created with your test message
   c. Check the Owner is your Agentforce Service Agent
```

**Verify the chat widget works:**

✅ **Verify:**

Expected result: The chat bubble appears on your website. Clicking it opens a chat window. Sending a message produces a response from your Agentforce agent. A `MessagingSession` record exists in Salesforce with `AgentType` indicating bot handling.

## Customization

### Branding

You can customize the chat widget's appearance to match your website's design:

**Choose one of the following methods:**

#### SF CLI

```bash
# Branding is configured on the EmbeddedServiceConfig via Setup UI
# or by updating the deployment metadata. Key options:
# - Primary color, secondary color, contrast colors
# - Chat window header text
# - Avatar image
# - Font family
```

#### REST API

```bash
# Update branding via Tooling API (replace DEPLOYMENT_ID):
curl -X PATCH "{{INSTANCE_URL}}/services/data/v64.0/tooling/sobjects/EmbeddedServiceConfig/DEPLOYMENT_ID" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" \
  -H "Content-Type: application/json" \
  -d '{
    "Metadata": {
      "embeddedServiceBranding": {
        "primaryColor": "#1B5297",
        "secondaryColor": "#FFFFFF",
        "headerBackgroundColor": "#1B5297",
        "font": "Salesforce Sans"
      }
    }
  }'
```

#### Browser Agent

```text
1. Navigate to Setup -> Embedded Service Deployments
2. Click your deployment: {{DEPLOYMENT_NAME}}
3. Click "Branding" or "Customize"
4. Set colors, fonts, header text, and avatar
5. Click Save, then Publish to apply changes
```

### Pre-Chat Forms

Pre-chat forms collect customer information (name, email, case subject) before starting the conversation. This data is available to your agent via the session context.

**Choose one of the following methods:**

#### SF CLI

```bash
# Pre-chat configuration is part of the EmbeddedServiceConfig metadata.
# Configure via Setup UI (see Browser tab).
```

#### REST API

```bash
# Pre-chat forms are configured as part of the EmbeddedServiceConfig.
# See Browser method for the recommended approach.
```

#### Browser Agent

```text
1. Navigate to Setup -> Embedded Service Deployments
2. Click your deployment: {{DEPLOYMENT_NAME}}
3. Navigate to the "Pre-Chat" section
4. Add fields: Name, Email, Subject, or custom fields
5. Mark fields as required or optional
6. Map pre-chat fields to Contact or Case fields for record creation
7. Save and Publish
```

## Troubleshooting

### Chat bubble does not appear on the website

Check these common causes in order:

1. **Snippet not loading**: Open browser DevTools (F12) and check the Console for JavaScript errors. Verify the `bootstrap.min.js` URL is correct and accessible.
2. **CORS error**: Check the Console for CORS-related errors. Add your website's origin to **Setup -> CORS -> Allowed Origins List**.
3. **Deployment not published**: Navigate to **Setup -> Embedded Service Deployments** and verify the deployment status shows as Published.
4. **Incorrect SCRT2 URL**: Verify the `scrt2URL` in your snippet matches your org's domain pattern: `https://<your-domain>.my.salesforce-scrt.com`.

### Chat bubble appears but messages fail to send

This usually indicates a routing issue:

1. Verify the MessagingChannel is active: query `SELECT IsActive FROM MessagingChannel WHERE DeveloperName='{{CHANNEL_NAME}}'`.
2. Verify `SessionHandlerId` is set on the channel and points to a valid BotDefinition.
3. Check that the fallback queue exists and has `MessagingSession` as a supported SObject type.
4. Ensure your Agentforce Service Agent is active and published.

### Agent does not respond to messages

The chat connects but the agent is silent:

1. Check the agent is active: `SELECT Id, IsActive FROM BotDefinition WHERE DeveloperName='{{AGENT_NAME}}'`.
2. Verify a BotVersion is active: `SELECT Id, Status FROM BotVersion WHERE BotDefinition.DeveloperName='{{AGENT_NAME}}' AND Status='Active'`.
3. Check `AgentWork` records to see if the conversation was assigned: `SELECT Id, WorkItemId, BotId FROM AgentWork ORDER BY CreatedDate DESC LIMIT 5`.
4. Review the agent's subagent instructions to ensure it can handle the test utterance.

### Widget loads but shows 'Service is not available'

The deployment is published but the runtime cannot connect:

1. Verify Messaging is enabled: **Setup -> Messaging Settings** should show Messaging as active.
2. Check that the MessagingChannel `IsActive = true`.
3. Ensure Omni-Channel is enabled: **Setup -> Omni-Channel Settings**.
4. If the org was recently provisioned, wait 5-10 minutes for SCRT2 infrastructure to propagate.

## Enhanced Chat v1 vs v2

Salesforce offers two versions of the Enhanced Chat widget:

| Feature       | Enhanced Chat v1 (Standard)            | Enhanced Chat v2 (Custom LWC)                                       |
| ------------- | -------------------------------------- | ------------------------------------------------------------------- |
| Setup         | Standard configuration in Setup        | Requires custom Lightning Web Components                            |
| Customization | Branding colors, fonts, pre-chat forms | Full custom UI via LWC (custom message types, interactive elements) |
| Use case      | Standard chat with agent responses     | Rich interactive experiences (carousels, forms, custom cards)       |
| Requirement   | Service Cloud                          | Service Cloud + custom LWC development                              |

This guide covers Enhanced Chat v1 (standard). For Enhanced Chat v2 with custom Lightning types, see the [Salesforce Web Developer Guide](https://developer.salesforce.com/docs/service/messaging-in-app-web/overview) and the [In-App Developer Guide](https://developer.salesforce.com/docs/service/messaging-in-app-web/overview).

> **ℹ️ Info**: Enhanced Chat also supports **mobile apps** via iOS and Android SDKs. The same MessagingChannel and EmbeddedServiceConfig are reused; only the client-side integration differs. See the Salesforce Mobile SDK documentation for details.

## Definition of Done

**Definition of Done:**

✅ **Verify:**

- MessagingChannel is active with `MessageType = EmbeddedMessaging` and `SessionHandlerId` pointing to your BotDefinition.
- EmbeddedServiceConfig exists with `DeploymentFeature = EmbeddedMessaging` and `DeploymentType = Web`, and is published.
- JavaScript snippet is added to your external website's HTML.
- Your website's origin is added to the Salesforce CORS allowlist.
- Chat bubble appears on the website and opens a chat window on click.
- Sending a test message produces a response from the Agentforce agent.
- `MessagingSession` and `AgentWork` records confirm the routing path works.

## WhatsApp Setup

# WhatsApp Setup (30 min)

## Outcome

A working WhatsApp-to-Agentforce pipeline where customers can message your business number and receive automated responses from your Service Agent.

When complete, you'll have:

- A WhatsApp Messaging Channel connected to your Salesforce org via Meta Cloud API
- Omni-Channel routing configured to send inbound messages to your Agentforce agent
- Verified end-to-end message flow from WhatsApp to agent and back

> **ℹ️ Info**: WhatsApp channels require the **Digital Engagement** add-on license and a **Meta Business Manager** account with a verified WhatsApp Business phone number. Unlike Enhanced Chat, no Experience Cloud site or EmbeddedServiceConfig is needed.

## Architecture

When a customer sends a WhatsApp message to your business number, the message flows through the following chain:

*(Architecture diagram — see the live page for the visual)*

The key routing field is `sessionHandlerType = AgentforceServiceAgent` on the MessagingChannel, with `sessionHandlerAsa` pointing to the BotDefinition DeveloperName. The `sessionHandlerQueue` field specifies the fallback queue when the agent is unavailable.

Unlike Enhanced Chat, WhatsApp does not require an EmbeddedServiceConfig or an Experience Cloud site. The channel is a direct integration between Meta's Cloud API and Salesforce's Messaging infrastructure.

### Configure Your Variables

**Variable Reference** — replace these placeholders in commands below:

| Variable | Description | Example |
|----------|-------------|---------|
| `{{ORG_ALIAS}}` | Salesforce org alias or username | `<your-org-alias>` |
| `{{AGENT_NAME}}` | Service Agent developer name | `Reservation_Agent` |
| `{{CHANNEL_NAME}}` | Messaging Channel developer name | `AgentforceChannel` |
| `{{INSTANCE_URL}}` | Salesforce instance URL | `https://myorg.my.salesforce.com` |
| `{{ACCESS_TOKEN}}` | OAuth access token | `YOUR_ACCESS_TOKEN` |

## Implementation Steps

## 1. Verify Your Starting Point

Confirm your agent exists, your org has the required licenses, and you have admin access.

### Confirm your Service Agent exists and is active

Before building the routing chain, verify your Agentforce Service Agent is deployed and active in the target org.

**Choose one of the following methods:**

#### SF CLI

```bash
sf data query \
  --target-org {{ORG_ALIAS}} \
  --query "SELECT Id, DeveloperName, MasterLabel, AgentType FROM BotDefinition WHERE DeveloperName='{{AGENT_NAME}}' AND IsActive=true"
```

#### REST API

```bash
curl -X GET "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id,DeveloperName,MasterLabel,AgentType+FROM+BotDefinition+WHERE+DeveloperName='{{AGENT_NAME}}'+AND+IsActive=true" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" \
  -H "Content-Type: application/json"
```

#### Browser Agent

```text
1. Navigate to Setup → Agents → Agent Builder
2. Find your agent by name: {{AGENT_NAME}}
3. Verify the status shows Active
4. Copy the Agent ID for later use
```

**Verify the agent is active:**

✅ **Verify:**

Expected result: One record returned with a valid Id.

If no record is returned, activate your agent in Agent Builder before proceeding.

### Authenticate and verify org access

Ensure you have admin access to the target org and the CLI is connected.

**Choose one of the following methods:**

#### SF CLI

```bash
sf org login web --alias {{ORG_ALIAS}} --instance-url https://login.salesforce.com
sf org display --target-org {{ORG_ALIAS}} --verbose
```

#### REST API

```bash
# Use OAuth 2.0 web server flow to obtain access token
# Then verify with:
curl -X GET "{{INSTANCE_URL}}/services/data/v64.0/limits" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}"
```

#### Browser Agent

```text
1. Log into your Salesforce org at https://login.salesforce.com
2. Verify you see Setup in the gear menu (admin access required)
3. Navigate to Setup → Company Information to confirm org details
```

**Verify org access:**

✅ **Verify:**

Expected result: Org display shows your username and instance URL without errors.

### Verify Digital Engagement license

WhatsApp channels require the Digital Engagement add-on. Confirm it is provisioned in your org.

**Choose one of the following methods:**

#### SF CLI

```bash
sf data query \
  --target-org {{ORG_ALIAS}} \
  --query "SELECT Id, Name, Status, IsProvisioned FROM PackageLicense WHERE NamespacePrefix='sfdc_livemessage'"
```

#### REST API

```bash
curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id,Name,Status,IsProvisioned+FROM+PackageLicense+WHERE+NamespacePrefix='sfdc_livemessage'" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}"
```

#### Browser Agent

```text
1. Navigate to Setup → Company Information → Permission Set Licenses
2. Look for "Digital Engagement" or "Messaging" in the list
3. Confirm it shows as Active with available licenses
```

**Verify Digital Engagement license:**

✅ **Verify:**

Expected result: One record returned with IsProvisioned = true.

If not found, contact your Salesforce account executive to provision Digital Engagement.

## 2. Enable Messaging and Omni-Channel

Enable the org-level settings required for WhatsApp messaging.

### Enable Messaging in Setup

Messaging must be enabled at the org level before any messaging channels can be created.

**Choose one of the following methods:**

#### SF CLI

```bash
# Deploy Messaging settings via metadata
mkdir -p force-app/main/default/settings

cat > force-app/main/default/settings/OmniChannel.settings-meta.xml << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<OmniChannelSettings xmlns="http://soap.sforce.com/2006/04/metadata">
    <enableOmniChannel>true</enableOmniChannel>
</OmniChannelSettings>
EOF

sf project deploy start \
  --target-org {{ORG_ALIAS}} \
  --source-dir force-app/main/default/settings/OmniChannel.settings-meta.xml
```

#### REST API

```bash
# Messaging and Omni-Channel settings are typically enabled via
# Metadata API or Setup UI. See Browser method.
```

#### Browser Agent

```text
1. Navigate to Setup → Messaging Settings
2. If you see a toggle for Messaging, enable it
3. Navigate to Setup → Omni-Channel Settings
4. Check "Enable Omni-Channel"
5. Click Save
```

**Verify messaging is enabled:**

✅ **Verify:**

Expected result: Messaging Settings page loads without errors and shows channel management options. Omni-Channel Settings shows "Omni-Channel is enabled."

## 3. Set Up Meta Business and WhatsApp

Connect your Meta Business account to Salesforce and register a WhatsApp Business phone number.

> **⚠️ Warning**: The phone number you use must NOT already be registered with WhatsApp or WhatsApp Business App. It will be migrated to the WhatsApp Business Platform during setup. If the number is currently in use with WhatsApp, you must deregister it first by deleting the WhatsApp app from that phone.

### Prepare your Meta Business Manager account

Before creating the WhatsApp channel in Salesforce, ensure your Meta Business environment is ready.

**Choose one of the following methods:**

#### SF CLI

```bash
# Meta Business setup is UI-driven (OAuth flow between Salesforce and Meta)
# No CLI equivalent. See Browser method for complete steps.
```

#### REST API

```bash
# Meta Business setup requires OAuth flow in browser.
# See Browser method for complete steps.
```

#### Browser Agent

```text
1. Go to https://business.facebook.com and sign in
2. If you don't have a Business Manager account, click Create Account
3. Navigate to Business Settings → Accounts → WhatsApp Accounts
4. If no WhatsApp Business Account (WABA) exists, create one:
   a. Click Add → Create a WhatsApp Account
   b. Enter your business name and select a timezone
   c. Click Create
5. Note your WABA ID (visible in the URL or account details)
6. Navigate to Meta Business Settings → Business Verification
7. Verify your business if not already verified
   - Upload business documents as requested
   - Verification may take 1-5 business days for production use
   - Test numbers with limited messaging are available before verification
```

> **ℹ️ Info**: Meta Business verification is required for production use (sending messages to customers). For development and testing, you can use a test phone number with limited messaging capabilities before completing verification.

**Verify Meta Business Manager access:**

✅ **Verify:**

Expected result: You have a Meta Business Manager account with a WhatsApp Business Account (WABA) and either a verified business or a test phone number ready.

### Prepare a phone number for WhatsApp

You need a phone number that can receive SMS or voice calls for verification. This number will become your WhatsApp Business number.

**Choose one of the following methods:**

#### SF CLI

```bash
# Phone number preparation is done in Meta Business Manager.
# See Browser method.
```

#### REST API

```bash
# Phone number preparation is done in Meta Business Manager.
# See Browser method.
```

#### Browser Agent

```text
1. Ensure you have a phone number that:
   - Can receive SMS or voice calls (for verification code)
   - Is NOT registered with WhatsApp or WhatsApp Business App
   - Is a valid phone number with country code
2. If the number is currently on WhatsApp:
   a. Open WhatsApp on the device with that number
   b. Go to Settings → Account → Delete My Account
   c. Confirm deletion
   d. Wait a few minutes before proceeding
3. Keep the phone accessible during the next step for verification
```

**Verify phone number availability:**

✅ **Verify:**

Expected result: You have an available phone number ready for WhatsApp Business registration.

## 4. Create the WhatsApp Messaging Channel

Use the Salesforce Setup wizard to create the WhatsApp channel. This wizard handles the OAuth connection to Meta and webhook registration automatically.

### Create the channel via Setup wizard

The WhatsApp channel creation in Salesforce uses an embedded Meta OAuth flow. It registers webhooks, connects your WABA, and creates the MessagingChannel record.

**Choose one of the following methods:**

#### SF CLI

```bash
# The WhatsApp channel must be created via the Setup wizard
# (OAuth flow between Salesforce and Meta cannot be done via CLI)
# After creation, verify with:
sf data query \
  --target-org {{ORG_ALIAS}} \
  --query "SELECT Id, DeveloperName, MasterLabel, MessageType, IsActive, MessagingPlatformKey FROM MessagingChannel WHERE MessageType='WhatsApp'"
```

#### REST API

```bash
# WhatsApp channel creation requires the Setup wizard (OAuth flow).
# After creation, verify with:
curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id,DeveloperName,MasterLabel,MessageType,IsActive,MessagingPlatformKey+FROM+MessagingChannel+WHERE+MessageType='WhatsApp'" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}"
```

#### Browser Agent

```text
1. Navigate to Setup → Messaging Settings
2. Click New Channel
3. Select WhatsApp as the channel type
4. Click Start to begin the Meta OAuth flow
5. Sign in to your Meta Business account when prompted
6. Select your WhatsApp Business Account (WABA)
7. Select or add the phone number to use
8. Verify the phone number via SMS or voice call code
9. Set the Channel Name: {{CHANNEL_NAME}}
10. Review the configuration summary
11. Click Finish to create the channel
12. The channel will show as Active in Messaging Settings
```

**Verify channel was created:**

✅ **Verify:**

Expected result: Query returns one record with MessageType = 'WhatsApp' and IsActive = true.

### Verify channel details and note key fields

After creation, confirm the channel is properly configured with the correct platform key and message type.

**Choose one of the following methods:**

#### SF CLI

```bash
sf data query \
  --target-org {{ORG_ALIAS}} \
  --query "SELECT Id, DeveloperName, MasterLabel, MessageType, IsActive, MessagingPlatformKey, SessionHandlerId, FallbackQueueId FROM MessagingChannel WHERE DeveloperName='{{CHANNEL_NAME}}'"
```

#### REST API

```bash
curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id,DeveloperName,MasterLabel,MessageType,IsActive,MessagingPlatformKey,SessionHandlerId,FallbackQueueId+FROM+MessagingChannel+WHERE+DeveloperName='{{CHANNEL_NAME}}'" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}"
```

#### Browser Agent

```text
1. Navigate to Setup → Messaging Settings
2. Click on your WhatsApp channel: {{CHANNEL_NAME}}
3. Note the following fields:
   - Channel Name / DeveloperName
   - Message Type (should be WhatsApp)
   - Platform Key (your WhatsApp phone number)
   - Status (should be Active)
```

**Verify channel configuration:**

✅ **Verify:**

Expected result: Channel record returned with MessageType = 'WhatsApp', IsActive = true, and MessagingPlatformKey matching your WhatsApp Business phone number.

## 5. Configure Routing to Your Agent

Set up the routing so inbound WhatsApp messages are handled by your Agentforce Service Agent.

### Create a messaging queue for fallback routing

The queue handles messages when the agent is unavailable. Even with direct agent routing, a fallback queue is required.

**Choose one of the following methods:**

#### SF CLI

```bash
# Check if queue exists
sf data query --target-org {{ORG_ALIAS}} \
  --query "SELECT Id FROM Group WHERE Type='Queue' AND DeveloperName='{{CHANNEL_NAME}}_Queue'"

# If not found, create via Metadata API
mkdir -p force-app/main/default/queues

cat > force-app/main/default/queues/{{CHANNEL_NAME}}_Queue.queue-meta.xml << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<Queue xmlns="http://soap.sforce.com/2006/04/metadata">
    <fullName>{{CHANNEL_NAME}}_Queue</fullName>
    <name>{{CHANNEL_NAME}} Queue</name>
    <queueSobject>
        <sobjectType>MessagingSession</sobjectType>
    </queueSobject>
</Queue>
EOF

sf project deploy start --target-org {{ORG_ALIAS}} --metadata Queue:{{CHANNEL_NAME}}_Queue
```

#### REST API

```bash
# Create queue
curl -X POST "{{INSTANCE_URL}}/services/data/v64.0/sobjects/Group" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" \
  -H "Content-Type: application/json" \
  -d '{
    "Name": "{{CHANNEL_NAME}} Queue",
    "DeveloperName": "{{CHANNEL_NAME}}_Queue",
    "Type": "Queue"
  }'

# Add MessagingSession to queue
QUEUE_ID=$(curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id+FROM+Group+WHERE+DeveloperName='{{CHANNEL_NAME}}_Queue'" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" | jq -r '.records[0].Id')

curl -X POST "{{INSTANCE_URL}}/services/data/v64.0/sobjects/QueueSobject" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" \
  -H "Content-Type: application/json" \
  -d "{\"QueueId\": \"$QUEUE_ID\", \"SobjectType\": \"MessagingSession\"}"
```

#### Browser Agent

```text
1. Navigate to Setup → Queues
2. Click New
3. Set Label: {{CHANNEL_NAME}} Queue
4. Set Queue Name: {{CHANNEL_NAME}}_Queue
5. In Supported Objects, add Messaging Session
6. Add queue members (agents who will handle escalations)
7. Click Save
```

**Verify queue creation:**

✅ **Verify:**

Expected result: One queue record returned with DeveloperName = '{{CHANNEL_NAME}}\_Queue'.

### Route the channel to your Agentforce agent

Set the SessionHandlerId on the MessagingChannel to point to your agent's BotDefinition. This enables direct agent routing for all inbound WhatsApp messages.

**Choose one of the following methods:**

#### SF CLI

```bash
# Get the Bot ID and Queue ID
BOT_ID=$(sf data query --target-org {{ORG_ALIAS}} --json \
  --query "SELECT Id FROM BotDefinition WHERE DeveloperName='{{AGENT_NAME}}'" \
  | jq -r '.result.records[0].Id')

QUEUE_ID=$(sf data query --target-org {{ORG_ALIAS}} --json \
  --query "SELECT Id FROM Group WHERE Type='Queue' AND DeveloperName='{{CHANNEL_NAME}}_Queue'" \
  | jq -r '.result.records[0].Id')

CHANNEL_ID=$(sf data query --target-org {{ORG_ALIAS}} --json \
  --query "SELECT Id FROM MessagingChannel WHERE DeveloperName='{{CHANNEL_NAME}}'" \
  | jq -r '.result.records[0].Id')

# Update the channel with agent routing
sf apex run --target-org {{ORG_ALIAS}} << EOF
MessagingChannel channel = [SELECT Id FROM MessagingChannel WHERE DeveloperName = '{{CHANNEL_NAME}}' LIMIT 1];
channel.SessionHandlerId = '$BOT_ID';
channel.FallbackQueueId = '$QUEUE_ID';
update channel;
System.debug('Updated MessagingChannel: ' + channel.Id + ' with SessionHandlerId: $BOT_ID');
EOF
```

#### REST API

```bash
BOT_ID=$(curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id+FROM+BotDefinition+WHERE+DeveloperName='{{AGENT_NAME}}'" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" | jq -r '.records[0].Id')

QUEUE_ID=$(curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id+FROM+Group+WHERE+Type='Queue'+AND+DeveloperName='{{CHANNEL_NAME}}_Queue'" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" | jq -r '.records[0].Id')

CHANNEL_ID=$(curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id+FROM+MessagingChannel+WHERE+DeveloperName='{{CHANNEL_NAME}}'" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" | jq -r '.records[0].Id')

curl -X PATCH "{{INSTANCE_URL}}/services/data/v64.0/sobjects/MessagingChannel/$CHANNEL_ID" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" \
  -H "Content-Type: application/json" \
  -d "{
    \"SessionHandlerId\": \"$BOT_ID\",
    \"FallbackQueueId\": \"$QUEUE_ID\"
  }"
```

#### Browser Agent

```text
1. Navigate to Setup → Messaging Settings
2. Click on your WhatsApp channel: {{CHANNEL_NAME}}
3. Under Routing Configuration:
   a. Set Routing Type to "Agentforce Service Agent"
   b. Select your agent: {{AGENT_NAME}}
   c. Set Fallback Queue: {{CHANNEL_NAME}} Queue
4. Click Save
```

**Verify agent routing:**

✅ **Verify:**

Expected result: Query the channel and confirm SessionHandlerId matches your BotDefinition.Id.

> **💡 Tip**: If `SessionHandlerId` is not available in your org's API version, configure routing through an Omni-Channel inbound flow instead. Create a flow with the Route Work action: Service Channel = `sfdc_livemessage`, Route To = your Agentforce Service Agent.

## 6. Test and Verify

Send a test message and confirm the full pipeline works end-to-end.

### Send a test WhatsApp message

Use your personal phone to send a message to the WhatsApp Business number and verify the agent responds.

**Choose one of the following methods:**

#### SF CLI

```bash
# After sending a test message from your phone, query for the session:
sf data query --target-org {{ORG_ALIAS}} \
  --query "SELECT Id, Status, AgentType, OwnerId, CreatedDate, MessagingChannelId FROM MessagingSession WHERE MessagingChannel.DeveloperName='{{CHANNEL_NAME}}' ORDER BY CreatedDate DESC LIMIT 5"
```

#### REST API

```bash
curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id,Status,AgentType,OwnerId,CreatedDate,MessagingChannelId+FROM+MessagingSession+WHERE+MessagingChannel.DeveloperName='{{CHANNEL_NAME}}'+ORDER+BY+CreatedDate+DESC+LIMIT+5" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}"
```

#### Browser Agent

```text
1. Open WhatsApp on your personal phone
2. Add the business phone number as a contact
3. Send a test message: "Hello, I need help"
4. Wait 5-10 seconds for the agent to respond
5. Verify you receive a response from the agent
6. Try a second message to confirm multi-turn works
```

**Verify the agent responds:**

✅ **Verify:**

Expected result: Agent responds to WhatsApp message. MessagingSession record created with Status = 'Active' or 'Ended'.

### Verify agent routing and session records

Confirm messages are being routed to your agent by checking MessagingSession and AgentWork records.

**Choose one of the following methods:**

#### SF CLI

```bash
# Check recent MessagingSessions for WhatsApp
sf data query --target-org {{ORG_ALIAS}} \
  --query "SELECT Id, Status, AgentType, OwnerId, CreatedDate FROM MessagingSession WHERE MessagingChannel.MessageType='WhatsApp' ORDER BY CreatedDate DESC LIMIT 5"

# Check AgentWork records
sf data query --target-org {{ORG_ALIAS}} \
  --query "SELECT Id, WorkItemId, BotId, RoutingType, CreatedDate FROM AgentWork ORDER BY CreatedDate DESC LIMIT 5"

# Check individual messages in the session
sf data query --target-org {{ORG_ALIAS}} \
  --query "SELECT Id, MessageText, SenderType, CreatedDate FROM MessagingEvent WHERE MessagingSession.MessagingChannel.DeveloperName='{{CHANNEL_NAME}}' ORDER BY CreatedDate DESC LIMIT 10"
```

#### REST API

```bash
curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id,Status,AgentType,OwnerId,CreatedDate+FROM+MessagingSession+WHERE+MessagingChannel.MessageType='WhatsApp'+ORDER+BY+CreatedDate+DESC+LIMIT+5" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}"

curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id,WorkItemId,BotId,RoutingType,CreatedDate+FROM+AgentWork+ORDER+BY+CreatedDate+DESC+LIMIT+5" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}"
```

#### Browser Agent

```text
1. Navigate to the App Launcher → Messaging Sessions
2. Find the most recent session
3. Click to view details
4. Verify:
   - Owner matches your agent
   - Message Type shows WhatsApp
   - Status is Active or Ended
5. Check the Messaging Events related list for individual messages
```

**Verify messaging session records:**

✅ **Verify:**

Expected result: Recent MessagingSession exists with AgentType populated and messages visible in MessagingEvent records.

> **⚠️ Warning**: WhatsApp enforces a **24-hour messaging window**. After the customer's last inbound message, you have 24 hours to respond with free-form text. After that window closes, only pre-approved **Message Templates** (submitted and approved via Meta Business Manager) can be sent. This applies to both agent and human responses.

## Troubleshooting

### Messages not reaching agent

1. Verify the MessagingChannel is Active: query `IsActive` field
2. Confirm `SessionHandlerId` is set and points to the correct BotDefinition.Id
3. Check the agent is Active (not Draft) in Agent Builder
4. Verify Omni-Channel is enabled in Setup
5. Check that the `sfdc_livemessage` Service Channel exists (auto-created with Digital Engagement)

### Phone number verification fails during channel creation

1. The number is already registered with WhatsApp. Deregister it first by deleting the WhatsApp account from the phone
2. The number cannot receive SMS or voice calls. Use a number that can
3. The verification code expired. Retry the verification step in the Setup wizard

### Channel creation fails in Setup wizard

1. Verify the Digital Engagement license is provisioned and active
2. Check your Meta Business verification status at business.facebook.com
3. Ensure your Meta Business account has a WhatsApp Business Account (WABA) created
4. Clear browser cache and try in incognito mode if the OAuth popup fails
5. Check for browser popup blockers interfering with the Meta OAuth flow

### 24-hour window expired and outbound messages fail

1. Submit Message Templates for approval in Meta Business Manager
2. Use approved templates for outbound messages after the 24-hour window
3. Templates typically take 24-48 hours for Meta to review and approve
4. Customer must send a new inbound message to reopen the free-form window

### WhatsApp messages arriving but agent not responding

1. Check `SessionHandlerId` versus `FallbackQueueId` routing. If SessionHandlerId is empty, messages route to the queue instead of the agent
2. Verify the agent's default_agent_user has proper permissions (Einstein Agent User profile, AgentforceServiceAgentUser permission set)
3. Check for errors in the agent's trace logs via Agent Builder preview

### Supported message types

WhatsApp supports: text (up to 4,096 characters per message), images, documents, location sharing, and interactive messages (buttons, lists). The Agentforce agent can receive all types but responds with text by default. Rich message responses require additional configuration via Messaging Components.

## Definition of Done

**Definition of Done:**

✅ **Verify:**

- WhatsApp channel active in Messaging Settings with MessageType = WhatsApp
- Customer can send a WhatsApp message to the business number
- Agent responds to the message automatically
- MessagingSession record created with correct routing to agent
- Escalation to human agent queue works when agent transfers

## SMS Setup

# SMS Setup (30 min)

## Outcome

A working SMS channel where customers can text a phone number and interact with your Agentforce Service Agent in real-time.

When complete, you'll have:

- A provisioned phone number (long code, toll-free, or short code)
- A Messaging Channel with `MessageType = Text` routed to your agent
- Omni-Channel routing configured with fallback queue
- Verified end-to-end text message flow from phone to agent

> **ℹ️ Info**: SMS channels require the **Digital Engagement** add-on license. Phone numbers are provisioned through Salesforce's built-in setup wizard. Available number types: long codes (10-digit), toll-free (US/Canada), and short codes (5-6 digit, US/Canada).

## Architecture

When a customer sends a text message to your provisioned phone number, the message flows through the following chain:

*(Architecture diagram — see the live page for the visual)*

The key routing link is `SessionHandlerId` on the MessagingChannel, which points directly to your agent's `BotDefinition.Id`. Unlike Enhanced Chat, SMS does not require an Experience Cloud site or an EmbeddedServiceConfig deployment. The MessagingChannel itself is the entry point.

The ServiceChannel for SMS messaging is `sfdc_livemessage`, and the queue SObject type is `MessagingSession`.

### Configure Your Variables

**Variable Reference** — replace these placeholders in commands below:

| Variable | Description | Example |
|----------|-------------|---------|
| `{{ORG_ALIAS}}` | Salesforce org alias or username | `<your-org-alias>` |
| `{{AGENT_NAME}}` | Service Agent developer name | `Reservation_Agent` |
| `{{CHANNEL_NAME}}` | Messaging Channel developer name | `AgentforceChannel` |
| `{{INSTANCE_URL}}` | Salesforce instance URL | `https://myorg.my.salesforce.com` |
| `{{ACCESS_TOKEN}}` | OAuth access token | `YOUR_ACCESS_TOKEN` |

## Implementation Steps

## 1. Verify Your Starting Point

Confirm your agent exists, your org has the required licenses, and CLI access is ready.

### Confirm your Service Agent exists and is active

Before building the routing chain, verify your Agentforce Service Agent is deployed and active in the target org.

**Choose one of the following methods:**

#### SF CLI

```bash
sf data query \
  --target-org {{ORG_ALIAS}} \
  --query "SELECT Id, DeveloperName, MasterLabel, AgentType FROM BotDefinition WHERE DeveloperName='{{AGENT_NAME}}' AND IsActive=true"
```

#### REST API

```bash
curl -X GET "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id,DeveloperName,MasterLabel,AgentType+FROM+BotDefinition+WHERE+DeveloperName='{{AGENT_NAME}}'+AND+IsActive=true" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" \
  -H "Content-Type: application/json"
```

#### Browser Agent

```text
1. Navigate to Setup -> Agents -> Agent Builder
2. Find your agent by name: {{AGENT_NAME}}
3. Verify the status shows Active
4. Copy the Agent ID for later use
```

**Verify the agent is active:**

✅ **Verify:**

Expected result: One record returned with a valid Id.

If no record is returned, activate your agent in Agent Builder before proceeding.

### Authenticate and verify org access

Ensure you have admin access to the target org and the CLI is connected.

**Choose one of the following methods:**

#### SF CLI

```bash
sf org login web --alias {{ORG_ALIAS}} --instance-url https://login.salesforce.com
sf org display --target-org {{ORG_ALIAS}} --verbose
```

#### REST API

```bash
# Use OAuth 2.0 web server flow to obtain access token
# Then verify with:
curl -X GET "{{INSTANCE_URL}}/services/data/v64.0/limits" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}"
```

#### Browser Agent

```text
1. Log into your Salesforce org at https://login.salesforce.com
2. Verify you see Setup in the gear menu (admin access required)
3. Navigate to Setup -> Company Information to confirm org details
```

**Verify org access:**

✅ **Verify:**

Expected result: Org display shows your username and instance URL without errors.

### Verify Digital Engagement license

SMS messaging requires the Digital Engagement add-on. Confirm it is active in your org.

**Choose one of the following methods:**

#### SF CLI

```bash
sf data query --target-org {{ORG_ALIAS}} \
  --query "SELECT Name, Status, ExpirationDate FROM PackageLicense WHERE NamespacePrefix='sfdc_messaging'"
```

#### REST API

```bash
curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Name,Status,ExpirationDate+FROM+PackageLicense+WHERE+NamespacePrefix='sfdc_messaging'" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}"
```

#### Browser Agent

```text
1. Navigate to Setup -> Company Information
2. Scroll to Permission Set Licenses or Feature Licenses
3. Look for Digital Engagement or Messaging
4. Verify it shows as Active
```

**Verify Digital Engagement license:**

✅ **Verify:**

Expected result: License record returned with `Status = 'Active'`.

## 2. Enable Messaging and Omni-Channel

Turn on the org-level settings that SMS depends on.

### Enable Messaging in Setup

Messaging must be enabled at the org level before any messaging channels can be created.

**Choose one of the following methods:**

#### SF CLI

```bash
# Verify Messaging is enabled
sf data query --target-org {{ORG_ALIAS}} \
  --query "SELECT Id, DeveloperName, IsActive FROM MessagingChannel LIMIT 1"

# If this returns an error about the object not existing,
# Messaging needs to be enabled via Setup UI (see Browser method)
```

#### REST API

```bash
# Check if Messaging object is accessible
curl -s "{{INSTANCE_URL}}/services/data/v64.0/sobjects/MessagingChannel/describe" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" | jq '.name'
```

#### Browser Agent

```text
1. Navigate to Setup -> Messaging Settings
2. If prompted, click Enable Messaging
3. Verify the Messaging Settings page shows "Messaging is enabled"
```

**Verify messaging is enabled:**

✅ **Verify:**

Expected result: Messaging Settings page shows messaging is enabled, or MessagingChannel object is queryable.

### Enable Omni-Channel

Omni-Channel handles the routing of messaging sessions to agents and queues.

**Choose one of the following methods:**

#### SF CLI

```bash
# Check if Omni-Channel is enabled by querying ServiceChannel
sf data query --target-org {{ORG_ALIAS}} \
  --query "SELECT Id, DeveloperName FROM ServiceChannel WHERE DeveloperName='sfdc_livemessage'"
```

#### REST API

```bash
curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id,DeveloperName+FROM+ServiceChannel+WHERE+DeveloperName='sfdc_livemessage'" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}"
```

#### Browser Agent

```text
1. Navigate to Setup -> Omni-Channel Settings
2. If not enabled, check "Enable Omni-Channel"
3. Click Save
4. Verify the sfdc_livemessage ServiceChannel exists under Setup -> Service Channels
```

**Verify service channel:**

✅ **Verify:**

Expected result: ServiceChannel record for `sfdc_livemessage` exists.

## 3. Provision Your Phone Number

Salesforce handles the carrier relationship and number provisioning through its built-in setup wizard. No external telephony provider is needed.

> **💡 Tip**: **Long codes** are standard 10-digit phone numbers, good for low-volume conversational messaging. **Toll-free numbers** support higher throughput for US/Canada. **Short codes** (5-6 digits) are best for high-volume, mass messaging but require a longer carrier approval process (weeks to months).

### Request a phone number through Salesforce Setup

**Choose one of the following methods:**

#### SF CLI

```bash
# Phone number provisioning must be done through Setup UI
# After provisioning, verify the number is available:
sf data query --target-org {{ORG_ALIAS}} \
  --query "SELECT Id, MasterLabel, MessageType, MessagingPlatformKey, IsActive FROM MessagingChannel WHERE MessageType='Text'"
```

#### REST API

```bash
# Phone number provisioning must be done through Setup UI
# After provisioning, verify:
curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id,MasterLabel,MessageType,MessagingPlatformKey,IsActive+FROM+MessagingChannel+WHERE+MessageType='Text'" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}"
```

#### Browser Agent

```text
1. Navigate to Setup -> Messaging Settings
2. Click New Channel
3. Select SMS as the channel type
4. Choose your number type:
   - Long Code: Standard 10-digit number (instant provisioning)
   - Toll-Free: Higher throughput for US/Canada (minutes to hours)
   - Short Code: 5-6 digit number (weeks for carrier approval)
5. Follow the provisioning wizard to request your number
6. For long codes and toll-free: number is typically available immediately
7. For short codes: you will receive a notification when approved
8. Note the provisioned phone number for the next step
```

> **⚠️ Warning**: Short code provisioning requires carrier approval and can take **2-12 weeks**. Long codes and toll-free numbers are typically provisioned within minutes. Plan accordingly if you need a short code for production.

**Verify phone number provisioning:**

✅ **Verify:**

Expected result: A phone number has been provisioned and is visible in Messaging Settings.

## 4. Create the SMS Messaging Channel

Configure the Messaging Channel to route incoming text messages to your agent.

### Create a messaging queue for fallback routing

The queue handles messages when the agent is unavailable. Even with direct agent routing, a fallback queue is required.

**Choose one of the following methods:**

#### SF CLI

```bash
# Check if queue exists
sf data query --target-org {{ORG_ALIAS}} \
  --query "SELECT Id FROM Group WHERE Type='Queue' AND DeveloperName='{{CHANNEL_NAME}}_Queue'"

# If not found, create via Metadata API
cat > queue-metadata.xml << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<Queue xmlns="http://soap.sforce.com/2006/04/metadata">
    <fullName>{{CHANNEL_NAME}}_Queue</fullName>
    <name>{{CHANNEL_NAME}} Queue</name>
    <queueSobject>
        <sobjectType>MessagingSession</sobjectType>
    </queueSobject>
</Queue>
EOF

sf project deploy start --target-org {{ORG_ALIAS}} --metadata Queue:{{CHANNEL_NAME}}_Queue
```

#### REST API

```bash
# Create queue
curl -X POST "{{INSTANCE_URL}}/services/data/v64.0/sobjects/Group" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" \
  -H "Content-Type: application/json" \
  -d '{
    "Name": "{{CHANNEL_NAME}} Queue",
    "DeveloperName": "{{CHANNEL_NAME}}_Queue",
    "Type": "Queue"
  }'

# Add MessagingSession to queue
QUEUE_ID=$(curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id+FROM+Group+WHERE+DeveloperName='{{CHANNEL_NAME}}_Queue'" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" | jq -r '.records[0].Id')

curl -X POST "{{INSTANCE_URL}}/services/data/v64.0/sobjects/QueueSobject" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" \
  -H "Content-Type: application/json" \
  -d "{\"QueueId\": \"$QUEUE_ID\", \"SobjectType\": \"MessagingSession\"}"
```

#### Browser Agent

```text
1. Navigate to Setup -> Queues
2. Click New
3. Set Label: {{CHANNEL_NAME}} Queue
4. Set Queue Name: {{CHANNEL_NAME}}_Queue
5. In Supported Objects, add Messaging Session
6. Click Save
```

**Verify queue creation:**

✅ **Verify:**

Expected result: One queue record returned with `DeveloperName = '{{CHANNEL_NAME}}_Queue'`.

### Configure the SMS channel to route to your agent

The Messaging Channel is the key routing component. Setting `SessionHandlerId` to your agent's `BotDefinition.Id` enables direct routing of incoming text messages to your Agentforce agent.

**Choose one of the following methods:**

#### SF CLI

```bash
# Get the Bot ID and Queue ID
BOT_ID=$(sf data query --target-org {{ORG_ALIAS}} --json \
  --query "SELECT Id FROM BotDefinition WHERE DeveloperName='{{AGENT_NAME}}'" \
  | jq -r '.result.records[0].Id')

QUEUE_ID=$(sf data query --target-org {{ORG_ALIAS}} --json \
  --query "SELECT Id FROM Group WHERE Type='Queue' AND DeveloperName='{{CHANNEL_NAME}}_Queue'" \
  | jq -r '.result.records[0].Id')

# If the channel was created during provisioning, update it to route to your agent
CHANNEL_ID=$(sf data query --target-org {{ORG_ALIAS}} --json \
  --query "SELECT Id FROM MessagingChannel WHERE MessageType='Text' AND DeveloperName='{{CHANNEL_NAME}}'" \
  | jq -r '.result.records[0].Id')

sf apex run --target-org {{ORG_ALIAS}} << EOF
MessagingChannel channel = [SELECT Id FROM MessagingChannel WHERE Id = '$CHANNEL_ID'];
channel.SessionHandlerId = '$BOT_ID';
channel.FallbackQueueId = '$QUEUE_ID';
channel.IsActive = true;
update channel;
System.debug('Updated MessagingChannel: ' + channel.Id);
EOF
```

#### REST API

```bash
BOT_ID=$(curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id+FROM+BotDefinition+WHERE+DeveloperName='{{AGENT_NAME}}'" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" | jq -r '.records[0].Id')

QUEUE_ID=$(curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id+FROM+Group+WHERE+Type='Queue'+AND+DeveloperName='{{CHANNEL_NAME}}_Queue'" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" | jq -r '.records[0].Id')

# Get the SMS channel created during provisioning
CHANNEL_ID=$(curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id+FROM+MessagingChannel+WHERE+MessageType='Text'+AND+DeveloperName='{{CHANNEL_NAME}}'" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" | jq -r '.records[0].Id')

# Update the channel with agent routing
curl -X PATCH "{{INSTANCE_URL}}/services/data/v64.0/sobjects/MessagingChannel/$CHANNEL_ID" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" \
  -H "Content-Type: application/json" \
  -d "{
    \"SessionHandlerId\": \"$BOT_ID\",
    \"FallbackQueueId\": \"$QUEUE_ID\",
    \"IsActive\": true
  }"
```

#### Browser Agent

```text
1. Navigate to Setup -> Messaging Settings
2. Find your SMS channel (created during phone number provisioning)
3. Click Edit
4. Under Routing:
   - Set Session Handler Type to Agent
   - Select your agent: {{AGENT_NAME}}
5. Under Fallback Queue, select {{CHANNEL_NAME}} Queue
6. Ensure the channel is Active
7. Click Save
```

**Verify channel creation:**

✅ **Verify:**

Expected result: MessagingChannel record returned with `IsActive = true`, `MessageType = 'Text'`, and `SessionHandlerId` matching your BotDefinition.Id.

### Verify the complete channel configuration

Confirm all routing fields are correctly set on the MessagingChannel.

**Choose one of the following methods:**

#### SF CLI

```bash
sf data query --target-org {{ORG_ALIAS}} \
  --query "SELECT Id, DeveloperName, MasterLabel, MessageType, MessagingPlatformKey, IsActive, SessionHandlerId, FallbackQueueId FROM MessagingChannel WHERE DeveloperName='{{CHANNEL_NAME}}'"
```

#### REST API

```bash
curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id,DeveloperName,MasterLabel,MessageType,MessagingPlatformKey,IsActive,SessionHandlerId,FallbackQueueId+FROM+MessagingChannel+WHERE+DeveloperName='{{CHANNEL_NAME}}'" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}"
```

#### Browser Agent

```text
1. Navigate to Setup -> Messaging Settings
2. Click on your SMS channel: {{CHANNEL_NAME}}
3. Verify:
   - Message Type: Text
   - Status: Active
   - Session Handler: {{AGENT_NAME}}
   - Fallback Queue: {{CHANNEL_NAME}} Queue
```

**Verify channel configuration:**

✅ **Verify:**

Expected result: `MessageType = 'Text'`, `IsActive = true`, `SessionHandlerId` is populated, `FallbackQueueId` is populated.

## 5. Configure Consent and Compliance

SMS messaging requires opt-in consent tracking and compliance with telecommunications regulations.

### Configure consent keywords

Salesforce automatically handles standard consent keywords, but you should verify the configuration.

**Choose one of the following methods:**

#### SF CLI

```bash
# Verify consent keywords are configured for the channel
sf data query --target-org {{ORG_ALIAS}} \
  --query "SELECT Id, MessagingChannelId, ConsentType, DoubleOptInRequired FROM MessagingChannelConsent WHERE MessagingChannel.DeveloperName='{{CHANNEL_NAME}}'"
```

#### REST API

```bash
curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id,MessagingChannelId,ConsentType,DoubleOptInRequired+FROM+MessagingChannelConsent+WHERE+MessagingChannel.DeveloperName='{{CHANNEL_NAME}}'" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}"
```

#### Browser Agent

```text
1. Navigate to Setup -> Messaging Settings
2. Click on your SMS channel: {{CHANNEL_NAME}}
3. Scroll to Consent Settings
4. Verify consent type is configured:
   - ImplicitOptIn: Consent assumed when customer initiates
   - ExplicitOptIn: Customer must text a keyword to opt in
   - DoubleOptIn: Customer must confirm opt-in via reply
5. Review auto-configured keywords:
   - Opt-out: STOP, CANCEL, END, QUIT, STOPALL, UNSUBSCRIBE
   - Help: HELP
```

> **⚠️ Warning**: **TCPA Compliance is your responsibility.** Salesforce auto-configures STOP/HELP keyword handling, but you must ensure your SMS usage complies with the Telephone Consumer Protection Act (TCPA) and carrier requirements. This includes maintaining proper consent records, honoring opt-out requests immediately, and only messaging customers who have given consent.

**Verify consent configuration:**

✅ **Verify:**

Expected result: Consent configuration exists for the channel with appropriate ConsentType.

## 6. Test and Verify

Send a test SMS and confirm the full routing chain works.

### Send a test text message

Use a real phone to send a text message to your provisioned number and verify the agent responds.

**Choose one of the following methods:**

#### SF CLI

```bash
# After sending a text message from your phone, verify the session was created
sf data query --target-org {{ORG_ALIAS}} \
  --query "SELECT Id, Status, AgentType, OwnerId, CreatedDate, MessageType FROM MessagingSession WHERE MessageType='Text' ORDER BY CreatedDate DESC LIMIT 5"
```

#### REST API

```bash
curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id,Status,AgentType,OwnerId,CreatedDate,MessageType+FROM+MessagingSession+WHERE+MessageType='Text'+ORDER+BY+CreatedDate+DESC+LIMIT+5" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}"
```

#### Browser Agent

```text
1. From your personal phone, send a text message to the provisioned number
2. Type: "Hello, I need help with my order"
3. Wait 10-30 seconds for the agent to respond
4. Verify you receive a text reply from the agent
5. Continue the conversation to test multi-turn behavior
```

**Verify the agent responds:**

✅ **Verify:**

Expected result: You receive a text response from the agent, and a MessagingSession record is created with `MessageType = 'Text'`.

### Verify agent routing records

Confirm messages are being routed to your agent by checking MessagingSession and AgentWork records.

**Choose one of the following methods:**

#### SF CLI

```bash
# Check recent MessagingSessions for SMS
sf data query --target-org {{ORG_ALIAS}} \
  --query "SELECT Id, Status, AgentType, OwnerId, CreatedDate, MessageType FROM MessagingSession WHERE MessageType='Text' ORDER BY CreatedDate DESC LIMIT 5"

# Check AgentWork records
sf data query --target-org {{ORG_ALIAS}} \
  --query "SELECT Id, WorkItemId, BotId, RoutingType, CreatedDate FROM AgentWork ORDER BY CreatedDate DESC LIMIT 5"

# Verify individual messages in the session
sf data query --target-org {{ORG_ALIAS}} \
  --query "SELECT Id, MessageText, SenderType, SentDate FROM MessagingMessage ORDER BY SentDate DESC LIMIT 10"
```

#### REST API

```bash
curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id,Status,AgentType,OwnerId,CreatedDate,MessageType+FROM+MessagingSession+WHERE+MessageType='Text'+ORDER+BY+CreatedDate+DESC+LIMIT+5" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}"

curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id,WorkItemId,BotId,RoutingType,CreatedDate+FROM+AgentWork+ORDER+BY+CreatedDate+DESC+LIMIT+5" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}"
```

#### Browser Agent

```text
1. Navigate to Setup -> Object Manager -> Messaging Session
2. Click Recent Messaging Sessions tab
3. Look for a session with Message Type = Text created in the last few minutes
4. Click to view details
5. Verify Owner matches your agent
6. Check the Messages related list for both inbound and outbound messages
```

**Verify messaging session records:**

✅ **Verify:**

Expected result: Recent MessagingSession exists with `Status = 'Active'` or `'Ended'`, `MessageType = 'Text'`, and AgentWork record shows routing to your agent.

### Test opt-out compliance

Verify that consent keywords are properly handled by the platform.

**Choose one of the following methods:**

#### SF CLI

```bash
# After sending "STOP" from your test phone, verify opt-out was recorded
sf data query --target-org {{ORG_ALIAS}} \
  --query "SELECT Id, MessagingChannelId, ConsentStatus, MessagingEndUserId FROM MessagingConsent ORDER BY LastModifiedDate DESC LIMIT 5"
```

#### REST API

```bash
curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id,MessagingChannelId,ConsentStatus,MessagingEndUserId+FROM+MessagingConsent+ORDER+BY+LastModifiedDate+DESC+LIMIT+5" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}"
```

#### Browser Agent

```text
1. From your test phone, text "STOP" to the provisioned number
2. You should receive an automatic opt-out confirmation
3. Text "START" or "UNSTOP" to re-subscribe
4. Verify you can resume messaging with the agent
```

**Verify opt-out handling:**

✅ **Verify:**

Expected result: Sending "STOP" triggers an automatic opt-out response, and a MessagingConsent record reflects the opt-out status.

## Troubleshooting

### Messages not reaching agent

1. Verify the MessagingChannel is Active (`IsActive = true`)
2. Confirm `SessionHandlerId` is set and points to the correct BotDefinition
3. Check that the agent is Active (not Draft) in Agent Builder
4. Verify the `sfdc_livemessage` ServiceChannel exists under Omni-Channel
5. Ensure Digital Engagement license is active

### Number provisioning delays

- **Long codes**: Provisioned instantly or within minutes
- **Toll-free numbers**: Usually available within minutes to hours
- **Short codes**: Require carrier approval, which takes **2-12 weeks**. Short codes also require a campaign use case submission to carriers. Plan for this lead time in production timelines.

### Character limit and message splitting

SMS messages are limited to 160 characters per segment. Longer agent responses are automatically split into multiple segments by the carrier. To keep agent responses clean:

1. Keep agent instructions concise to encourage shorter responses
2. Test with longer responses to verify segment splitting is acceptable
3. MMS (images) is supported on carriers that allow it, but adds cost per message

### Opt-out compliance issues

Salesforce auto-configures these consent keywords:

- **Opt-out**: STOP, CANCEL, END, QUIT, STOPALL, UNSUBSCRIBE
- **Help**: HELP

If customers report they cannot opt out, verify the MessagingChannelConsent configuration and check that the channel's consent type is set appropriately for your use case.

### Agent responds but messages are delayed

1. Check Omni-Channel queue depth in the Omni-Channel Supervisor
2. Verify the agent's response time is within acceptable bounds
3. Carrier network latency can add 1-5 seconds to delivery
4. If using a shared short code, high volume may cause queuing

### Cannot send outbound messages to opted-in customers

Unlike channels with a 24-hour messaging window (WhatsApp, Facebook Messenger), SMS has no platform-imposed window. However, you must have valid consent from the customer. Check the `MessagingConsent` records and ensure `ConsentStatus` is not `OptedOut`.

## Definition of Done

**Definition of Done:**

✅ **Verify:**

- SMS channel active in Messaging Settings with `MessageType = Text`
- Customer can text the provisioned phone number
- Agent responds automatically via SMS
- MessagingSession record created with `MessageType = Text`
- Opt-out keywords (STOP) trigger automated opt-out response
- Escalation to human agent queue works via fallback queue
- Consent records are properly tracked in MessagingConsent

## Messenger Setup

# Messenger Setup (30 min)

## Outcome

By the end of this guide, your Salesforce org has:

- A Facebook Messenger channel connected to your Facebook Page.
- Omni-Channel routing delivering Messenger conversations to your Agentforce Service Agent.
- A verified end-to-end flow: customer messages your Page, agent responds automatically in Messenger.
- SOQL evidence confirming MessagingSession creation and agent assignment.

> **ℹ️ Info**: Facebook Messenger channels require the **Digital Engagement** add-on license and a **Facebook Page** managed through **Meta Business Manager**. No Experience Cloud site or EmbeddedServiceConfig is needed.

## Architecture

*(Architecture diagram — see the live page for the visual)*

Key metadata relationships:

- `MessagingChannel.MessageType` = `Facebook`
- `MessagingChannel.MessagingPlatformKey` = Facebook Page ID
- `ServiceChannel.DeveloperName` = `sfdc_livemessage`
- `Queue.SobjectType` = `MessagingSession`
- `ConsentType` = ImplicitOptIn (user initiates by messaging your Page)

### Configure Your Variables

**Variable Reference** — replace these placeholders in commands below:

| Variable | Description | Example |
|----------|-------------|---------|
| `{{ORG_ALIAS}}` | Salesforce org alias or username | `<your-org-alias>` |
| `{{AGENT_NAME}}` | Service Agent developer name | `Reservation_Agent` |
| `{{CHANNEL_NAME}}` | Messaging Channel developer name | `AgentforceChannel` |
| `{{INSTANCE_URL}}` | Salesforce instance URL | `https://myorg.my.salesforce.com` |
| `{{ACCESS_TOKEN}}` | OAuth access token | `YOUR_ACCESS_TOKEN` |

## Prerequisites

- Salesforce CLI (`sf`) installed.
- Admin-level access in the target Salesforce org.
- **Digital Engagement** license provisioned on the org.
- A published, active Agentforce Service Agent (example: `{{AGENT_NAME}}`).
- A Facebook Page where you are a Page admin.
- A Meta Business Manager account (business.facebook.com).

## Step 1: Set Environment Variables and Authenticate

Set org alias, agent name, channel details, and queue name; then authenticate with the CLI.

Use these defaults or replace with your own values.

**Choose one of the following methods:**

#### SF CLI

```bash
export ORG_ALIAS="MY_ORG"
export API_VERSION="65.0"

export AGENT_NAME="Support_Agent"

export CHANNEL_DEV_NAME="Facebook_Messenger_Channel"
export CHANNEL_LABEL="Facebook Messenger Channel"

export QUEUE_DEV_NAME="Messenger_Queue"
export QUEUE_LABEL="Messenger Queue"

# Authenticate and verify
sf org login web --alias "$ORG_ALIAS" --instance-url https://login.salesforce.com
sf org display --target-org "$ORG_ALIAS" --verbose --json
```

> **💡 Tip**: If you already have an authenticated org alias, skip `sf org login web` and just run `sf org display` to confirm your session is active.

## Step 2: Enable Messaging and Omni-Channel

Confirm that Messaging and Omni-Channel are enabled in your org's Setup.

Both Messaging and Omni-Channel must be enabled before you can create a Facebook Messenger channel.

### A. Enable Messaging

1. Navigate to **Setup > Messaging Settings**.
2. Toggle **Messaging** to **On** if not already enabled.

> **ℹ️ Info**: Messaging Settings is auto-enabled in most orgs with Digital Engagement. If you do not see this option, confirm the Digital Engagement license is provisioned.

### B. Enable Omni-Channel

1. Navigate to **Setup > Omni-Channel Settings**.
2. Toggle **Enable Omni-Channel** to **On** if not already enabled.

### C. Verify the service channel exists

**Choose one of the following methods:**

#### SF CLI

```bash
sf data query \
  --target-org "$ORG_ALIAS" \
  --query "SELECT Id,DeveloperName,MasterLabel FROM ServiceChannel WHERE DeveloperName='sfdc_livemessage' LIMIT 1"
```

#### REST API

```bash
# Using curl with access token from sf org display
ACCESS_TOKEN=$(sf org display --target-org "$ORG_ALIAS" --json | python3 -c "import sys,json; print(json.load(sys.stdin)['result']['accessToken'])")
INSTANCE_URL=$(sf org display --target-org "$ORG_ALIAS" --json | python3 -c "import sys,json; print(json.load(sys.stdin)['result']['instanceUrl'])")

curl -s "$INSTANCE_URL/services/data/v$API_VERSION/query?q=SELECT+Id,DeveloperName,MasterLabel+FROM+ServiceChannel+WHERE+DeveloperName='sfdc_livemessage'+LIMIT+1" \
  -H "Authorization: Bearer $ACCESS_TOKEN" | python3 -m json.tool
```

**Verify the Live Message channel is set up:**

✅ **Verify:**

**Expected result:** `sfdc_livemessage` ServiceChannel record returned with a valid Id

If no record is returned, Messaging is not enabled. Return to Setup > Messaging Settings and enable it.

## Step 3: Set Up Meta Business and Facebook Page

Prepare your Meta Business Manager account, Facebook Page, and Meta App for the Messenger integration.

This step is performed entirely in Meta's platforms. Salesforce handles webhook registration automatically when you connect the channel in Step 4.

### A. Verify Meta Business Manager

1. Go to [business.facebook.com](https://business.facebook.com).
2. Confirm you have an active Meta Business Manager account.
3. If not, create one and complete business verification.

### B. Confirm Your Facebook Page

1. In Meta Business Manager, go to **Business Settings > Accounts > Pages**.
2. Confirm you have a Facebook Page and that you are a **Page admin**.
3. Note the **Page ID** (visible in Page Settings > Page transparency, or in the URL).

### C. Create a Meta App (if not already done)

1. Go to [developers.facebook.com](https://developers.facebook.com).
2. Click **Create App** and select a Business type.
3. Add the **Messenger** product to your app.
4. Link your Facebook Page to the app under **Messenger > Settings**.

> **⚠️ Warning**: For production use beyond Page admins and testers, your Meta App must pass **App Review** for the `pages_messaging` permission. This review process can take days to weeks. Until approved, only users listed as Page admins, developers, or testers in the Meta App dashboard can exchange messages.

> **💡 Tip**: You do **not** need to manually configure webhooks in the Meta App dashboard. Salesforce registers the webhook endpoint automatically during channel creation in Step 4. The webhook URL follows the format: `https://{{INSTANCE_URL}}/services/messaging/facebook/webhook/{{CHANNEL_ID}}`

## Step 4: Create the Messenger Channel

Connect your Facebook Page to Salesforce via Setup and configure routing to your agent.

### A. Create the Channel via Setup UI

1. Navigate to **Setup > Messaging Settings**.
2. Click **New Channel**.
3. Select **Facebook Messenger** as the channel type.
4. Salesforce initiates an OAuth flow to Meta. Log in with your Facebook credentials and authorize access to your Page.
5. Select the Facebook Page you want to connect.
6. Complete the wizard. Salesforce automatically registers the webhook with Meta.

### B. Configure Agent Routing on the Channel

1. In **Setup > Messaging Settings**, click into your new Messenger channel.
2. Under **Routing**, set:
   - **Session Handler Type**: `AgentforceServiceAgent`
   - **Session Handler**: Select your agent (`{{AGENT_NAME}}`)
3. Set a **Fallback Queue** (for escalation when the agent hands off to a human).
4. Save and activate the channel.

### C. Verify the Channel via CLI

**Choose one of the following methods:**

#### SF CLI

```bash
sf data query \
  --target-org "$ORG_ALIAS" \
  --query "SELECT Id,DeveloperName,IsActive,MessageType,MessagingPlatformKey,SessionHandlerId,FallbackQueueId FROM MessagingChannel WHERE DeveloperName='$CHANNEL_DEV_NAME' LIMIT 1"
```

#### REST API

```bash
curl -s "$INSTANCE_URL/services/data/v$API_VERSION/query?q=SELECT+Id,DeveloperName,IsActive,MessageType,MessagingPlatformKey,SessionHandlerId,FallbackQueueId+FROM+MessagingChannel+WHERE+DeveloperName='$CHANNEL_DEV_NAME'+LIMIT+1" \
  -H "Authorization: Bearer $ACCESS_TOKEN" | python3 -m json.tool
```

**Verify the Messenger channel is configured:**

✅ **Verify:**

**Expected result:** MessagingChannel record with `MessageType=Facebook`, `IsActive=true`, and `SessionHandlerId` matching your BotDefinition Id

If `SessionHandlerId` is null, re-open the channel settings and assign your agent. If `MessageType` is not `Facebook`, the channel was created with the wrong type.

### D. Create or Verify the Queue (if not done via UI)

If the wizard did not create a fallback queue, create one manually:

**Choose one of the following methods:**

#### SF CLI

```bash
# Check if queue exists
sf data query \
  --target-org "$ORG_ALIAS" \
  --query "SELECT Id,Name,DeveloperName FROM Group WHERE Type='Queue' AND DeveloperName='$QUEUE_DEV_NAME' LIMIT 1"
```

#### REST API

```bash
# Create queue if it does not exist
curl -s "$INSTANCE_URL/services/data/v$API_VERSION/sobjects/Group" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"Name\": \"$QUEUE_LABEL\", \"DeveloperName\": \"$QUEUE_DEV_NAME\", \"Type\": \"Queue\"}" | python3 -m json.tool

# Map queue to MessagingSession
QUEUE_ID=$(sf data query --target-org "$ORG_ALIAS" --query "SELECT Id FROM Group WHERE Type='Queue' AND DeveloperName='$QUEUE_DEV_NAME' LIMIT 1" --json | python3 -c "import sys,json; print(json.load(sys.stdin)['result']['records'][0]['Id'])")

curl -s "$INSTANCE_URL/services/data/v$API_VERSION/sobjects/QueueSobject" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"QueueId\": \"$QUEUE_ID\", \"SobjectType\": \"MessagingSession\"}" | python3 -m json.tool
```

## Step 5: Configure Routing to Your Agent

Ensure Omni-Channel routes Messenger conversations to your Agentforce agent with a fallback queue for escalation.

If you already set the Session Handler in Step 4B, this step confirms the routing path. If your org requires flow-based routing instead of direct `SessionHandlerId`, configure it here.

### Direct Routing (Preferred)

Direct routing uses `SessionHandlerId` on the MessagingChannel to point to your BotDefinition. This was configured in Step 4B.

Verify the agent identity:

**Choose one of the following methods:**

#### SF CLI

```bash
sf data query \
  --target-org "$ORG_ALIAS" \
  --query "SELECT Id,DeveloperName,MasterLabel,AgentType FROM BotDefinition WHERE DeveloperName='$AGENT_NAME' LIMIT 1"
```

### Flow-Based Routing (Fallback)

If your org does not support `SessionHandlerId` on MessagingChannel, use an Omni-Channel inbound flow:

1. Navigate to **Setup > Flows**.
2. Create a new **Record-Triggered Flow** or **Omni-Channel Flow** that:
   - Triggers on `MessagingSession` creation.
   - Evaluates channel type = `Facebook`.
   - Routes to your agent queue.
3. Assign the flow to the Messaging Channel routing configuration.

> **ℹ️ Info**: Direct routing via `SessionHandlerId` is the recommended approach. Flow-based routing is only needed if your org version does not expose the `SessionHandlerId` field for API updates.

## Step 6: Test and Verify

Send a test message to your Facebook Page and confirm the agent responds.

### A. Send a Test Message

1. Open Facebook Messenger (web or mobile app).
2. Search for your Facebook Page name.
3. Send a message such as: "Hi, I need help with my order."

> **💡 Tip**: Before App Review approval, only Page admins, app developers, and testers (configured in Meta App Settings > Roles) can message the Page and trigger the agent. Add test users in the Meta App dashboard under **App Roles > Testers**.

### B. Verify MessagingSession Creation

**Choose one of the following methods:**

#### SF CLI

```bash
sf data query \
  --target-org "$ORG_ALIAS" \
  --query "SELECT Id,Status,AgentType,OwnerId,CreatedDate,MessageType FROM MessagingSession ORDER BY CreatedDate DESC LIMIT 5"
```

#### REST API

```bash
curl -s "$INSTANCE_URL/services/data/v$API_VERSION/query?q=SELECT+Id,Status,AgentType,OwnerId,CreatedDate,MessageType+FROM+MessagingSession+ORDER+BY+CreatedDate+DESC+LIMIT+5" \
  -H "Authorization: Bearer $ACCESS_TOKEN" | python3 -m json.tool
```

### C. Verify AgentWork Assignment

**Choose one of the following methods:**

#### SF CLI

```bash
sf data query \
  --target-org "$ORG_ALIAS" \
  --query "SELECT Id,WorkItemId,BotId,RoutingType,CreatedDate FROM AgentWork ORDER BY CreatedDate DESC LIMIT 5"
```

### D. Verify Agent Response in Messenger

Return to the Messenger conversation. Confirm that your Agentforce agent responded to the test message.

**Verify the agent responds in Messenger:**

✅ **Verify:**

**Expected result:** MessagingSession record with `MessageType=Facebook`, agent response visible in Messenger conversation

If no session is created, check that the Messenger channel is active and the webhook is registered. If the agent does not respond, verify the BotDefinition is published and activated.

> **⚠️ Warning**: Facebook enforces a **24-hour messaging window**. After the customer's last message, you have 24 hours to send responses. After that window closes, only specific **message tags** are permitted (confirmed event updates, post-purchase updates, account updates). Plan your escalation and follow-up flows accordingly.

## Messenger-Specific Constraints

| Constraint              | Value                                                                            |
| ----------------------- | -------------------------------------------------------------------------------- |
| Message character limit | 2,000 per message                                                                |
| Attachment size limit   | 25 MB                                                                            |
| Messaging window        | 24 hours from customer's last message                                            |
| Supported content       | Text, images, files, buttons, quick replies, carousels (via Messenger templates) |
| Consent model           | ImplicitOptIn (customer initiates by messaging your Page)                        |
| Platform type           | Enhanced (not Standard/legacy)                                                   |

## Troubleshooting

### Messages not reaching Salesforce

Check the following:

1. In the Meta App dashboard (developers.facebook.com), navigate to **Messenger > Settings > Webhooks**. Confirm a webhook is registered and pointing to your Salesforce instance.
2. In Salesforce, verify the Messenger channel is **Active** in Setup > Messaging Settings.
3. Confirm the Facebook Page is linked to the Meta App and that the `pages_messaging` permission is authorized.
4. Test with a user who is a Page admin or listed as a tester in the Meta App.

### Cannot connect Facebook Page during channel creation

Verify:

1. You are logged into Facebook as a **Page admin** (not just a Page editor).
2. Your Meta Business Manager account has completed business verification.
3. The Facebook Page is claimed by your Meta Business Manager account (Business Settings > Accounts > Pages).
4. Your browser is not blocking the OAuth popup from Meta.

### App Review pending - limited testing

Until your Meta App passes App Review for `pages_messaging`:

1. Only Page admins, developers, and testers can interact with the agent via Messenger.
2. Add test users in the Meta App dashboard: **App Roles > Testers**.
3. Each tester must accept the invitation before they can message.
4. Submit for App Review early as it can take days to weeks. Include screenshots of your Messenger integration and describe the use case clearly.

### 24-hour messaging window expired

After 24 hours from the customer's last message:

1. Standard replies are blocked by Meta.
2. Use **message tags** for permitted follow-up categories only: `CONFIRMED_EVENT_UPDATE`, `POST_PURCHASE_UPDATE`, `ACCOUNT_UPDATE`.
3. Design your agent's escalation flow to resolve conversations within the 24-hour window.
4. Consider sending a proactive "Is there anything else?" message before the window closes.

### Agent not responding but session is created

1. Verify the BotDefinition is published and the active BotVersion is correct:

```bash
sf data query --target-org "$ORG_ALIAS" --query "SELECT Id,DeveloperName,Status FROM BotVersion WHERE BotDefinition.DeveloperName='$AGENT_NAME' AND Status='Active' LIMIT 1"
```

2. Check that the `SessionHandlerId` on the MessagingChannel matches the BotDefinition Id.
3. Review agent debug logs in Setup > Agentforce > Agents > [Your Agent] > Logs.

## Definition of Done

**Definition of Done:**

✅ **Verify:**

- Messenger channel active in Messaging Settings with `MessageType=Facebook`.
- Customer can message your Facebook Page and reach the Agentforce agent.
- Agent responds automatically in Messenger.
- `MessagingSession` record created with `MessageType=Facebook`.
- `AgentWork` record confirms agent assignment.
- Escalation to human agent fallback queue works when agent hands off.

## Apple Messages Setup

# Apple Messages Setup (30 min)

## Outcome

A working Apple Messages for Business pipeline where customers on iPhone, iPad, or Mac can message your business and receive automated responses from your Agentforce Service Agent.

When complete, you will have:

- An Apple Messages for Business channel connected to your Salesforce org
- Omni-Channel routing configured to send inbound iMessage conversations to your Agentforce agent
- Verified end-to-end message flow from Apple Messages to agent and back

> **ℹ️ Info**: Apple Messages for Business channels require the **Digital Engagement** add-on license and an approved **Apple Business Register** account (register.apple.com/business). Unlike Enhanced Chat, no Experience Cloud site or EmbeddedServiceConfig is needed. Apple Messages only works on Apple devices (iPhone, iPad, Mac) via the iMessage protocol.

## Architecture

When a customer sends a message to your business through Apple Maps, Safari, Spotlight, or Siri, the message flows through the following chain:

*(Architecture diagram — see the live page for the visual)*

The key routing field is `sessionHandlerType = AgentforceServiceAgent` on the MessagingChannel, with `sessionHandlerAsa` pointing to the BotDefinition DeveloperName. The `sessionHandlerQueue` field specifies the fallback queue when the agent is unavailable.

Unlike Enhanced Chat, Apple Messages does not require an EmbeddedServiceConfig or an Experience Cloud site. The channel is a direct integration between Apple's Business Chat infrastructure and Salesforce's Messaging platform. Salesforce handles the webhook registration during channel setup.

Key metadata relationships:

- `MessagingChannel.MessageType` = `AppleBusinessChat`
- `ServiceChannel.DeveloperName` = `sfdc_livemessage`
- `Queue.SobjectType` = `MessagingSession`
- No 24-hour messaging window (unlike WhatsApp/Messenger) -- Apple allows ongoing conversations

### Configure Your Variables

**Variable Reference** — replace these placeholders in commands below:

| Variable | Description | Example |
|----------|-------------|---------|
| `{{ORG_ALIAS}}` | Salesforce org alias or username | `<your-org-alias>` |
| `{{AGENT_NAME}}` | Service Agent developer name | `Reservation_Agent` |
| `{{CHANNEL_NAME}}` | Messaging Channel developer name | `AgentforceChannel` |
| `{{INSTANCE_URL}}` | Salesforce instance URL | `https://myorg.my.salesforce.com` |
| `{{ACCESS_TOKEN}}` | OAuth access token | `YOUR_ACCESS_TOKEN` |

## Implementation Steps

## 1. Verify Your Starting Point

Confirm your agent exists, your org has the required licenses, and you have admin access.

### Confirm your Service Agent exists and is active

Before building the routing chain, verify your Agentforce Service Agent is deployed and active in the target org.

**Choose one of the following methods:**

#### SF CLI

```bash
sf data query \
  --target-org {{ORG_ALIAS}} \
  --query "SELECT Id, DeveloperName, MasterLabel, AgentType FROM BotDefinition WHERE DeveloperName='{{AGENT_NAME}}' AND IsActive=true"
```

#### REST API

```bash
curl -X GET "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id,DeveloperName,MasterLabel,AgentType+FROM+BotDefinition+WHERE+DeveloperName='{{AGENT_NAME}}'+AND+IsActive=true" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" \
  -H "Content-Type: application/json"
```

#### Browser Agent

```text
1. Navigate to Setup > Agents > Agent Builder
2. Find your agent by name: {{AGENT_NAME}}
3. Verify the status shows Active
4. Copy the Agent ID for later use
```

**Verify the agent is active:**

✅ **Verify:**

Expected result: One record returned with a valid Id.

If no record is returned, activate your agent in Agent Builder before proceeding.

### Authenticate and verify org access

Ensure you have admin access to the target org and the CLI is connected.

**Choose one of the following methods:**

#### SF CLI

```bash
sf org login web --alias {{ORG_ALIAS}} --instance-url https://login.salesforce.com
sf org display --target-org {{ORG_ALIAS}} --verbose
```

#### REST API

```bash
# Use OAuth 2.0 web server flow to obtain access token
# Then verify with:
curl -X GET "{{INSTANCE_URL}}/services/data/v64.0/limits" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}"
```

#### Browser Agent

```text
1. Log into your Salesforce org at https://login.salesforce.com
2. Verify you see Setup in the gear menu (admin access required)
3. Navigate to Setup > Company Information to confirm org details
```

**Verify org access:**

✅ **Verify:**

Expected result: Org display shows your username and instance URL without errors.

### Verify Digital Engagement license

Apple Messages for Business channels require the Digital Engagement add-on. Confirm it is provisioned in your org.

**Choose one of the following methods:**

#### SF CLI

```bash
sf data query \
  --target-org {{ORG_ALIAS}} \
  --query "SELECT Id, Name, Status, IsProvisioned FROM PackageLicense WHERE NamespacePrefix='sfdc_livemessage'"
```

#### REST API

```bash
curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id,Name,Status,IsProvisioned+FROM+PackageLicense+WHERE+NamespacePrefix='sfdc_livemessage'" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}"
```

#### Browser Agent

```text
1. Navigate to Setup > Company Information > Permission Set Licenses
2. Look for "Digital Engagement" or "Messaging" in the list
3. Confirm it shows as Active with available licenses
```

**Verify Digital Engagement license:**

✅ **Verify:**

Expected result: One record returned with IsProvisioned = true.

If not found, contact your Salesforce account executive to provision Digital Engagement.

## 2. Enable Messaging and Omni-Channel

Enable the org-level settings required for Apple Messages for Business.

### Enable Messaging in Setup

Messaging must be enabled at the org level before any messaging channels can be created.

**Choose one of the following methods:**

#### SF CLI

```bash
# Deploy Messaging settings via metadata
mkdir -p force-app/main/default/settings

cat > force-app/main/default/settings/OmniChannel.settings-meta.xml << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<OmniChannelSettings xmlns="http://soap.sforce.com/2006/04/metadata">
    <enableOmniChannel>true</enableOmniChannel>
</OmniChannelSettings>
EOF

sf project deploy start \
  --target-org {{ORG_ALIAS}} \
  --source-dir force-app/main/default/settings/OmniChannel.settings-meta.xml
```

#### REST API

```bash
# Messaging and Omni-Channel settings are typically enabled via
# Metadata API or Setup UI. See Browser method.
```

#### Browser Agent

```text
1. Navigate to Setup > Messaging Settings
2. If you see a toggle for Messaging, enable it
3. Navigate to Setup > Omni-Channel Settings
4. Check "Enable Omni-Channel"
5. Click Save
```

**Verify messaging is enabled:**

✅ **Verify:**

Expected result: Messaging Settings page loads without errors and shows channel management options. Omni-Channel Settings shows "Omni-Channel is enabled."

## 3. Register with Apple Business Register

Before creating the channel in Salesforce, you must register your business with Apple and get your account approved.

> **⚠️ Warning**: Apple reviews and approves every business account. The approval process can take **days to weeks** depending on your business type and verification requirements. Start this step early. You cannot create the Salesforce channel until Apple approves your account.

### Create your Apple Business Register account

**Choose one of the following methods:**

#### SF CLI

```bash
# Apple Business Register setup is browser-based.
# No CLI equivalent. See Browser method for complete steps.
```

#### REST API

```bash
# Apple Business Register setup requires browser access.
# See Browser method for complete steps.
```

#### Browser Agent

```text
1. Go to https://register.apple.com/business
2. Sign in with your Apple Business account (or create one)
3. Register your business:
   a. Enter your business name, website, and contact information
   b. Select your business category
   c. Provide your customer support contact details
4. Plan your customer journey:
   a. Define your entry points (Maps, Safari, Spotlight, Siri, custom)
   b. Specify the intents you want to handle (sales, support, scheduling)
   c. Configure business hours and messaging availability
5. Select Salesforce as your Messaging Service Provider (MSP)
6. Complete the Apple review process:
   - Apple will verify your business identity
   - Approval timeline varies: days for established brands, weeks for new registrations
   - You will receive an email when approved
7. After approval, note your Apple Business Chat Account ID
```

> **ℹ️ Info**: Apple provides test capabilities during the review process. You can use the Apple Business Chat sandbox to test your integration before going live. Ask your Apple Business representative about sandbox access.

**Verify Apple Business Register access:**

✅ **Verify:**

Expected result: You have an approved Apple Business Register account with Salesforce selected as your Messaging Service Provider.

### Configure entry points for your business

Entry points determine how customers discover and start conversations with your business on Apple devices.

**Choose one of the following methods:**

#### SF CLI

```bash
# Entry point configuration is managed in Apple Business Register.
# See Browser method.
```

#### REST API

```bash
# Entry point configuration is managed in Apple Business Register.
# See Browser method.
```

#### Browser Agent

```text
Entry points where customers can start a conversation:

1. Apple Maps:
   - Ensure your business location is claimed on Apple Maps Connect
   - The Messages button appears automatically once channel is active

2. Safari / Spotlight:
   - Apple indexes your business for Spotlight and Safari suggestions
   - Customers see a Messages button in search results

3. Siri:
   - Customers can say "Message [your business name]"
   - Requires your business to be registered in Apple Maps

4. Custom entry points (your website or app):
   - Use Apple's Business Chat Button API
   - Add a "Message on iMessage" button to your website or iOS app
   - Button URL format: https://bcrw.apple.com/urn:biz:YOUR_BUSINESS_ID
   - See Apple developer documentation for button styling guidelines
```

**Verify entry point configuration:**

✅ **Verify:**

Expected result: You have identified and configured at least one entry point for customers to reach your business via Apple Messages.

## 4. Create the Apple Messages Channel in Salesforce

Use the Salesforce Setup wizard to create the Apple Messages for Business channel. Salesforce handles the webhook connection to Apple automatically during setup.

### Create the channel via Setup wizard

**Choose one of the following methods:**

#### SF CLI

```bash
# The Apple Messages channel must be created via the Setup wizard.
# After creation, verify with:
sf data query \
  --target-org {{ORG_ALIAS}} \
  --query "SELECT Id, DeveloperName, MasterLabel, MessageType, IsActive, MessagingPlatformKey FROM MessagingChannel WHERE MessageType='AppleBusinessChat'"
```

#### REST API

```bash
# Apple Messages channel creation requires the Setup wizard.
# After creation, verify with:
curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id,DeveloperName,MasterLabel,MessageType,IsActive,MessagingPlatformKey+FROM+MessagingChannel+WHERE+MessageType='AppleBusinessChat'" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}"
```

#### Browser Agent

```text
1. Navigate to Setup > Messaging Settings
2. Click New Channel
3. Select Apple Messages for Business as the channel type
4. Enter your Apple Business Chat Account ID from Step 3
5. Set the Channel Name: {{CHANNEL_NAME}}
6. Salesforce registers the webhook with Apple automatically
7. Review the configuration summary
8. Click Finish to create the channel
9. The channel will show as Active in Messaging Settings
```

**Verify channel was created:**

✅ **Verify:**

Expected result: Query returns one record with MessageType = 'AppleBusinessChat' and IsActive = true.

### Verify channel details and note key fields

After creation, confirm the channel is properly configured with the correct platform key and message type.

**Choose one of the following methods:**

#### SF CLI

```bash
sf data query \
  --target-org {{ORG_ALIAS}} \
  --query "SELECT Id, DeveloperName, MasterLabel, MessageType, IsActive, MessagingPlatformKey, SessionHandlerId, FallbackQueueId FROM MessagingChannel WHERE DeveloperName='{{CHANNEL_NAME}}'"
```

#### REST API

```bash
curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id,DeveloperName,MasterLabel,MessageType,IsActive,MessagingPlatformKey,SessionHandlerId,FallbackQueueId+FROM+MessagingChannel+WHERE+DeveloperName='{{CHANNEL_NAME}}'" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}"
```

#### Browser Agent

```text
1. Navigate to Setup > Messaging Settings
2. Click on your Apple Messages channel: {{CHANNEL_NAME}}
3. Note the following fields:
   - Channel Name / DeveloperName
   - Message Type (should be AppleBusinessChat)
   - Platform Key (your Apple Business Chat Account ID)
   - Status (should be Active)
```

**Verify channel configuration:**

✅ **Verify:**

Expected result: Channel record returned with MessageType = 'AppleBusinessChat', IsActive = true, and MessagingPlatformKey matching your Apple Business Chat Account ID.

## 5. Configure Routing to Your Agent

Set up the routing so inbound Apple Messages conversations are handled by your Agentforce Service Agent.

### Create a messaging queue for fallback routing

The queue handles messages when the agent is unavailable. Even with direct agent routing, a fallback queue is required.

**Choose one of the following methods:**

#### SF CLI

```bash
# Check if queue exists
sf data query --target-org {{ORG_ALIAS}} \
  --query "SELECT Id FROM Group WHERE Type='Queue' AND DeveloperName='{{CHANNEL_NAME}}_Queue'"

# If not found, create via Metadata API
mkdir -p force-app/main/default/queues

cat > force-app/main/default/queues/{{CHANNEL_NAME}}_Queue.queue-meta.xml << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<Queue xmlns="http://soap.sforce.com/2006/04/metadata">
    <fullName>{{CHANNEL_NAME}}_Queue</fullName>
    <name>{{CHANNEL_NAME}} Queue</name>
    <queueSobject>
        <sobjectType>MessagingSession</sobjectType>
    </queueSobject>
</Queue>
EOF

sf project deploy start --target-org {{ORG_ALIAS}} --metadata Queue:{{CHANNEL_NAME}}_Queue
```

#### REST API

```bash
# Create queue
curl -X POST "{{INSTANCE_URL}}/services/data/v64.0/sobjects/Group" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" \
  -H "Content-Type: application/json" \
  -d '{
    "Name": "{{CHANNEL_NAME}} Queue",
    "DeveloperName": "{{CHANNEL_NAME}}_Queue",
    "Type": "Queue"
  }'

# Add MessagingSession to queue
QUEUE_ID=$(curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id+FROM+Group+WHERE+DeveloperName='{{CHANNEL_NAME}}_Queue'" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" | jq -r '.records[0].Id')

curl -X POST "{{INSTANCE_URL}}/services/data/v64.0/sobjects/QueueSobject" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" \
  -H "Content-Type: application/json" \
  -d "{\"QueueId\": \"$QUEUE_ID\", \"SobjectType\": \"MessagingSession\"}"
```

#### Browser Agent

```text
1. Navigate to Setup > Queues
2. Click New
3. Set Label: {{CHANNEL_NAME}} Queue
4. Set Queue Name: {{CHANNEL_NAME}}_Queue
5. In Supported Objects, add Messaging Session
6. Add queue members (agents who will handle escalations)
7. Click Save
```

**Verify queue creation:**

✅ **Verify:**

Expected result: One queue record returned with DeveloperName = '{{CHANNEL_NAME}}\_Queue'.

### Route the channel to your Agentforce agent

Set the SessionHandlerId on the MessagingChannel to point to your agent's BotDefinition. This enables direct agent routing for all inbound Apple Messages conversations.

**Choose one of the following methods:**

#### SF CLI

```bash
# Get the Bot ID and Queue ID
BOT_ID=$(sf data query --target-org {{ORG_ALIAS}} --json \
  --query "SELECT Id FROM BotDefinition WHERE DeveloperName='{{AGENT_NAME}}'" \
  | jq -r '.result.records[0].Id')

QUEUE_ID=$(sf data query --target-org {{ORG_ALIAS}} --json \
  --query "SELECT Id FROM Group WHERE Type='Queue' AND DeveloperName='{{CHANNEL_NAME}}_Queue'" \
  | jq -r '.result.records[0].Id')

CHANNEL_ID=$(sf data query --target-org {{ORG_ALIAS}} --json \
  --query "SELECT Id FROM MessagingChannel WHERE DeveloperName='{{CHANNEL_NAME}}'" \
  | jq -r '.result.records[0].Id')

# Update the channel with agent routing
sf apex run --target-org {{ORG_ALIAS}} << EOF
MessagingChannel channel = [SELECT Id FROM MessagingChannel WHERE DeveloperName = '{{CHANNEL_NAME}}' LIMIT 1];
channel.SessionHandlerId = '$BOT_ID';
channel.FallbackQueueId = '$QUEUE_ID';
update channel;
System.debug('Updated MessagingChannel: ' + channel.Id + ' with SessionHandlerId: $BOT_ID');
EOF
```

#### REST API

```bash
BOT_ID=$(curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id+FROM+BotDefinition+WHERE+DeveloperName='{{AGENT_NAME}}'" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" | jq -r '.records[0].Id')

QUEUE_ID=$(curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id+FROM+Group+WHERE+Type='Queue'+AND+DeveloperName='{{CHANNEL_NAME}}_Queue'" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" | jq -r '.records[0].Id')

CHANNEL_ID=$(curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id+FROM+MessagingChannel+WHERE+DeveloperName='{{CHANNEL_NAME}}'" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" | jq -r '.records[0].Id')

curl -X PATCH "{{INSTANCE_URL}}/services/data/v64.0/sobjects/MessagingChannel/$CHANNEL_ID" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" \
  -H "Content-Type: application/json" \
  -d "{
    \"SessionHandlerId\": \"$BOT_ID\",
    \"FallbackQueueId\": \"$QUEUE_ID\"
  }"
```

#### Browser Agent

```text
1. Navigate to Setup > Messaging Settings
2. Click on your Apple Messages channel: {{CHANNEL_NAME}}
3. Under Routing Configuration:
   a. Set Routing Type to "Agentforce Service Agent"
   b. Select your agent: {{AGENT_NAME}}
   c. Set Fallback Queue: {{CHANNEL_NAME}} Queue
4. Click Save
```

**Verify agent routing:**

✅ **Verify:**

Expected result: Query the channel and confirm SessionHandlerId matches your BotDefinition.Id.

> **💡 Tip**: If `SessionHandlerId` is not available in your org's API version, configure routing through an Omni-Channel inbound flow instead. Create a flow with the Route Work action: Service Channel = `sfdc_livemessage`, Route To = your Agentforce Service Agent.

## 6. Test and Verify

Send a test message and confirm the full pipeline works end-to-end.

### Send a test message via Apple Messages

Use an Apple device (iPhone, iPad, or Mac) to send a message to your business and verify the agent responds.

**Choose one of the following methods:**

#### SF CLI

```bash
# After sending a test message from your Apple device, query for the session:
sf data query --target-org {{ORG_ALIAS}} \
  --query "SELECT Id, Status, AgentType, OwnerId, CreatedDate, MessagingChannelId FROM MessagingSession WHERE MessagingChannel.DeveloperName='{{CHANNEL_NAME}}' ORDER BY CreatedDate DESC LIMIT 5"
```

#### REST API

```bash
curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id,Status,AgentType,OwnerId,CreatedDate,MessagingChannelId+FROM+MessagingSession+WHERE+MessagingChannel.DeveloperName='{{CHANNEL_NAME}}'+ORDER+BY+CreatedDate+DESC+LIMIT+5" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}"
```

#### Browser Agent

```text
1. On an iPhone, iPad, or Mac, open one of the following entry points:
   - Apple Maps: Search for your business name, tap the Messages icon
   - Safari: Visit your website with a Business Chat button
   - Spotlight: Search for your business name, tap the Messages suggestion
   - Siri: Say "Message [your business name]"
2. Send a test message: "Hello, I need help"
3. Wait 5-10 seconds for the agent to respond
4. Verify you receive a response from the agent in the Messages app
5. Try a second message to confirm multi-turn conversation works
```

> **ℹ️ Info**: Apple Messages for Business only works on Apple devices. You cannot test from Android phones or non-Apple computers. Ensure your test device is running a recent version of iOS, iPadOS, or macOS.

**Verify the agent responds:**

✅ **Verify:**

Expected result: Agent responds to Apple Messages conversation. MessagingSession record created with Status = 'Active' or 'Ended'.

### Verify agent routing and session records

Confirm messages are being routed to your agent by checking MessagingSession and AgentWork records.

**Choose one of the following methods:**

#### SF CLI

```bash
# Check recent MessagingSessions for Apple Messages
sf data query --target-org {{ORG_ALIAS}} \
  --query "SELECT Id, Status, AgentType, OwnerId, CreatedDate FROM MessagingSession WHERE MessagingChannel.MessageType='AppleBusinessChat' ORDER BY CreatedDate DESC LIMIT 5"

# Check AgentWork records
sf data query --target-org {{ORG_ALIAS}} \
  --query "SELECT Id, WorkItemId, BotId, RoutingType, CreatedDate FROM AgentWork ORDER BY CreatedDate DESC LIMIT 5"

# Check individual messages in the session
sf data query --target-org {{ORG_ALIAS}} \
  --query "SELECT Id, MessageText, SenderType, CreatedDate FROM MessagingEvent WHERE MessagingSession.MessagingChannel.DeveloperName='{{CHANNEL_NAME}}' ORDER BY CreatedDate DESC LIMIT 10"
```

#### REST API

```bash
curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id,Status,AgentType,OwnerId,CreatedDate+FROM+MessagingSession+WHERE+MessagingChannel.MessageType='AppleBusinessChat'+ORDER+BY+CreatedDate+DESC+LIMIT+5" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}"

curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id,WorkItemId,BotId,RoutingType,CreatedDate+FROM+AgentWork+ORDER+BY+CreatedDate+DESC+LIMIT+5" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}"
```

#### Browser Agent

```text
1. Navigate to the App Launcher > Messaging Sessions
2. Find the most recent session
3. Click to view details
4. Verify:
   - Owner matches your agent
   - Message Type shows AppleBusinessChat
   - Status is Active or Ended
5. Check the Messaging Events related list for individual messages
```

**Verify messaging session records:**

✅ **Verify:**

Expected result: Recent MessagingSession exists with AgentType populated and messages visible in MessagingEvent records.

## Apple Messages-Specific Capabilities

Apple Messages for Business supports rich interactive content beyond plain text:

| Capability                  | Description                                                          |
| --------------------------- | -------------------------------------------------------------------- |
| List Pickers                | Present a scrollable list of options for the customer to select from |
| Time Pickers                | Let customers choose an available appointment time slot              |
| Apple Pay                   | Request payments directly within the conversation                    |
| Authentication              | Verify customer identity using Apple's authentication framework      |
| Custom Interactive Messages | Build custom iMessage app extensions for rich UI experiences         |
| Secure Forms                | Collect structured data securely within the conversation             |
| Images and Attachments      | Send and receive images, documents, and other media                  |

> **💡 Tip**: Rich interactive content (list pickers, time pickers, Apple Pay) requires additional configuration via Messaging Components in Salesforce. Start with text-based conversations and add rich content incrementally.

## Troubleshooting

### Messages not reaching agent

1. Verify the MessagingChannel is Active: query `IsActive` field
2. Confirm `SessionHandlerId` is set and points to the correct BotDefinition.Id
3. Check the agent is Active (not Draft) in Agent Builder
4. Verify Omni-Channel is enabled in Setup
5. Check that the `sfdc_livemessage` Service Channel exists (auto-created with Digital Engagement)

### Apple Business Register account not approved

1. Apple reviews every business account manually. Typical approval timelines:
   - Established brands with verified web presence: 2-5 business days
   - New or small businesses: 1-3 weeks
   - Businesses requiring additional verification: up to 4 weeks
2. Ensure your business information on register.apple.com matches your public web presence
3. Verify your D-U-N-S number is correct if Apple requests business verification
4. Contact Apple Business support if approval is delayed beyond expected timelines

### Channel creation fails in Setup wizard

1. Verify the Digital Engagement license is provisioned and active
2. Confirm your Apple Business Register account is approved and shows Active status
3. Ensure Salesforce is selected as the Messaging Service Provider in Apple Business Register
4. Check that your Apple Business Chat Account ID is correct
5. Clear browser cache and retry if the Setup wizard encounters errors

### Customer cannot find your business in Apple Maps or Safari

1. Verify your business location is claimed and verified on Apple Maps Connect (mapsconnect.apple.com)
2. Ensure your Apple Business Register account entry points include Maps and Safari
3. It can take 24-48 hours after channel activation for entry points to appear in Apple services
4. Check that your business name and address match between Apple Maps Connect and Apple Business Register

### Agent not responding but session is created

1. Check `SessionHandlerId` versus `FallbackQueueId` routing. If SessionHandlerId is empty, messages route to the queue instead of the agent
2. Verify the agent's default_agent_user has proper permissions (Einstein Agent User profile, AgentforceServiceAgentUser permission set)
3. Check for errors in the agent's trace logs via Agent Builder preview
4. Confirm the BotDefinition is published and the active BotVersion is correct:

```bash
sf data query --target-org {{ORG_ALIAS}} \
  --query "SELECT Id, DeveloperName, Status FROM BotVersion WHERE BotDefinition.DeveloperName='{{AGENT_NAME}}' AND Status='Active' LIMIT 1"
```

## Definition of Done

**Definition of Done:**

✅ **Verify:**

- Apple Messages channel active in Messaging Settings with MessageType = AppleBusinessChat
- Customer can message your business from an Apple device (Maps, Safari, Spotlight, or Siri)
- Agent responds to the message automatically via iMessage
- MessagingSession record created with correct routing to agent
- Escalation to human agent queue works when agent transfers

## LINE Setup

# LINE Setup (30 min)

## Outcome

A working LINE-to-Agentforce pipeline where customers in Japan and other LINE-dominant markets can message your business and receive automated responses from your Service Agent.

When complete, you'll have:

- A LINE Messaging Channel connected to your Salesforce org via the LINE Messaging API
- Omni-Channel routing configured to send inbound messages to your Agentforce agent
- A webhook verified between LINE and Salesforce for real-time message delivery
- Verified end-to-end message flow from LINE to agent and back

> **ℹ️ Info**: LINE channels require the **Digital Engagement** add-on license and a **LINE Official Account** with the **Messaging API** enabled. Unlike Enhanced Chat, no Experience Cloud site or EmbeddedServiceConfig is needed. LINE is the dominant messaging platform in Japan, Taiwan, and Thailand.

## Architecture

When a customer sends a LINE message to your Official Account, the message flows through the following chain:

*(Architecture diagram — see the live page for the visual)*

The key routing field is `sessionHandlerType = AgentforceServiceAgent` on the MessagingChannel, with `sessionHandlerAsa` pointing to the BotDefinition DeveloperName. The `sessionHandlerQueue` field specifies the fallback queue when the agent is unavailable.

Key metadata relationships:

- `MessagingChannel.MessageType` = `Line`
- `ServiceChannel.DeveloperName` = `sfdc_livemessage`
- `Queue.SobjectType` = `MessagingSession`
- No 24-hour messaging window restriction (unlike WhatsApp and Messenger)

Unlike WhatsApp and Messenger, LINE does not require a Meta Business Manager account. The integration uses credentials from the LINE Developers Console (Channel ID, Channel Secret, Channel Access Token) and a webhook URL that Salesforce generates after channel creation.

### Configure Your Variables

**Variable Reference** — replace these placeholders in commands below:

| Variable | Description | Example |
|----------|-------------|---------|
| `{{ORG_ALIAS}}` | Salesforce org alias or username | `<your-org-alias>` |
| `{{AGENT_NAME}}` | Service Agent developer name | `Reservation_Agent` |
| `{{CHANNEL_NAME}}` | Messaging Channel developer name | `AgentforceChannel` |
| `{{INSTANCE_URL}}` | Salesforce instance URL | `https://myorg.my.salesforce.com` |
| `{{ACCESS_TOKEN}}` | OAuth access token | `YOUR_ACCESS_TOKEN` |

## Implementation Steps

## 1. Verify Your Starting Point

Confirm your agent exists, your org has the required licenses, and you have admin access.

### Confirm your Service Agent exists and is active

Before building the routing chain, verify your Agentforce Service Agent is deployed and active in the target org.

**Choose one of the following methods:**

#### SF CLI

```bash
sf data query \
  --target-org {{ORG_ALIAS}} \
  --query "SELECT Id, DeveloperName, MasterLabel, AgentType FROM BotDefinition WHERE DeveloperName='{{AGENT_NAME}}' AND IsActive=true"
```

#### REST API

```bash
curl -X GET "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id,DeveloperName,MasterLabel,AgentType+FROM+BotDefinition+WHERE+DeveloperName='{{AGENT_NAME}}'+AND+IsActive=true" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" \
  -H "Content-Type: application/json"
```

#### Browser Agent

```text
1. Navigate to Setup > Agents > Agent Builder
2. Find your agent by name: {{AGENT_NAME}}
3. Verify the status shows Active
4. Copy the Agent ID for later use
```

**Verify the agent is active:**

✅ **Verify:**

Expected result: One record returned with a valid Id.

If no record is returned, activate your agent in Agent Builder before proceeding.

### Authenticate and verify org access

Ensure you have admin access to the target org and the CLI is connected.

**Choose one of the following methods:**

#### SF CLI

```bash
sf org login web --alias {{ORG_ALIAS}} --instance-url https://login.salesforce.com
sf org display --target-org {{ORG_ALIAS}} --verbose
```

#### REST API

```bash
# Use OAuth 2.0 web server flow to obtain access token
# Then verify with:
curl -X GET "{{INSTANCE_URL}}/services/data/v64.0/limits" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}"
```

#### Browser Agent

```text
1. Log into your Salesforce org at https://login.salesforce.com
2. Verify you see Setup in the gear menu (admin access required)
3. Navigate to Setup > Company Information to confirm org details
```

**Verify org access:**

✅ **Verify:**

Expected result: Org display shows your username and instance URL without errors.

### Verify Digital Engagement license

LINE channels require the Digital Engagement add-on. Confirm it is provisioned in your org.

**Choose one of the following methods:**

#### SF CLI

```bash
sf data query \
  --target-org {{ORG_ALIAS}} \
  --query "SELECT Id, Name, Status, IsProvisioned FROM PackageLicense WHERE NamespacePrefix='sfdc_livemessage'"
```

#### REST API

```bash
curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id,Name,Status,IsProvisioned+FROM+PackageLicense+WHERE+NamespacePrefix='sfdc_livemessage'" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}"
```

#### Browser Agent

```text
1. Navigate to Setup > Company Information > Permission Set Licenses
2. Look for "Digital Engagement" or "Messaging" in the list
3. Confirm it shows as Active with available licenses
```

**Verify Digital Engagement license:**

✅ **Verify:**

Expected result: One record returned with IsProvisioned = true.

If not found, contact your Salesforce account executive to provision Digital Engagement.

## 2. Enable Messaging and Omni-Channel

Enable the org-level settings required for LINE messaging.

### Enable Messaging in Setup

Messaging must be enabled at the org level before any messaging channels can be created.

**Choose one of the following methods:**

#### SF CLI

```bash
# Deploy Messaging settings via metadata
mkdir -p force-app/main/default/settings

cat > force-app/main/default/settings/OmniChannel.settings-meta.xml << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<OmniChannelSettings xmlns="http://soap.sforce.com/2006/04/metadata">
    <enableOmniChannel>true</enableOmniChannel>
</OmniChannelSettings>
EOF

sf project deploy start \
  --target-org {{ORG_ALIAS}} \
  --source-dir force-app/main/default/settings/OmniChannel.settings-meta.xml
```

#### REST API

```bash
# Messaging and Omni-Channel settings are typically enabled via
# Metadata API or Setup UI. See Browser method.
```

#### Browser Agent

```text
1. Navigate to Setup > Messaging Settings
2. If you see a toggle for Messaging, enable it
3. Navigate to Setup > Omni-Channel Settings
4. Check "Enable Omni-Channel"
5. Click Save
```

**Verify messaging is enabled:**

✅ **Verify:**

Expected result: Messaging Settings page loads without errors and shows channel management options. Omni-Channel Settings shows "Omni-Channel is enabled."

## 3. Set Up LINE Official Account and Messaging API

Create a LINE Official Account and enable the Messaging API to obtain the credentials needed for the Salesforce integration.

> **ℹ️ Info**: LINE Official Accounts are free to create. The free tier includes a limited number of messages per month. For higher volume, upgrade to the Light or Standard plan in the LINE Official Account Manager.

### Create a LINE Official Account

If you do not already have a LINE Official Account, create one before proceeding.

**Choose one of the following methods:**

#### SF CLI

```bash
# LINE Official Account setup is done in the LINE platform.
# No CLI equivalent. See Browser method for complete steps.
```

#### REST API

```bash
# LINE Official Account setup is done in the LINE platform.
# See Browser method for complete steps.
```

#### Browser Agent

```text
1. Go to https://manager.line.biz and sign in with your LINE account
2. If you do not have a LINE Official Account, click "Create a LINE Official Account"
3. Enter your business name, category, and region
4. Complete the registration
5. Note the account name for reference
```

**Verify LINE account access:**

✅ **Verify:**

Expected result: You can log into the LINE Official Account Manager and see your account listed.

### Enable the Messaging API and create a provider

The Messaging API must be enabled on your LINE Official Account to integrate with Salesforce. This creates a channel in the LINE Developers Console.

**Choose one of the following methods:**

#### SF CLI

```bash
# Messaging API enablement is done in the LINE platform.
# No CLI equivalent. See Browser method for complete steps.
```

#### REST API

```bash
# Messaging API enablement is done in the LINE platform.
# See Browser method for complete steps.
```

#### Browser Agent

```text
1. In the LINE Official Account Manager, go to Settings > Messaging API
2. Click "Enable Messaging API"
3. When prompted, create or select a Provider:
   a. A Provider is a developer entity (your company name)
   b. If you do not have one, click "Create a new provider"
   c. Enter your company or project name
4. Confirm to enable the Messaging API
5. After enabling, note these values from the LINE Developers Console
   (https://developers.line.biz):
   a. Navigate to your Provider > your Messaging API channel
   b. Channel Name: shown at the top of the channel settings
   c. Channel ID: shown in the Basic Settings tab
   d. Channel Secret: shown in the Basic Settings tab
   e. Channel Access Token: go to the Messaging API tab, click "Issue"
      to generate a long-lived token
6. Save these four values — you will enter them in Salesforce in Step 4
```

> **⚠️ Warning**: Keep your Channel Secret and Channel Access Token secure. These credentials grant full access to send messages as your LINE Official Account. Do not commit them to version control or share them in public channels.

**Verify LINE credentials:**

✅ **Verify:**

Expected result: You have all four LINE credentials: Channel Name, Channel ID, Channel Secret, and Channel Access Token.

### Disable auto-reply and greeting messages

LINE Official Accounts have auto-reply and greeting messages enabled by default. These conflict with Agentforce agent responses and should be disabled.

**Choose one of the following methods:**

#### SF CLI

```bash
# Auto-reply settings are configured in the LINE platform.
# No CLI equivalent. See Browser method.
```

#### REST API

```bash
# Auto-reply settings are configured in the LINE platform.
# See Browser method.
```

#### Browser Agent

```text
1. In the LINE Official Account Manager, go to Settings > Response Settings
2. Set "Auto-response" to Disabled
3. Set "Greeting message" to Disabled
4. These settings prevent LINE from sending its own responses that
   would conflict with your Agentforce agent's replies
5. Click Save
```

> **💡 Tip**: If you leave auto-reply enabled, customers will receive both the LINE auto-response and the Agentforce agent response, creating a confusing experience.

**Verify auto-response disabled:**

✅ **Verify:**

Expected result: Auto-response and greeting message are both disabled in LINE Official Account Manager > Settings > Response Settings.

## 4. Create the LINE Messaging Channel

Use the Salesforce Setup wizard to create the LINE channel by entering the credentials from Step 3.

### Create the channel in Salesforce

Unlike WhatsApp and Messenger, the LINE channel does not require an OAuth flow. You enter the LINE credentials directly in the Setup wizard.

**Choose one of the following methods:**

#### SF CLI

```bash
# The LINE channel must be created via the Setup wizard.
# After creation, verify with:
sf data query \
  --target-org {{ORG_ALIAS}} \
  --query "SELECT Id, DeveloperName, MasterLabel, MessageType, IsActive FROM MessagingChannel WHERE MessageType='Line'"
```

#### REST API

```bash
# LINE channel creation requires the Setup wizard.
# After creation, verify with:
curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id,DeveloperName,MasterLabel,MessageType,IsActive+FROM+MessagingChannel+WHERE+MessageType='Line'" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}"
```

#### Browser Agent

```text
1. Navigate to Setup > Messaging Settings
2. Click New Channel
3. Select LINE as the channel type
4. Enter the LINE credentials:
   a. Channel Name: {{CHANNEL_NAME}}
   b. Channel ID: (from LINE Developers Console > Basic Settings)
   c. Channel Secret: (from LINE Developers Console > Basic Settings)
   d. Channel Access Token: (from LINE Developers Console > Messaging API tab)
5. Choose routing method:
   - "Create new queue" to have Salesforce create a queue automatically
   - "Set up routing later" if you want to configure Omni-Channel flow routing
6. Click Finish to create the channel
7. After creation, copy the Webhook URL from the channel record
   (displayed in the channel details page)
```

**Verify channel was created:**

✅ **Verify:**

Expected result: Query returns one record with MessageType = 'Line'.

Note the Webhook URL from the channel details page -- you will need it in the next step.

### Register the webhook URL in LINE Developers Console

Salesforce generates a webhook URL after channel creation. This URL must be registered in LINE so that inbound messages are forwarded to your Salesforce org.

**Choose one of the following methods:**

#### SF CLI

```bash
# Webhook registration is done in the LINE Developers Console.
# No CLI equivalent. See Browser method.
```

#### REST API

```bash
# Webhook registration is done in the LINE Developers Console.
# See Browser method.
```

#### Browser Agent

```text
1. Go to https://developers.line.biz
2. Navigate to your Provider > your Messaging API channel
3. Click the Messaging API tab
4. In the Webhook settings section:
   a. Paste the Webhook URL copied from Salesforce into the Webhook URL field
   b. Click Update
   c. Click Verify to test the connection
   d. You should see "Success" — this confirms LINE can reach your Salesforce org
5. Enable the "Use webhook" toggle
6. Confirm the toggle is set to On
```

> **⚠️ Warning**: If the webhook verification fails, check that your Salesforce org is accessible from the public internet (not behind a VPN or firewall that blocks LINE's servers). Sandbox orgs with My Domain restrictions may need additional configuration.

**Verify webhook configuration:**

✅ **Verify:**

Expected result: LINE Developers Console shows "Use webhook" toggle enabled and the Verify button returns "Success."

### Complete channel activation in Salesforce

After the webhook is verified in LINE, return to Salesforce to complete the channel activation.

**Choose one of the following methods:**

#### SF CLI

```bash
# Verify channel is active
sf data query \
  --target-org {{ORG_ALIAS}} \
  --query "SELECT Id, DeveloperName, MasterLabel, MessageType, IsActive, SessionHandlerId, FallbackQueueId FROM MessagingChannel WHERE DeveloperName='{{CHANNEL_NAME}}'"
```

#### REST API

```bash
curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id,DeveloperName,MasterLabel,MessageType,IsActive,SessionHandlerId,FallbackQueueId+FROM+MessagingChannel+WHERE+DeveloperName='{{CHANNEL_NAME}}'" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}"
```

#### Browser Agent

```text
1. Navigate to Setup > Messaging Settings
2. Click on your LINE channel: {{CHANNEL_NAME}}
3. Complete any items in the Channel Activation Checklist
4. Click Activate Channel
5. Verify the channel status shows Active
```

**Verify channel is active:**

✅ **Verify:**

Expected result: Channel record returned with MessageType = 'Line' and IsActive = true.

## 5. Configure Routing to Your Agent

Set up the routing so inbound LINE messages are handled by your Agentforce Service Agent.

### Create a messaging queue for fallback routing

The queue handles messages when the agent is unavailable. Even with direct agent routing, a fallback queue is required.

**Choose one of the following methods:**

#### SF CLI

```bash
# Check if queue exists
sf data query --target-org {{ORG_ALIAS}} \
  --query "SELECT Id FROM Group WHERE Type='Queue' AND DeveloperName='{{CHANNEL_NAME}}_Queue'"

# If not found, create via Metadata API
mkdir -p force-app/main/default/queues

cat > force-app/main/default/queues/{{CHANNEL_NAME}}_Queue.queue-meta.xml << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<Queue xmlns="http://soap.sforce.com/2006/04/metadata">
    <fullName>{{CHANNEL_NAME}}_Queue</fullName>
    <name>{{CHANNEL_NAME}} Queue</name>
    <queueSobject>
        <sobjectType>MessagingSession</sobjectType>
    </queueSobject>
</Queue>
EOF

sf project deploy start --target-org {{ORG_ALIAS}} --metadata Queue:{{CHANNEL_NAME}}_Queue
```

#### REST API

```bash
# Create queue
curl -X POST "{{INSTANCE_URL}}/services/data/v64.0/sobjects/Group" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" \
  -H "Content-Type: application/json" \
  -d '{
    "Name": "{{CHANNEL_NAME}} Queue",
    "DeveloperName": "{{CHANNEL_NAME}}_Queue",
    "Type": "Queue"
  }'

# Add MessagingSession to queue
QUEUE_ID=$(curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id+FROM+Group+WHERE+DeveloperName='{{CHANNEL_NAME}}_Queue'" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" | jq -r '.records[0].Id')

curl -X POST "{{INSTANCE_URL}}/services/data/v64.0/sobjects/QueueSobject" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" \
  -H "Content-Type: application/json" \
  -d "{\"QueueId\": \"$QUEUE_ID\", \"SobjectType\": \"MessagingSession\"}"
```

#### Browser Agent

```text
1. Navigate to Setup > Queues
2. Click New
3. Set Label: {{CHANNEL_NAME}} Queue
4. Set Queue Name: {{CHANNEL_NAME}}_Queue
5. In Supported Objects, add Messaging Session
6. Add queue members (agents who will handle escalations)
7. Click Save
```

**Verify queue creation:**

✅ **Verify:**

Expected result: One queue record returned with DeveloperName = '{{CHANNEL_NAME}}\_Queue'.

### Route the channel to your Agentforce agent

Set the SessionHandlerId on the MessagingChannel to point to your agent's BotDefinition. This enables direct agent routing for all inbound LINE messages.

**Choose one of the following methods:**

#### SF CLI

```bash
# Get the Bot ID and Queue ID
BOT_ID=$(sf data query --target-org {{ORG_ALIAS}} --json \
  --query "SELECT Id FROM BotDefinition WHERE DeveloperName='{{AGENT_NAME}}'" \
  | jq -r '.result.records[0].Id')

QUEUE_ID=$(sf data query --target-org {{ORG_ALIAS}} --json \
  --query "SELECT Id FROM Group WHERE Type='Queue' AND DeveloperName='{{CHANNEL_NAME}}_Queue'" \
  | jq -r '.result.records[0].Id')

CHANNEL_ID=$(sf data query --target-org {{ORG_ALIAS}} --json \
  --query "SELECT Id FROM MessagingChannel WHERE DeveloperName='{{CHANNEL_NAME}}'" \
  | jq -r '.result.records[0].Id')

# Update the channel with agent routing
sf apex run --target-org {{ORG_ALIAS}} << EOF
MessagingChannel channel = [SELECT Id FROM MessagingChannel WHERE DeveloperName = '{{CHANNEL_NAME}}' LIMIT 1];
channel.SessionHandlerId = '$BOT_ID';
channel.FallbackQueueId = '$QUEUE_ID';
update channel;
System.debug('Updated MessagingChannel: ' + channel.Id + ' with SessionHandlerId: $BOT_ID');
EOF
```

#### REST API

```bash
BOT_ID=$(curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id+FROM+BotDefinition+WHERE+DeveloperName='{{AGENT_NAME}}'" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" | jq -r '.records[0].Id')

QUEUE_ID=$(curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id+FROM+Group+WHERE+Type='Queue'+AND+DeveloperName='{{CHANNEL_NAME}}_Queue'" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" | jq -r '.records[0].Id')

CHANNEL_ID=$(curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id+FROM+MessagingChannel+WHERE+DeveloperName='{{CHANNEL_NAME}}'" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" | jq -r '.records[0].Id')

curl -X PATCH "{{INSTANCE_URL}}/services/data/v64.0/sobjects/MessagingChannel/$CHANNEL_ID" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" \
  -H "Content-Type: application/json" \
  -d "{
    \"SessionHandlerId\": \"$BOT_ID\",
    \"FallbackQueueId\": \"$QUEUE_ID\"
  }"
```

#### Browser Agent

```text
1. Navigate to Setup > Messaging Settings
2. Click on your LINE channel: {{CHANNEL_NAME}}
3. Under Routing Configuration:
   a. Set Routing Type to "Agentforce Service Agent"
   b. Select your agent: {{AGENT_NAME}}
   c. Set Fallback Queue: {{CHANNEL_NAME}} Queue
4. Click Save
```

**Verify agent routing:**

✅ **Verify:**

Expected result: Query the channel and confirm SessionHandlerId matches your BotDefinition.Id.

> **💡 Tip**: If `SessionHandlerId` is not available in your org's API version, configure routing through an Omni-Channel inbound flow instead. Create a flow with the Route Work action: Service Channel = `sfdc_livemessage`, Route To = your Agentforce Service Agent.

## 6. Test and Verify

Send a test message and confirm the full pipeline works end-to-end.

### Send a test LINE message

Use your personal phone to send a message to the LINE Official Account and verify the agent responds.

**Choose one of the following methods:**

#### SF CLI

```bash
# After sending a test message from your phone, query for the session:
sf data query --target-org {{ORG_ALIAS}} \
  --query "SELECT Id, Status, AgentType, OwnerId, CreatedDate, MessagingChannelId FROM MessagingSession WHERE MessagingChannel.DeveloperName='{{CHANNEL_NAME}}' ORDER BY CreatedDate DESC LIMIT 5"
```

#### REST API

```bash
curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id,Status,AgentType,OwnerId,CreatedDate,MessagingChannelId+FROM+MessagingSession+WHERE+MessagingChannel.DeveloperName='{{CHANNEL_NAME}}'+ORDER+BY+CreatedDate+DESC+LIMIT+5" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}"
```

#### Browser Agent

```text
1. Open the LINE app on your phone
2. Search for your LINE Official Account by name or scan its QR code
3. Add the account as a friend if not already added
4. Send a test message: "Hello, I need help"
5. Wait 5-10 seconds for the agent to respond
6. Verify you receive a response from the agent
7. Try a second message to confirm multi-turn works
```

**Verify the agent responds:**

✅ **Verify:**

Expected result: Agent responds to LINE message. MessagingSession record created with Status = 'Active' or 'Ended'.

### Verify agent routing and session records

Confirm messages are being routed to your agent by checking MessagingSession and AgentWork records.

**Choose one of the following methods:**

#### SF CLI

```bash
# Check recent MessagingSessions for LINE
sf data query --target-org {{ORG_ALIAS}} \
  --query "SELECT Id, Status, AgentType, OwnerId, CreatedDate FROM MessagingSession WHERE MessagingChannel.MessageType='Line' ORDER BY CreatedDate DESC LIMIT 5"

# Check AgentWork records
sf data query --target-org {{ORG_ALIAS}} \
  --query "SELECT Id, WorkItemId, BotId, RoutingType, CreatedDate FROM AgentWork ORDER BY CreatedDate DESC LIMIT 5"

# Check individual messages in the session
sf data query --target-org {{ORG_ALIAS}} \
  --query "SELECT Id, MessageText, SenderType, CreatedDate FROM MessagingEvent WHERE MessagingSession.MessagingChannel.DeveloperName='{{CHANNEL_NAME}}' ORDER BY CreatedDate DESC LIMIT 10"
```

#### REST API

```bash
curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id,Status,AgentType,OwnerId,CreatedDate+FROM+MessagingSession+WHERE+MessagingChannel.MessageType='Line'+ORDER+BY+CreatedDate+DESC+LIMIT+5" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}"

curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id,WorkItemId,BotId,RoutingType,CreatedDate+FROM+AgentWork+ORDER+BY+CreatedDate+DESC+LIMIT+5" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}"
```

#### Browser Agent

```text
1. Navigate to the App Launcher > Messaging Sessions
2. Find the most recent session
3. Click to view details
4. Verify:
   - Owner matches your agent
   - Message Type shows Line
   - Status is Active or Ended
5. Check the Messaging Events related list for individual messages
```

**Verify messaging session records:**

✅ **Verify:**

Expected result: Recent MessagingSession exists with AgentType populated and messages visible in MessagingEvent records.

## LINE-Specific Constraints

| Constraint                 | Value                                                         |
| -------------------------- | ------------------------------------------------------------- |
| Messaging window           | No restriction (unlike WhatsApp/Messenger 24-hour window)     |
| Supported inbound content  | Text, images, emoji, stickers, links, videos                  |
| Supported outbound content | Text, images, questions with clickable options                |
| Consent model              | Explicit (user must add your account as a friend)             |
| Primary markets            | Japan, Taiwan, Thailand, Indonesia                            |
| Channel migration          | Channels can be moved between SF orgs (sandbox to production) |
| Routing types              | Omni-Flow (supports bots) or Omni-Queue                       |

## Troubleshooting

### Messages not reaching Salesforce

1. Verify the "Use webhook" toggle is enabled in LINE Developers Console > Messaging API tab
2. Confirm the Webhook URL in LINE matches the URL from your Salesforce channel record
3. Click Verify in LINE Developers Console to test webhook connectivity
4. Check that the MessagingChannel is Active in Setup > Messaging Settings
5. Verify the `sfdc_livemessage` Service Channel exists (auto-created with Digital Engagement)

### Webhook verification fails in LINE Developers Console

1. Ensure your Salesforce org is accessible from the public internet
2. Check that the Webhook URL was copied correctly (no trailing spaces or line breaks)
3. Sandbox orgs behind My Domain restrictions may block LINE's webhook verification requests
4. Wait 1-2 minutes after channel creation before attempting verification (webhook endpoint may take time to provision)

### LINE channel creation fails in Salesforce

1. Verify the Digital Engagement license is provisioned and active
2. Confirm the Channel ID, Channel Secret, and Channel Access Token are correct
3. Re-issue the Channel Access Token in LINE Developers Console if the current one has expired or been revoked
4. Ensure the Messaging API is enabled on your LINE Official Account (not just the standard chat mode)

### Agent not responding but session is created

1. Check `SessionHandlerId` on the MessagingChannel matches the BotDefinition Id
2. Verify the agent's default_agent_user has proper permissions (Einstein Agent User profile, AgentforceServiceAgentUser permission set)
3. Confirm the BotDefinition is published and has an active BotVersion
4. Review agent debug logs in Setup > Agentforce > Agents > [Your Agent] > Logs

### Customers receiving duplicate responses

1. Disable auto-reply messages in LINE Official Account Manager > Settings > Response Settings
2. Disable greeting messages in the same settings page
3. If using a third-party LINE integration alongside Salesforce, only one system should respond to webhooks

### Supported message types

LINE supports: text messages, images, stickers, emoji, location sharing, and videos as inbound content. The Agentforce agent can receive all types but responds with text by default. Rich message responses (carousels, quick reply buttons, flex messages) require additional configuration via LINE's message template format.

## Definition of Done

**Definition of Done:**

✅ **Verify:**

- LINE channel active in Messaging Settings with MessageType = Line
- Webhook URL registered and verified in LINE Developers Console
- "Use webhook" toggle enabled in LINE Developers Console
- Auto-reply and greeting messages disabled in LINE Official Account Manager
- Customer can send a LINE message and reach the Agentforce agent
- Agent responds to the message automatically
- MessagingSession record created with correct routing to agent
- Escalation to human agent queue works when agent transfers

## Custom Channel Setup (BYOC)

# Custom Channel Setup (BYOC) (45 min)

## Outcome

A working Bring Your Own Channel (BYOC) pipeline where customers on any external messaging platform can reach your Agentforce Service Agent through a partner-managed integration.

When complete, you will have:

- A BYOC Messaging Channel connected to your Salesforce org via a managed package and the Interaction Service API
- Omni-Channel routing configured to deliver inbound messages to your Agentforce agent
- Verified end-to-end message flow from the external messaging provider through to your agent and back
- Escalation routing to a human agent queue when the agent hands off

> **ℹ️ Info**: BYOC channels require the **Digital Engagement** add-on license and a **managed package** from your messaging or CCaaS provider. Unlike native channels (WhatsApp, SMS, Messenger), BYOC requires developer integration work to translate messages between the external provider and Salesforce. No Experience Cloud site or EmbeddedServiceConfig is needed.

> **⚠️ Warning**: Salesforce offers two BYOC flavors. This guide covers **Bring Your Own Channel for Messaging**, which connects an external messaging provider to Salesforce using the Interaction Service API. The second flavor, **Bring Your Own Channel for CCaaS**, connects an external contact center with unified partner-owned routing and supports both messaging and voice. Choose the flavor that matches your provider.

## Architecture

When a customer sends a message through the external messaging platform, the message flows through the following chain:

*(Architecture diagram — see the live page for the visual)*

Key metadata relationships:

- `MessagingChannel.MessageType` = `Custom`
- `MessagingChannel.SessionHandlerId` = BotDefinition.Id
- `sessionHandlerType` = `AgentforceServiceAgent`
- `sessionHandlerAsa` = BotDefinition DeveloperName
- `ServiceChannel.DeveloperName` = `sfdc_livemessage`
- `Queue.SobjectType` = `MessagingSession`

The managed package from your messaging provider handles webhook ingestion, message format translation, and delivery status callbacks. Salesforce receives normalized messages through the Interaction Service API, creates MessagingSession records, and routes them to your Agentforce agent via Omni-Channel.

### Configure Your Variables

**Variable Reference** — replace these placeholders in commands below:

| Variable | Description | Example |
|----------|-------------|---------|
| `{{ORG_ALIAS}}` | Salesforce org alias or username | `<your-org-alias>` |
| `{{AGENT_NAME}}` | Service Agent developer name | `Reservation_Agent` |
| `{{CHANNEL_NAME}}` | Messaging Channel developer name | `AgentforceChannel` |
| `{{INSTANCE_URL}}` | Salesforce instance URL | `https://myorg.my.salesforce.com` |
| `{{ACCESS_TOKEN}}` | OAuth access token | `YOUR_ACCESS_TOKEN` |

## Implementation Steps

## 1. Verify Your Starting Point

Confirm your agent exists, your org has the required licenses, and you have admin access.

### Confirm your Service Agent exists and is active

Before building the routing chain, verify your Agentforce Service Agent is deployed and active in the target org.

**Choose one of the following methods:**

#### SF CLI

```bash
sf data query \
  --target-org {{ORG_ALIAS}} \
  --query "SELECT Id, DeveloperName, MasterLabel, AgentType FROM BotDefinition WHERE DeveloperName='{{AGENT_NAME}}' AND IsActive=true"
```

#### REST API

```bash
curl -X GET "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id,DeveloperName,MasterLabel,AgentType+FROM+BotDefinition+WHERE+DeveloperName='{{AGENT_NAME}}'+AND+IsActive=true" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" \
  -H "Content-Type: application/json"
```

#### Browser Agent

```text
1. Navigate to Setup > Agents > Agent Builder
2. Find your agent by name: {{AGENT_NAME}}
3. Verify the status shows Active
4. Copy the Agent ID for later use
```

**Verify the agent is active:**

✅ **Verify:**

Expected result: One record returned with a valid Id.

If no record is returned, activate your agent in Agent Builder before proceeding.

### Authenticate and verify org access

Ensure you have admin access to the target org and the CLI is connected.

**Choose one of the following methods:**

#### SF CLI

```bash
sf org login web --alias {{ORG_ALIAS}} --instance-url https://login.salesforce.com
sf org display --target-org {{ORG_ALIAS}} --verbose
```

#### REST API

```bash
# Use OAuth 2.0 web server flow to obtain access token
# Then verify with:
curl -X GET "{{INSTANCE_URL}}/services/data/v64.0/limits" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}"
```

#### Browser Agent

```text
1. Log into your Salesforce org at https://login.salesforce.com
2. Verify you see Setup in the gear menu (admin access required)
3. Navigate to Setup > Company Information to confirm org details
```

**Verify org access:**

✅ **Verify:**

Expected result: Org display shows your username and instance URL without errors.

### Verify Digital Engagement license

BYOC channels require the Digital Engagement add-on. Confirm it is provisioned in your org.

**Choose one of the following methods:**

#### SF CLI

```bash
sf data query \
  --target-org {{ORG_ALIAS}} \
  --query "SELECT Id, Name, Status, IsProvisioned FROM PackageLicense WHERE NamespacePrefix='sfdc_livemessage'"
```

#### REST API

```bash
curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id,Name,Status,IsProvisioned+FROM+PackageLicense+WHERE+NamespacePrefix='sfdc_livemessage'" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}"
```

#### Browser Agent

```text
1. Navigate to Setup > Company Information > Permission Set Licenses
2. Look for "Digital Engagement" or "Messaging" in the list
3. Confirm it shows as Active with available licenses
```

**Verify Digital Engagement license:**

✅ **Verify:**

Expected result: One record returned with IsProvisioned = true.

If not found, contact your Salesforce account executive to provision Digital Engagement.

## 2. Enable Messaging and Omni-Channel

Enable the org-level settings required for BYOC messaging.

### Enable Messaging in Setup

Messaging must be enabled at the org level before any messaging channels can be created.

**Choose one of the following methods:**

#### SF CLI

```bash
# Deploy Omni-Channel settings via metadata
mkdir -p force-app/main/default/settings

cat > force-app/main/default/settings/OmniChannel.settings-meta.xml << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<OmniChannelSettings xmlns="http://soap.sforce.com/2006/04/metadata">
    <enableOmniChannel>true</enableOmniChannel>
</OmniChannelSettings>
EOF

sf project deploy start \
  --target-org {{ORG_ALIAS}} \
  --source-dir force-app/main/default/settings/OmniChannel.settings-meta.xml
```

#### REST API

```bash
# Messaging and Omni-Channel settings are typically enabled via
# Metadata API or Setup UI. See Browser method.
```

#### Browser Agent

```text
1. Navigate to Setup > Messaging Settings
2. If you see a toggle for Messaging, enable it
3. Navigate to Setup > Omni-Channel Settings
4. Check "Enable Omni-Channel"
5. Click Save
```

**Verify messaging is enabled:**

✅ **Verify:**

Expected result: Messaging Settings page loads without errors and shows channel management options. Omni-Channel Settings shows "Omni-Channel is enabled."

### Verify the service channel exists

The `sfdc_livemessage` service channel is automatically created when Digital Engagement is enabled.

**Choose one of the following methods:**

#### SF CLI

```bash
sf data query \
  --target-org {{ORG_ALIAS}} \
  --query "SELECT Id, DeveloperName, MasterLabel FROM ServiceChannel WHERE DeveloperName='sfdc_livemessage' LIMIT 1"
```

#### REST API

```bash
curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id,DeveloperName,MasterLabel+FROM+ServiceChannel+WHERE+DeveloperName='sfdc_livemessage'+LIMIT+1" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}"
```

#### Browser Agent

```text
1. Navigate to Setup > Service Channels
2. Look for "Messaging" or "sfdc_livemessage" in the list
3. Confirm it exists and is active
```

**Verify the Live Message channel:**

✅ **Verify:**

Expected result: One record returned with DeveloperName = `sfdc_livemessage`.

If not found, Messaging is not enabled. Return to Setup > Messaging Settings and enable it.

## 3. Install the Provider Managed Package

Install the managed package from your external messaging or CCaaS provider. This package provides the webhook handlers, message translation logic, and Interaction Service API integration that bridges your external platform to Salesforce.

> **ℹ️ Info**: Each messaging provider (Twilio, Vonage, MessageBird, custom platforms, etc.) distributes their own managed package. Contact your provider for the installation URL or find it on Salesforce AppExchange. The managed package may include sample Omni-Channel flows for routing.

### Install the managed package

**Choose one of the following methods:**

#### SF CLI

```bash
# Install the managed package using the installation URL from your provider
# Replace the URL with your provider's package installation link
sf package install \
  --target-org {{ORG_ALIAS}} \
  --package "{{PROVIDER_PACKAGE_ID}}" \
  --wait 10

# Verify installed packages
sf package installed list --target-org {{ORG_ALIAS}}
```

#### REST API

```bash
# Managed packages are typically installed via the Setup UI or SF CLI.
# After installation, verify with:
curl -s "{{INSTANCE_URL}}/services/data/v64.0/tooling/query?q=SELECT+Id,SubscriberPackage.Name,SubscriberPackageVersion.Name+FROM+InstalledSubscriberPackage" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}"
```

#### Browser Agent

```text
1. Obtain the package installation URL from your messaging provider
2. Open the URL in your browser while logged into your Salesforce org
3. Select "Install for All Users" (recommended)
4. Click Install
5. Wait for the installation to complete (may take several minutes)
6. Navigate to Setup > Installed Packages to verify
```

**Verify the managed package installation:**

✅ **Verify:**

Expected result: The provider's managed package appears in Setup > Installed Packages with status "Installed".

### Configure the managed package

Follow your provider's configuration guide to connect the managed package to your external messaging platform. This typically involves:

**Choose one of the following methods:**

#### SF CLI

```bash
# Provider configuration is typically UI-driven.
# After configuration, verify the Named Credential exists:
sf data query \
  --target-org {{ORG_ALIAS}} \
  --query "SELECT Id, MasterLabel, DeveloperName FROM NamedCredential WHERE DeveloperName LIKE '%{{PROVIDER_NAME}}%'"
```

#### REST API

```bash
# Verify Named Credential for provider integration
curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id,MasterLabel,DeveloperName+FROM+NamedCredential" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}"
```

#### Browser Agent

```text
1. Navigate to Setup > [Provider Name] Settings (added by the managed package)
2. Enter your provider API credentials:
   - API Key or Client ID from your messaging provider
   - API Secret or Client Secret
   - Webhook endpoint URL (your provider will send messages here)
3. Configure the message format mapping if required
4. Test the connection using the provider's built-in test button
5. Save the configuration
```

> **⚠️ Warning**: The managed package configuration varies by provider. Consult your provider's documentation for exact field names, API credential formats, and webhook setup instructions. Common requirements include API keys, webhook URLs, and message format configuration.

**Verify provider configuration:**

✅ **Verify:**

Expected result: Provider configuration shows a connected/healthy status.

If the provider offers a test connection button, it succeeds.

## 4. Create the BYOC Messaging Channel

Create a Bring Your Own Channel in Salesforce Messaging Settings. This registers the channel and enables message routing through the Interaction Service API.

### Create the channel via Setup

**Choose one of the following methods:**

#### SF CLI

```bash
# The BYOC channel must be created via the Setup wizard.
# After creation, verify with:
sf data query \
  --target-org {{ORG_ALIAS}} \
  --query "SELECT Id, DeveloperName, MasterLabel, MessageType, IsActive FROM MessagingChannel WHERE MessageType='Custom'"
```

#### REST API

```bash
# BYOC channel creation requires the Setup wizard.
# After creation, verify with:
curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id,DeveloperName,MasterLabel,MessageType,IsActive+FROM+MessagingChannel+WHERE+MessageType='Custom'" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}"
```

#### Browser Agent

```text
1. Navigate to Setup > Messaging Settings
2. Click New Channel
3. Select "Bring Your Own Channel" as the channel type
4. Configure the channel:
   a. Channel Name: {{CHANNEL_NAME}}
   b. Select your installed messaging provider package
   c. Configure the Interaction Service API endpoint mapping
5. Review the configuration summary
6. Click Finish to create the channel
7. The channel will appear in Messaging Settings
```

**Verify channel was created:**

✅ **Verify:**

Expected result: Query returns one record with MessageType = 'Custom' and the DeveloperName matching your channel.

### Verify channel details

After creation, confirm the channel is properly configured with the correct message type and platform key.

**Choose one of the following methods:**

#### SF CLI

```bash
sf data query \
  --target-org {{ORG_ALIAS}} \
  --query "SELECT Id, DeveloperName, MasterLabel, MessageType, IsActive, MessagingPlatformKey, SessionHandlerId, FallbackQueueId FROM MessagingChannel WHERE DeveloperName='{{CHANNEL_NAME}}'"
```

#### REST API

```bash
curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id,DeveloperName,MasterLabel,MessageType,IsActive,MessagingPlatformKey,SessionHandlerId,FallbackQueueId+FROM+MessagingChannel+WHERE+DeveloperName='{{CHANNEL_NAME}}'" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}"
```

#### Browser Agent

```text
1. Navigate to Setup > Messaging Settings
2. Click on your BYOC channel: {{CHANNEL_NAME}}
3. Note the following fields:
   - Channel Name / DeveloperName
   - Message Type (should be Custom)
   - Platform Key
   - Status (should be Active)
```

**Verify channel is active:**

✅ **Verify:**

Expected result: Channel record returned with MessageType = 'Custom' and IsActive = true.

## 5. Configure Routing to Your Agent

Set up the routing so inbound BYOC messages are handled by your Agentforce Service Agent, with a fallback queue for escalation.

### Create a messaging queue for fallback routing

The queue handles messages when the agent is unavailable or escalates to a human. Even with direct agent routing, a fallback queue is required.

**Choose one of the following methods:**

#### SF CLI

```bash
# Check if queue exists
sf data query --target-org {{ORG_ALIAS}} \
  --query "SELECT Id FROM Group WHERE Type='Queue' AND DeveloperName='{{CHANNEL_NAME}}_Queue'"

# If not found, create via Metadata API
mkdir -p force-app/main/default/queues

cat > force-app/main/default/queues/{{CHANNEL_NAME}}_Queue.queue-meta.xml << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<Queue xmlns="http://soap.sforce.com/2006/04/metadata">
    <fullName>{{CHANNEL_NAME}}_Queue</fullName>
    <name>{{CHANNEL_NAME}} Queue</name>
    <queueSobject>
        <sobjectType>MessagingSession</sobjectType>
    </queueSobject>
</Queue>
EOF

sf project deploy start --target-org {{ORG_ALIAS}} --metadata Queue:{{CHANNEL_NAME}}_Queue
```

#### REST API

```bash
# Create queue
curl -X POST "{{INSTANCE_URL}}/services/data/v64.0/sobjects/Group" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" \
  -H "Content-Type: application/json" \
  -d '{
    "Name": "{{CHANNEL_NAME}} Queue",
    "DeveloperName": "{{CHANNEL_NAME}}_Queue",
    "Type": "Queue"
  }'

# Add MessagingSession to queue
QUEUE_ID=$(curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id+FROM+Group+WHERE+DeveloperName='{{CHANNEL_NAME}}_Queue'" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" | jq -r '.records[0].Id')

curl -X POST "{{INSTANCE_URL}}/services/data/v64.0/sobjects/QueueSobject" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" \
  -H "Content-Type: application/json" \
  -d "{\"QueueId\": \"$QUEUE_ID\", \"SobjectType\": \"MessagingSession\"}"
```

#### Browser Agent

```text
1. Navigate to Setup > Queues
2. Click New
3. Set Label: {{CHANNEL_NAME}} Queue
4. Set Queue Name: {{CHANNEL_NAME}}_Queue
5. In Supported Objects, add Messaging Session
6. Add queue members (agents who will handle escalations)
7. Click Save
```

**Verify queue creation:**

✅ **Verify:**

Expected result: One queue record returned with DeveloperName = '{{CHANNEL_NAME}}\_Queue'.

### Route the channel to your Agentforce agent

Set the SessionHandlerId on the MessagingChannel to point to your agent's BotDefinition. This enables direct agent routing for all inbound BYOC messages.

**Choose one of the following methods:**

#### SF CLI

```bash
# Get the Bot ID and Queue ID
BOT_ID=$(sf data query --target-org {{ORG_ALIAS}} --json \
  --query "SELECT Id FROM BotDefinition WHERE DeveloperName='{{AGENT_NAME}}'" \
  | jq -r '.result.records[0].Id')

QUEUE_ID=$(sf data query --target-org {{ORG_ALIAS}} --json \
  --query "SELECT Id FROM Group WHERE Type='Queue' AND DeveloperName='{{CHANNEL_NAME}}_Queue'" \
  | jq -r '.result.records[0].Id')

CHANNEL_ID=$(sf data query --target-org {{ORG_ALIAS}} --json \
  --query "SELECT Id FROM MessagingChannel WHERE DeveloperName='{{CHANNEL_NAME}}'" \
  | jq -r '.result.records[0].Id')

# Update the channel with agent routing
sf apex run --target-org {{ORG_ALIAS}} << EOF
MessagingChannel channel = [SELECT Id FROM MessagingChannel WHERE DeveloperName = '{{CHANNEL_NAME}}' LIMIT 1];
channel.SessionHandlerId = '$BOT_ID';
channel.FallbackQueueId = '$QUEUE_ID';
update channel;
System.debug('Updated MessagingChannel: ' + channel.Id + ' with SessionHandlerId: $BOT_ID');
EOF
```

#### REST API

```bash
BOT_ID=$(curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id+FROM+BotDefinition+WHERE+DeveloperName='{{AGENT_NAME}}'" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" | jq -r '.records[0].Id')

QUEUE_ID=$(curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id+FROM+Group+WHERE+Type='Queue'+AND+DeveloperName='{{CHANNEL_NAME}}_Queue'" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" | jq -r '.records[0].Id')

CHANNEL_ID=$(curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id+FROM+MessagingChannel+WHERE+DeveloperName='{{CHANNEL_NAME}}'" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" | jq -r '.records[0].Id')

curl -X PATCH "{{INSTANCE_URL}}/services/data/v64.0/sobjects/MessagingChannel/$CHANNEL_ID" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}" \
  -H "Content-Type: application/json" \
  -d "{
    \"SessionHandlerId\": \"$BOT_ID\",
    \"FallbackQueueId\": \"$QUEUE_ID\"
  }"
```

#### Browser Agent

```text
1. Navigate to Setup > Messaging Settings
2. Click on your BYOC channel: {{CHANNEL_NAME}}
3. Under Routing Configuration:
   a. Set Routing Type to "Agentforce Service Agent"
   b. Select your agent: {{AGENT_NAME}}
   c. Set Fallback Queue: {{CHANNEL_NAME}} Queue
4. Click Save
```

**Verify agent routing:**

✅ **Verify:**

Expected result: Query the channel and confirm SessionHandlerId matches your BotDefinition.Id.

> **💡 Tip**: If `SessionHandlerId` is not available in your org's API version, configure routing through an Omni-Channel inbound flow instead. Create a flow with the Route Work action: Service Channel = `sfdc_livemessage`, Route To = your Agentforce Service Agent.

### Set up Omni-Channel flows for agent integration (optional)

For advanced routing, create inbound and outbound Omni-Channel flows. Your provider's managed package may include sample flows you can customize.

**Choose one of the following methods:**

#### SF CLI

```bash
# Check if the provider included sample flows
sf data query \
  --target-org {{ORG_ALIAS}} \
  --query "SELECT Id, MasterLabel, ProcessType FROM FlowDefinition WHERE ProcessType='Flow' AND MasterLabel LIKE '%{{PROVIDER_NAME}}%'"
```

#### REST API

```bash
curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id,MasterLabel,ProcessType+FROM+FlowDefinition+WHERE+ProcessType='Flow'" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}"
```

#### Browser Agent

```text
For inbound routing (messages arriving from customers):
1. Navigate to Setup > Flows
2. Create a new Omni-Channel Flow
3. Add a Route Work element:
   - Service Channel: sfdc_livemessage
   - Route To: Your Agentforce Service Agent
4. Activate the flow

For outbound routing (escalation to human agents):
1. Create a second Omni-Channel Flow
2. Add routing logic to assign to a human agent queue
3. Configure the escalation message
4. Activate the flow
5. Reference this flow in your agent's connection block
```

> **ℹ️ Info**: Inbound flows handle how messages reach the agent. Outbound flows handle escalation from the agent to human agents. Both are optional when using direct `SessionHandlerId` routing, but required for advanced routing scenarios like skill-based routing or conditional agent assignment.

## 6. Test and Verify

Send a test message through your external messaging platform and confirm the full pipeline works end-to-end.

> **⚠️ Warning**: Testing BYOC requires the external messaging provider to be connected and operational. Unlike native channels, you cannot test purely within Salesforce. Ensure the managed package is configured and the external provider webhook is active before proceeding.

### Send a test message

Use the external messaging platform to send a message and verify the agent responds.

**Choose one of the following methods:**

#### SF CLI

```bash
# After sending a test message from the external platform, query for the session:
sf data query --target-org {{ORG_ALIAS}} \
  --query "SELECT Id, Status, AgentType, OwnerId, CreatedDate, MessagingChannelId FROM MessagingSession WHERE MessagingChannel.DeveloperName='{{CHANNEL_NAME}}' ORDER BY CreatedDate DESC LIMIT 5"
```

#### REST API

```bash
curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id,Status,AgentType,OwnerId,CreatedDate,MessagingChannelId+FROM+MessagingSession+WHERE+MessagingChannel.DeveloperName='{{CHANNEL_NAME}}'+ORDER+BY+CreatedDate+DESC+LIMIT+5" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}"
```

#### Browser Agent

```text
1. Open the external messaging platform (your provider's test interface or app)
2. Send a test message: "Hello, I need help"
3. Wait 5-10 seconds for the agent to respond
4. Verify you receive a response from the agent
5. Try a second message to confirm multi-turn works
```

**Verify the agent responds:**

✅ **Verify:**

Expected result: Agent responds to the message. MessagingSession record created with Status = 'Active' or 'Ended'.

### Verify agent routing and session records

Confirm messages are being routed to your agent by checking MessagingSession and AgentWork records.

**Choose one of the following methods:**

#### SF CLI

```bash
# Check recent MessagingSessions for the BYOC channel
sf data query --target-org {{ORG_ALIAS}} \
  --query "SELECT Id, Status, AgentType, OwnerId, CreatedDate FROM MessagingSession WHERE MessagingChannel.MessageType='Custom' ORDER BY CreatedDate DESC LIMIT 5"

# Check AgentWork records
sf data query --target-org {{ORG_ALIAS}} \
  --query "SELECT Id, WorkItemId, BotId, RoutingType, CreatedDate FROM AgentWork ORDER BY CreatedDate DESC LIMIT 5"

# Check individual messages in the session
sf data query --target-org {{ORG_ALIAS}} \
  --query "SELECT Id, MessageText, SenderType, CreatedDate FROM MessagingEvent WHERE MessagingSession.MessagingChannel.DeveloperName='{{CHANNEL_NAME}}' ORDER BY CreatedDate DESC LIMIT 10"
```

#### REST API

```bash
curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id,Status,AgentType,OwnerId,CreatedDate+FROM+MessagingSession+WHERE+MessagingChannel.MessageType='Custom'+ORDER+BY+CreatedDate+DESC+LIMIT+5" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}"

curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id,WorkItemId,BotId,RoutingType,CreatedDate+FROM+AgentWork+ORDER+BY+CreatedDate+DESC+LIMIT+5" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}"
```

#### Browser Agent

```text
1. Navigate to the App Launcher > Messaging Sessions
2. Find the most recent session
3. Click to view details
4. Verify:
   - Owner matches your agent
   - Message Type shows Custom
   - Status is Active or Ended
5. Check the Messaging Events related list for individual messages
```

**Verify messaging session records:**

✅ **Verify:**

Expected result: Recent MessagingSession exists with AgentType populated and messages visible in MessagingEvent records.

### Test escalation to human agent

Verify that when the agent escalates, the conversation routes to your fallback queue.

**Choose one of the following methods:**

#### SF CLI

```bash
# Send a message that triggers escalation (e.g., "I want to speak to a human")
# Then verify the session was transferred:
sf data query --target-org {{ORG_ALIAS}} \
  --query "SELECT Id, Status, OwnerId, Owner.Name, AgentType FROM MessagingSession WHERE MessagingChannel.DeveloperName='{{CHANNEL_NAME}}' ORDER BY CreatedDate DESC LIMIT 1"
```

#### REST API

```bash
curl -s "{{INSTANCE_URL}}/services/data/v64.0/query?q=SELECT+Id,Status,OwnerId,AgentType+FROM+MessagingSession+WHERE+MessagingChannel.DeveloperName='{{CHANNEL_NAME}}'+ORDER+BY+CreatedDate+DESC+LIMIT+1" \
  -H "Authorization: Bearer {{ACCESS_TOKEN}}"
```

#### Browser Agent

```text
1. In the external messaging platform, type: "I want to speak to a human"
2. The agent should acknowledge the escalation request
3. Navigate to Setup > Omni-Channel Supervisor (or the Service Console)
4. Verify the messaging session appears in the queue: {{CHANNEL_NAME}} Queue
5. A human agent can accept and continue the conversation
```

**Verify escalation routing:**

✅ **Verify:**

Expected result: After escalation, the MessagingSession owner changes from the BotDefinition to the fallback queue or a human agent.

## BYOC-Specific Considerations

| Consideration          | Details                                                                                                |
| ---------------------- | ------------------------------------------------------------------------------------------------------ |
| Setup complexity       | Higher than native channels. Requires managed package installation and provider-specific configuration |
| Custom development     | Provider must implement the Interaction Service API for message ingestion and delivery                 |
| Message format         | Messages are normalized by the managed package before reaching Salesforce                              |
| Progress indicators    | Supported. Agent can show "Doing research", "Analyzing" etc. to the customer                           |
| Interactive components | Supported if the external platform and managed package support rich message types                      |
| Session handling       | Managed by Salesforce MessagingSession. Session lifecycle depends on provider configuration            |
| Consent model          | Depends on the external platform. Configure consent handling in the managed package                    |

## Troubleshooting

### Messages not reaching Salesforce

1. Verify the managed package is installed and configured correctly
2. Check the external provider's webhook status. The webhook must be active and pointing to your Salesforce instance
3. Confirm the MessagingChannel is Active: query `IsActive` field
4. Review the managed package logs (if available) for message delivery errors
5. Verify the Interaction Service API endpoint is accessible from the provider

### Channel creation fails in Setup

1. Verify the Digital Engagement license is provisioned and active
2. Confirm the managed package from your provider is installed
3. Check that the managed package version is compatible with your org's API version
4. Clear browser cache and retry if the Setup wizard stalls

### Agent not responding but session is created

1. Verify `SessionHandlerId` is set and points to the correct BotDefinition.Id
2. Check the agent is Active (not Draft) in Agent Builder
3. Verify the agent's `default_agent_user` has proper permissions (Einstein Agent User profile, AgentforceServiceAgentUser permission set)
4. Check for errors in the agent's trace logs via Agent Builder preview
5. Verify the BotVersion is published and active:

```bash
sf data query --target-org {{ORG_ALIAS}} \
  --query "SELECT Id, DeveloperName, Status FROM BotVersion WHERE BotDefinition.DeveloperName='{{AGENT_NAME}}' AND Status='Active' LIMIT 1"
```

### Messages arriving but responses not reaching the external platform

1. The managed package outbound webhook may be misconfigured. Check the provider's delivery status dashboard
2. Verify the Named Credential for the external provider is valid and not expired
3. Check for API rate limits on the external provider side
4. Review Salesforce debug logs for errors in the managed package's outbound message handling

### Interaction Service API errors

1. Check the API version. The Interaction Service API requires a minimum Salesforce API version
2. Verify the Connected App used by the managed package has the correct OAuth scopes
3. Review the provider's developer documentation for known issues and required API permissions
4. Test the Interaction Service API endpoint directly using the BYOC for Messaging Developer Guide

### Provider managed package not appearing in channel creation

1. Confirm the package installation completed successfully (Setup > Installed Packages)
2. Some packages require post-installation configuration steps before they register as a channel provider
3. Check for package version updates that may be required for BYOC compatibility
4. Contact the provider's support for channel registration issues

## Developer Resources

For custom BYOC implementations:

- **BYOC for Messaging Developer Guide** -- Building custom integrations using the Interaction Service API
- **BYOC for CCaaS Developer Guide** -- Connecting external contact centers with unified routing
- **Interaction Service API Developer Guide** -- API reference for message ingestion and delivery

## Definition of Done

**Definition of Done:**

✅ **Verify:**

- BYOC channel active in Messaging Settings with MessageType = Custom
- Managed package from messaging provider installed and configured
- Customer can send a message through the external platform and reach the Agentforce agent
- Agent responds to the message automatically
- MessagingSession record created with correct routing to agent
- Escalation to human agent fallback queue works when agent transfers
- Responses from the agent are delivered back through the external messaging platform

---

# Labs

## Feedback

# Feedback

This page is a placeholder for Labs feedback docs.

## Experiments

# Experiments

Early-access tools and integrations from the Agentforce product and engineering teams.
- [Start Building](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/start>): Create your first agent in minutes, no setup. Keep working in your IDE of choice.
- [Agents](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/agents>): Create and edit Agentforce agents in a fast, lightweight editor.
- [Upgrade Agent](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/upgrade-agent>): Convert your Einstein Bot into an Agentforce agent.
- [Troubleshoot & Optimize](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/troubleshoot>): Diagnose and fix agent issues like wrong topic routing, a failing action, or a flaky response, all against your own org.
- [Salesforce Development Skills](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/docs/skills>): Choose Salesforce platform skills and Agentforce lifecycle plugins for your coding agent.
- [nanoRag](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/nanorag>): Let your agents reference documents when answering, no vector database required.
- [AgentLens Visualizer](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/agentlens>): Debug Agentforce traces with interactive visualizations.
- [MCP Servers](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/mcp>): Connect your agents to external tools and data via Model Context Protocol servers.
- [Agentforce Grid MCP & Skills](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/docs/agentforce-grid>): Equip Claude Code to manage Agentforce Grid workbooks with the official MCP server — run it via npx, no clone or build required.
- [Salesforce Docs MCP](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/docs/salesforce-docs-mcp>): Browse Salesforce documentation directly from your IDE using the Model Context Protocol.
- [GUI Process Automation](<https://www.salesforceairesearch.com/gpa>): GPA is the next generation of RPA, an agent that learns tasks by watching a user perform them once.
- [MCP+](<https://mcp-plus.github.io/>): A context optimization layer that filters MCP tool outputs down to only the relevant context, cutting inference costs.
- [SkillOrchestra](<https://jiayuww.github.io/SkillOrchestra/>): A skill-centric framework that routes tasks to the right agent based on competence and cost.
- [Agentforce for Google Workspace](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/docs/agentforce-slides>): Ask your Agentforce agent questions in Google Slides and insert answers into a slide.

## Agentforce Grid MCP

# Agentforce Grid MCP

Equip Claude Code (or Cursor, VS Code Copilot, and other MCP clients) to manage Agentforce Grid workbooks directly. The official `@salesforce/sf-grid-mcp` server exposes the Grid Connect API as MCP tools — create workbooks and worksheets, configure every column type, run agent tests and evaluations, and read results back — all through natural language.

No cloning or building required: your MCP client fetches the package from npm on first run via `npx`. It authenticates against your existing Salesforce CLI org login, so there are no tokens to manage.
[MCP Repo](<https://github.com/salesforcecli/sf-grid-mcp>)
[npm Package](<https://www.npmjs.com/package/@salesforce/sf-grid-mcp>)
*(Open Grid: open `/lightning/n/standard-WorkbenchHome` in your Salesforce org)*
![Agentforce Grid](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/images/products/agentforce-grid.png>)

## Prerequisites

- **Node.js 18+** — provides the `npx` command your MCP client uses to fetch and run the server.
- **Salesforce CLI (`sf`)** — the server shells out to it for every API call.

Install the Salesforce CLI if you don't have it:

```bash
brew install sf                    # macOS
npm install -g @salesforce/cli     # any platform
```

## Authenticate Your Org

Copy the command below and run it in your terminal to authenticate the Salesforce CLI with your org:
**Authenticate SF CLI**: Log in on this page to get your pre-filled authentication command.

```bash
echo "<SFDX_AUTH_URL>" | sf org login sfdx-url --sfdx-url-stdin --set-default --alias afdx-lab
```
Or authenticate manually:

```bash
sf org login web --alias my-org --set-default --instance-url https://your-instance.salesforce.com/
```

✅ **Verify:**

Run this command:

```bash
sf org display user
```

**Expected output:** Your org username and alias

## Configure Your MCP Client

You don't install the package directly — your MCP client invokes it via `npx -y` and Node fetches `@salesforce/sf-grid-mcp` from the npm registry on first run.

Add this to your Claude Code config (`.mcp.json` in your project, or `~/.claude/mcp.json` globally):

```json
{
  "mcpServers": {
    "sf-grid": {
      "command": "npx",
      "args": ["-y", "@salesforce/sf-grid-mcp", "--orgs", "DEFAULT_TARGET_ORG"]
    }
  }
}
```

`--orgs` is required. Use `DEFAULT_TARGET_ORG` to follow whatever `sf config get target-org` returns, a specific alias (e.g. `my-org`) to pin the server to one org, or `ALLOW_ALL_ORGS` to expose every authenticated org (use cautiously — the agent can then reach any org you've logged into).

The same config works for other clients — Cursor (`mcp.json`) and VS Code / GitHub Copilot (`.vscode/mcp.json`, under a `servers` key instead of `mcpServers`).

## Verify

Restart your MCP client so it picks up the new server, then ask Claude Code:

```text
List my Grid workbooks
```

If everything is set up, Claude queries your org and returns results.

## What You Can Do

Once connected, the server gives Claude Code 10 consolidated tools spanning the full Grid workflow:

- **`apply_grid`** — create an entire grid from a single declarative YAML spec. It resolves agent names to IDs, topologically sorts column dependencies, expands flat config into validated JSON, and populates data — collapsing 10–15 sequential calls into one. Supports a `dryRun` mode for validation without API calls.
- **Typed mutations** — change prompts, swap models (with shorthands like `claude-4.5-sonnet`), add evaluations, and update filters without hand-writing raw JSON.
- **Full CRUD** — workbooks, worksheets, columns, cells, and rows, each consolidated into a single action-discriminated tool.
- **Discovery** — one `discover` tool answers 25+ metadata, data, and agent queries: available agents, models, evaluation types, SObjects, Data Cloud DMOs, prompt templates, and more.
- **Composite workflows** — `setup_agent_test`, `poll_worksheet_status`, and `get_worksheet_summary` for common multi-step tasks.

All 12 Agentforce Grid column types are supported with typed schemas, and every config is validated before it hits the API.

## Declarative Grids with `apply_grid`

The flagship tool takes a YAML spec and builds a complete grid in one call. Ask Claude Code to create one, or hand it a spec like this:

```yaml
workbook: Sales Agent Tests
worksheet: Q1 Regression
columns:
  - name: Utterances
    type: text

  - name: Agent Output
    type: agent_test
    agent: "Sales Coach"
    inputUtterance: "Utterances"

  - name: Coherence
    type: eval/coherence
    input: "Agent Output"

data:
  Utterances:
    - "How do I reset my password?"
    - "What is my account balance?"
```

## Troubleshooting

### \

The server shells out to the `sf` CLI for every API call. Install it (`brew install sf` or `npm install -g @salesforce/cli`) and confirm `sf --version` works in the same shell your MCP client launches.

### \

Run `sf org login web --alias my-org`, then either pass `--orgs my-org` to pin the server to that alias, or run `sf config set target-org=my-org` and use `--orgs DEFAULT_TARGET_ORG`.

### MCP client can't find sf-grid-mcp

Make sure your client config uses `"command": "npx"` with `"args": ["-y", "@salesforce/sf-grid-mcp", ...]` — the `-y` auto-fetches the package on first run. If `npx` isn't on PATH, install Node 18+.

### 403 / INSUFFICIENT_ACCESS errors from the Grid API

The user behind your `sf` org login needs Agentforce Grid access in that org. Ask the org admin to confirm the user's profile/permission set grants access to the `AIWorksheet*` entities and the Grid Connect API.

### Use a local build for development

To test a local checkout instead of the published package, point your client at the absolute path of `dist/index.js`:

```json
{
  "mcpServers": {
    "sf-grid-local": {
      "command": "node",
      "args": ["/path/to/sf-grid-mcp/dist/index.js", "--orgs", "DEFAULT_TARGET_ORG"]
    }
  }
}
```

## Community

# Community

Join our Slack community to connect with fellow Agentblazers experimenting in Agentforce Labs, share what you're building, and discover new things to build.

### [Open in Slack](<https://agentblazercommunity.slack.com/channels/explore-agentforce-labs>)

Join the conversation in our developer Slack community.

## Setting up nanoRag

# Getting Started with nanoRag

nanoRag is a Salesforce CLI plugin that builds BM25 document libraries and attaches them to your Agentforce agent. The library and index live in your org as `ContentDocument` and `StaticResource` records — no embeddings, no vector database, no Data Cloud.

> **ℹ️ Info**: **Want to try it without installing anything?** [Try in your org →](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/nanorag>)

## 1. Install the Salesforce CLI

#### Homebrew

```bash
brew install sf
```

#### npm

```bash
npm install -g @salesforce/cli
```

Requires Node.js 18+.

**Verify the installation:**

✅ **Verify:**

Run this command:

```bash
sf --version
```

**Expected output:** `@salesforce/cli/`

## 2. Install the nanoRag plugin

Requires Python 3.10+.

```bash
git clone https://github.com/ankita13makker/nanoRAG.git
cd nanoRAG
sf plugins link plugins/sf-nanorag
```

The plugin creates its own virtual environment and installs Python dependencies on link.

**Verify the installation:**

✅ **Verify:**

Run this command:

```bash
sf nanorag --help
```

**Expected output:** A list of `nanorag` subcommands (`install`, `build`, `attach`, …)

## 3. Authenticate Your Org

The org needs Agentforce enabled and a NextGen (AgentScript) agent.

```bash
sf org login web --alias myOrg
```

**Verify your org connection:**

✅ **Verify:**

Run this command:

```bash
sf org display user
```

**Expected output:** Your org username and alias

## 4. Deploy the Apex Runtime

One-time per org. Installs the Apex classes that score queries at runtime.

```bash
sf nanorag install --target-org myOrg
```

## 5. Build a Library

```bash
sf nanorag build --target-org myOrg \
  --library-name product_docs \
  --files ./docs/guide.pdf ./docs/faq.docx
```

Supported formats: PDF, DOCX, PPTX, XLSX, MD, TXT, and 30+ others.

## 6. Attach to Your Agent

```bash
sf nanorag attach --target-org myOrg \
  --library-name product_docs \
  --agent-developer-name My_Service_Agent
```

Your agent can now retrieve from the library. Test a query:

```bash
sf nanorag search --target-org myOrg \
  --library-name product_docs \
  --query "how do I cancel"
```

## When to Use BM25 vs. Embeddings

nanoRag uses BM25 — fast, deterministic keyword search. Use it when:

- Users search with the same words that appear in your documents
- Your corpus is product docs, policy manuals, runbooks, or API references
- You have under ~10k documents

Reach for an embedding-based RAG instead when:

- You need synonym matching (user says "cancel," doc says "terminate")
- Your corpus is multilingual and users mix languages
- You need long-form generative answers, not retrieved passages

The agent action signature is the same either way — start with BM25 and swap backends later if recall feels shallow.

## Reference

| Command | Purpose |
|---------|---------|
| `sf nanorag search` | Test search against a library |
| `sf nanorag detach` | Remove a library from an agent |
| `sf nanorag library list` | List libraries in the org |
| `sf nanorag library delete` | Delete a library |
| `sf nanorag file add` | Add files, rebuild the index |
| `sf nanorag file delete` | Remove files, rebuild the index |
| `sf nanorag skill install` | Install the matching Claude Code skill |

All commands accept `--json` for structured output. Source: [github.com/ankita13makker/nanoRAG](https://github.com/ankita13makker/nanoRAG).

## AgentLens Visualizer

# AgentLens Visualizer

Debug subagent routing and orchestration in minutes using AgentLens, a free open source tool that visualizes Agentforce agent traces as interactive graphs, FSM diagrams, and step-by-step inspectors.

## Installation

### VSCode IDE

Install the extension from the VS Code Marketplace:

[AgentLens Viewer on the VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=MohithShrivastava.agentlens-viewer)

### Cursor, Windsurf, or Antigravity

Install from Open VSX:

[AgentLens Viewer on Open VSX](https://open-vsx.org/extension/msrivastav13/agentlens-viewer)

### Not using an IDE?

Paste your trace directly into the hosted viewer:

[AgentLens web viewer](https://msrivastav13.github.io/AgentLens/)

## About AgentLens Visualizer

AgentLens is an open source tool that transforms Agentforce traces into interactive visualizations — agent graphs, finite state machine (FSM) diagrams, and step-by-step execution inspectors — so you can debug Agent Script orchestration in minutes. It has zero dependencies and does not save your trace data anywhere on the server.

To learn how to use the tool, check out the blog:

[AgentLens: Debug Agentforce with Interactive Visualizations](https://developer.salesforce.com/blogs/2026/05/agentlens-debug-agentforce-with-interactive-visualizations)

## Agentforce for Google Workspace

# Agentforce for Google Workspace

Bring your Salesforce Agentforce agent directly into Google Workspace. Once installed, an **Agentforce** menu appears inside your presentation: open a chat panel, ask a question in plain English (for example, "show my open opportunities"), and get a live answer straight from your Salesforce data. With one click you can drop that answer — as text or a formatted table — onto the current slide.

Think of it as a bridge: your Salesforce AI agent, available right inside Google Slides.

The full source is open on GitHub: [newrishav-debug/AF-X-GoogleWS](https://github.com/newrishav-debug/AF-X-GoogleWS).

## Setup at a glance

There are three parts, and each one exists for a reason:

1. **Part 1 — Set up Salesforce.** Point the add-on at your existing agent and create a secure "login key" so it can talk to Salesforce on its own.
2. **Part 2 — Install the add-on into Slides.** Copy the code into your own Google account in a single command.
3. **Part 3 — Connect and test.** Paste your Salesforce keys into the add-on and confirm everything works.

By the end of Part 1 you'll have collected **four values** (two keys, a web address, and an ID) that you paste into the add-on in Part 3.

## Part 1 — Set up Salesforce

Two one-time things in an org that already has Agentforce enabled:

- **Agent ID** — open your agent in **Agent Builder** and copy the 18-character ID (starts with `0Xx…`) from the page URL.
- **Connected App / External Client App** — in **App Manager → New External Client App**, enable the **Client Credentials flow**, set an **API-only Run As user**, and add the `api`, `chatbot_api`, and `sfap_api` scopes.

For the full walkthrough (exact scopes, IP settings, and screenshots), see the [setup guide on GitHub](https://github.com/newrishav-debug/AF-X-GoogleWS).

### Collect your four values

Grab these now while you're here — you'll paste them into the add-on in Part 3.

| Value                               | Where to find it                                                                            |
| ----------------------------------- | ------------------------------------------------------------------------------------------- |
| **Consumer Key (Client ID)**        | External Client App → Settings → OAuth → Consumer Key                                        |
| **Consumer Secret (Client Secret)** | Same screen → reveal Consumer Secret                                                         |
| **My Domain URL**                   | Setup → My Domain → _Current My Domain URL_ (`https://…my.salesforce.com`)                    |
| **Agent ID**                        | An 18-character ID starting with `0Xx…`, from the Agent Builder URL (or `discoverAgents()`) |

## Part 2 — Install the add-on into Google Slides

1. Turn on the Apps Script API once at [script.google.com/home/usersettings](https://script.google.com/home/usersettings) (**Google Apps Script API → On**).
2. Download and install:

```bash
git clone https://github.com/newrishav-debug/AF-X-GoogleWS.git
cd AF-X-GoogleWS
npm install
```

3. Sign in and create the presentation (`npm run login`, then `npm run create`). To use an existing deck instead, copy its Script ID from **Extensions → Apps Script → Project Settings** into `.clasp.json`.
4. Upload the add-on with `npm run push`, then reload your presentation — an **Agentforce** menu appears in the menu bar.

## Part 3 — Connect your keys and test

1. In your presentation, click **Agentforce → Configure Agentforce**.
2. Paste in your four values (Consumer Key, Consumer Secret, My Domain URL, Agent ID) and click **Save**.
3. Click **Agentforce → Test Connection** to confirm the keys work.
4. Click **Agentforce → Open Chat** and ask a sample question.
5. Click **+ Add to slide** to place the formatted answer (text or table) onto the current slide.

Your keys are stored privately in the add-on's own settings and are **not** saved inside the slide deck.

## About

This community experiment connects Google Slides to Salesforce Agentforce using the official Agentforce Agent API. Credentials are stored privately in the add-on's own Apps Script settings, are transmitted only over HTTPS, and are never saved inside the slide deck. Every request runs as your configured Salesforce integration user, so that user's permissions are the security boundary.

## Upgrade Agent

# Upgrade Agent

The Upgrade Agent tool converts an Einstein Bot into an [Agentforce Service Agent](https://help.salesforce.com/s/articleView?id=service.bots_service_asa_about.htm&type=5). It reads your bot's dialogs, action steps, and intents, and generates the equivalent agent subagents and actions from them. Your original bot stays active during the move, so you can cut over at your own pace.

> **ℹ️ Info**: **Want to run it now?** [Open Upgrade Agent →](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/upgrade-agent>) allows you to select an org, bot and bot version and then starts conversion.

## How the Upgrade Agent works

**Subagents from dialogs.** The tool looks at your bot's dialogs and groups the ones with intents and utterances. Similar dialogs get grouped together, and each group becomes a subagent with a generated classification description, scope, and subagent instructions. Custom conditional logic in a dialog carries into that subagent's instructions.[Learn more about subagents](https://help.salesforce.com/s/articleView?id=ai.agent_topics.htm&type=5).

**Actions from action steps.** For every dialog it converts, the tool turns the dialog's action steps into agent actions, each with a generated description and input/output parameters. [Learn more about actions](https://help.salesforce.com/s/articleView?id=ai.copilot_actions_instructions.htm&language=en_US&type=5).

For example, a bot with `Service Request`, `Get Service Status`, `Reschedule Service`, `Customer Details`, and `Vehicle Details` dialogs — each backed by a flow-based action step — converts into a single **Service Appointment Management** subagent with `Manage Service Appointment`, `Get Service Request`, and `Get Car Models` actions.

## Upgrade Einstein bots to Agentforce Service Agents

The upgrade tool works directly in [labs.agentforce.com/upgrade-agent](https://labs.agentforce.com) or through your Claude Code.

Run the tool from Labs directly, or from local Claude Code.

### From the [Labs UI](https://labs.agentforce.com/upgrade-agent)

Connect to an org, then pick the bot to convert.

1. Click **Connect another org**.

   ![Bot Upgrade prompt with the org picker open, "Connect another org" highlighted at the bottom of the list](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/images/labs/bot-migration/org-picker-connect-another.png>)

2. In **Your Orgs**, click **Add an org**.

   ![Your Orgs dialog listing connected orgs, with "Add an org" highlighted](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/images/labs/bot-migration/your-orgs-add-an-org.png>)

3. Click **Manually connect**.

   ![Add-an-org menu with "Manually connect" highlighted alongside "Connect existing org" and "Get a fresh org"](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/images/labs/bot-migration/your-orgs-manually-connect-menu.png>)

4. Choose **Connect Production Org** or **Connect Sandbox Org** —  expand **Use custom domain** and enter a login domain — then click **Connect**.

   ![Manually connect screen with Connect Production Org, Connect Sandbox Org, and a custom login domain field](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/images/labs/bot-migration/manually-connect-org.png>)


> **ℹ️ Info**: To connect an internal orgfarm sandbox org, click **Add an org → Connect existing org** instead — this lists every orgfarm org you own. Not all orgfarm sandbox orgs can be connected to the tool; this is a known limitation.
>
>    ![Your Orgs dialog with the Add an org menu open on "Connect existing org", showing available orgfarm orgs](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/images/labs/bot-migration/orgfarm-connect-existing-org.png>)

5. Once the org is connected, click **Pick a bot**. This lists every bot and bot version in that org.

   ![Pick-a-bot dropdown listing bots in the connected org with their last-modified timestamps](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/images/labs/bot-migration/pick-a-bot-list.png>)

6. Click **Coding agent in browser** then select **Coding agent in browser**.

   ![Upgrade prompt with the coding-agent picker open, Coding agent in browser selected among Claude Code, Codex, Cursor, Antigravity, and Windsurf](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/images/labs/bot-migration/select-coding-agent-in-browser.png>)

7. Click the arrow button to start conversion.

   ![Upgrade prompt with a bot selected and "Convert with Labs Agent" tooltip showing on the submit arrow](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/images/labs/bot-migration/convert-with-labs-agent.png>)

8. Press **Enter** to accept the default prompt in the coding session that opens.

   ![In-browser coding session with the /agentforce-bot-upgrade command pre-filled and ready to run](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/images/labs/bot-migration/coding-session-default-prompt.png>)

Alternatively, click **+** icon and select the bot package zip as input. However, org should be selected before you begin conversion as the converted agent file is deployed in the org directly.
![Attach menu with "Attach bot metadata zip" highlighted alongside past-conversation and context-file options](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/images/labs/bot-migration/attach-bot-metadata-zip-menu.png>)

### From Claude Code

Run the conversion through the `agentforce-bot-upgrade` skill if you're already working in Claude Code, or want the upgrade scriptable and running on your own machine.

> **ℹ️ Prerequisites**: - sf-cli installed, version 2.0.0 or later
> - Claude Code installed and set up

**Check or install sf-cli:**

```bash
sf --version
```

If it's missing or outdated, install or update it from the [Salesforce CLI setup guide](https://developer.salesforce.com/tools/salesforcecli).

**Install the skill:**

1. If you have any ADLC skills or plugins (`agentforce-generate`, `agentforce-test`, `agentforce-observe`) installed in `~/.claude`, delete them first — the bot-upgrade skill installs its own versions.
2. Create and `cd` into a new, empty folder. This is the root the skill runs from and where converted bots land.
3. Install the skills package:

   ```bash
   npx skills add forcedotcom/sf-skills
   ```

   ![Terminal running npx skills add forcedotcom/sf-skills, showing the install prompt and a searchable list of 102 available skills](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/images/labs/bot-migration/npx-skills-add-install.png>)

4. Select `agentforce-bot-upgrade`, `agentforce-generate`, `agentforce-observe`, and `agentforce-test`, then confirm.

   ![Skill selection list with agentforce-bot-upgrade, agentforce-generate, agentforce-observe, and agentforce-test checked](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/images/labs/bot-migration/skills-selected-bot-upgrade.png>)

5. Select **Claude Code** as the agent to install to.

   ![Agent picker listing Claude Code selected among universal and additional coding agents](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/images/labs/bot-migration/select-claude-code-agent.png>)

6. Select **Project** as the installation scope — this keeps skills scoped to the current folder and available during the conversion.

   ![Installation scope prompt with Project selected over Global](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/images/labs/bot-migration/installation-scope-project.png>)


> **⚠️ Warning**: Choosing **Global** scope can leave the skill's symlinks missing, so the skill won't be available in a Claude Code session.

7. Run `claude` in the terminal and invoke the skill. It supports both interactive and non-interactive modes.

**Create Org Alias**

If you haven't aliased the org yet, then create an alias as mentioned below.

```bash
sf org login web --instance-url <org_url> --alias <org-alias>
```

**Invoke the skill:**

```bash
/agentforce-bot-upgrade --mode online --org-alias <alias> --bots bot1:v1,bot2:v2,bot3:v3 --interactive true/false
```

### Considerations

**These bot elements aren’t moved or converted by the Upgrade Agent.**

- Goals
- Knowledge feedback
- Action steps with no output, or that exist only for dialog navigation
- Action steps that create feedback logs
- Logic that routes to a specific queue on escalation
- Welcome messages with menu options (routing from menu options isn't supported; use prechat config instead)
- Option buttons where the option count exceeds 11
- File attachments (inbound or outbound)

**These bot elements aren’t moved by the Create Agent from Bot tool, but you can add similar functionality to your agent after creating it.**

- The bot user isn’t moved. After creating an agent, you can assign a user to it from the agent Details page.
- The bot’s connected channels and Omni-Flows aren’t moved. See [Connect Your AI Agent to Customer Channels](https://help.salesforce.com/s/articleView?id=service.bots_service_asa_connect_channels.htm&language=en_US&type=5).
- [System dialogs](https://help.salesforce.com/s/articleView?id=service.bots_service_system_bot_dialog.htm&language=en_US&type=5), such as the Welcome dialog, aren’t moved. You can add a static welcome and transfer message from the agent Details page.
- Dialogs that use data from your knowledge base, including those related to Article Answers and Generative Knowledge Answers, aren’t moved. To use knowledge articles with your agent, see [Manage Data Sources](https://help.salesforce.com/s/articleView?id=ai.agent_setup_data_sources.htm&language=en_US&type=5).
- Dialogs that transfer the conversation aren’t moved. To transfer conversations, add the [Escalation subagent](https://help.salesforce.com/s/articleView?id=ai.copilot_topics_ref_escalation.htm&language=en_US&type=5) to your agent.
- Rich content and messaging components aren’t moved. To format agent responses based on messaging components, set up [adaptive response formats](https://help.salesforce.com/s/articleView?id=ai.agent_response_enable.htm&language=en_US&type=5).
- Action steps that use an Apex class or flow are moved, but other action steps, such as those that use object search, aren’t moved. After creating the agent, you can add custom actions. See [Create a Custom Agent Action](https://help.salesforce.com/s/articleView?id=ai.copilot_actions_custom.htm&language=en_US&type=5).
- The bot’s primary language is moved to your agent, but additional languages aren’t. Currently, you can create AI agents from a bot with English as a primary language only. To add additional languages to your agent, see [Use Additional Languages in Enhanced Chat Conversations}(https://help.salesforce.com/s/articleView?id=ai.service_agent_languages.htm&type=5).

**General Considerations**

- The Upgrade Agent tool uses generative AI to create agent subagents and actions based on your bot’s dialogs, intents, and other information.
- For the best results, make sure that your bot follows best practices before you create an agent from it. See [Use Intents to Understand Your Customers](https://help.salesforce.com/s/articleView?id=service.bots_service_train_bot.htm&language=en_US&type=5) and [Guidelines for the Cross-Lingual Intent Model](https://help.salesforce.com/s/articleView?id=service.bots_service_cross_lingual_intent_guidelines.htm&language=en_US&type=5).
- Before activating the agent, carefully review and test it. See [Best Practices for Topic Instructions](https://help.salesforce.com/s/articleView?id=ai.copilot_topics_instructions.htm&language=en_US&type=5) and [Test Your Agent](https://help.salesforce.com/s/articleView?id=ai.agent_parent_test.htm&language=en_US&type=5).
  - If your bot involves context variable, deploy the agent in messaging embedding channel and test primary use cases
- Agentforce Service Agent is a consumption-based product that bills based on usage type.

## Next steps

1. Preview the converted agent in **preview mode** in Agent Builder.
   - Context variables don't work in preview mode.
   - Deploy the agent to a messaging embedded channel to test with context variables.
2. The Upgrade Agent tool doesn't generate or evaluate test cases against the converted agent. Test it end-to-end in the [Agentforce Testing Center](https://help.salesforce.com/s/articleView?id=005228642&type=1) before you activate it.
3. [Connect Your AI Agent to Channels](https://help.salesforce.com/s/articleView?id=service.bots_service_asa_connect_channels.htm&type=5)
4. [Transfer Conversations from an Enhanced Bot to an Agentforce Service Agent](https://help.salesforce.com/s/articleView?id=service.bots_service_asa_transfer_conversation.htm&type=5)

---

# Getting Started

## Agentforce Labs

- [Start Building](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/start>): Create your first agent in minutes, no setup. Keep working in your IDE of choice.
- [Agents](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/agents>): Create and edit Agentforce agents in a fast, lightweight editor.
- [Upgrade Agent](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/upgrade-agent>): Convert your Einstein Bot into an Agentforce agent.
- [Troubleshoot & Optimize](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/troubleshoot>): Diagnose and fix agent issues like wrong topic routing, a failing action, or a flaky response, all against your own org.
- [Salesforce Development Skills](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/docs/skills>): Choose Salesforce platform skills and Agentforce lifecycle plugins for your coding agent.
- [nanoRag](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/nanorag>): Let your agents reference documents when answering, no vector database required.
- [AgentLens Visualizer](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/agentlens>): Debug Agentforce traces with interactive visualizations.
- [MCP Servers](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/mcp>): Connect your agents to external tools and data via Model Context Protocol servers.
- [Agentforce Grid MCP & Skills](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/docs/agentforce-grid>): Equip Claude Code to manage Agentforce Grid workbooks with the official MCP server — run it via npx, no clone or build required.
- [Salesforce Docs MCP](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/docs/salesforce-docs-mcp>): Browse Salesforce documentation directly from your IDE using the Model Context Protocol.
- [GUI Process Automation](<https://www.salesforceairesearch.com/gpa>): GPA is the next generation of RPA, an agent that learns tasks by watching a user perform them once.
- [MCP+](<https://mcp-plus.github.io/>): A context optimization layer that filters MCP tool outputs down to only the relevant context, cutting inference costs.
- [SkillOrchestra](<https://jiayuww.github.io/SkillOrchestra/>): A skill-centric framework that routes tasks to the right agent based on competence and cost.
- [Agentforce for Google Workspace](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/docs/agentforce-slides>): Ask your Agentforce agent questions in Google Slides and insert answers into a slide.
## Stay Up to Date

### [What's New](<https://af-fir-pilot-fe-4c8fb71abbfe.pine-oregon.herokuapp.com/releases>)

See the latest updates and what's new in Labs.

---

# IDE Integrations

## Docs MCP

# Docs MCP

Your AI assistant can search and retrieve official Salesforce docs without leaving the IDE. Help articles, Developer Guides, release notes, all indexed.

## Available tools

| Tool | What it does |
| --- | --- |
| `salesforce_docs_search` | Semantic search across Salesforce doc collections. Returns ranked excerpts with source URLs. |
| `salesforce_docs_fetch` | Retrieves the full content of a page by `documentPath` (returned from search). |

Once connected, ask questions like "How do I configure a JWT bearer flow?" or "What fields does the AgentScript config block support?" and get cited answers inline.

## Install

#### Cursor

**One-click:** [Install in Cursor](cursor://anysphere.cursor-deeplink/mcp/install?name=salesforce-docs&config=eyJ1cmwiOiJodHRwczovL3NhbGVzZm9yY2UtZG9jcy03NjI1ODc0NGM5ZDcuaGVyb2t1YXBwLmNvbS9hcGkvbWNwIn0%3D)

Or add to `~/.cursor/mcp.json` (Windows: `%USERPROFILE%\.cursor\mcp.json`):

```json
{
  "mcpServers": {
    "salesforce-docs": {
      "url": "https://salesforce-docs-76258744c9d7.herokuapp.com/api/mcp"
    }
  }
}
```

Restart Cursor after saving.

#### Claude Code

```bash
claude mcp add --transport http salesforce-docs https://salesforce-docs-76258744c9d7.herokuapp.com/api/mcp
```

> **ℹ️ Restart required**: Claude Code loads MCP servers at startup. Restart after adding.

#### Windsurf

Add to `~/.codeium/windsurf/mcp_config.json`:

```json
{
  "mcpServers": {
    "salesforce-docs": {
      "url": "https://salesforce-docs-76258744c9d7.herokuapp.com/api/mcp"
    }
  }
}
```

#### VS Code

Add to `.vscode/mcp.json` in your workspace:

```json
{
  "servers": {
    "salesforce-docs": {
      "type": "http",
      "url": "https://salesforce-docs-76258744c9d7.herokuapp.com/api/mcp"
    }
  }
}
```

#### Other

Any client that supports the [MCP open protocol](https://modelcontextprotocol.io/) works. Point it at:

```
https://salesforce-docs-76258744c9d7.herokuapp.com/api/mcp
```

If your client requires auth headers:

```json
{
  "url": "https://salesforce-docs-76258744c9d7.herokuapp.com/api/mcp",
  "headers": {
    "Authorization": "Bearer YOUR_TOKEN_HERE"
  }
}
```

## Verify

✅ **Verify:**

Ask your AI assistant:

```
Search Salesforce docs for "How do I set up a record-triggered flow?"
```

**Expected:** The assistant calls `salesforce_docs_search` and returns excerpts with titles, URLs, and relevance scores.

## Get better results

- **Ask full questions.** "How do I configure OAuth 2.0 JWT bearer flow?" beats "JWT flow"
- **Scope by collection.** Pass `collection: "developer-lwc"` or `collection: "admin-ai"` to narrow the search
- **Keep limits low.** Excerpts can be long. Default is 5; lower it in busy sessions
- **Fetch for full pages.** Search returns excerpts. Follow up with `salesforce_docs_fetch` using the `documentPath` when you need complete procedures or tables
