> ## Documentation Index
> Fetch the complete documentation index at: https://developer.salesforge.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Create a multichannel sequence

> Create leads, build a multichannel sequence, enroll leads, and launch it using Salesforge and multichannel public APIs.

This tutorial walks through a full multichannel-sequence setup for a single workspace.

By the end of the flow, you will have:

1. Created three leads with different channel availability.
2. Created a multichannel sequence in draft state.
3. Added a LinkedIn branch and an email branch.
4. Optionally validated leads before enrollment.
5. Enrolled leads into the sequence.
6. Launched the sequence.

<Info>
  This tutorial assumes your sender profile already exists and is ready to use. The guide still covers how to list sender profiles and assign one to the sequence.
</Info>

<Warning>
  This is a two-service workflow.

  * Use the Salesforge public API to create leads in the workspace.
  * Use the multichannel public API to create the sequence, add nodes, validate leads, enroll them, and launch the sequence.

  The endpoint paths below are shown as they appear in the public API references.
</Warning>

## Prerequisites

Prepare these values before you start:

* `WORKSPACE_ID`
* `SALESFORGE_API_KEY`
* `SENDER_PROFILE_ID`
* a base URL for the Salesforge public API
* a base URL for the multichannel public API

Salesforge public API and multichannel public API use the same API key.

Use `SALESFORGE_API_KEY` for requests to both services.

Both APIs use bearer authentication.

```http theme={null}
Authorization: Bearer YOUR_API_KEY
```

For the shared authentication pattern, see [Authentication](/authentication).

## Step 1: Create leads in the workspace

Start by creating the leads you want to target.

For this guide, create three lead shapes:

* one lead with email only,
* one lead with LinkedIn only,
* one lead with both email and LinkedIn.

Use the Salesforge public API bulk-create endpoint so you can create the full sample set in one request.

`POST /workspaces/{workspaceID}/contacts/bulk`

<Note>
  The Salesforge public API uses `contacts` in endpoint paths and response field names. In this guide, those same records are referred to as leads to keep the sequencing terminology consistent.
</Note>

```json theme={null}
{
  "contacts": [
    {
      "firstName": "Alicia",
      "lastName": "North",
      "email": "alicia.north@example.com",
      "company": "Northwind Labs",
      "position": "Revenue Operations Manager",
      "tags": ["guide-multichannel", "email-only"],
      "customVars": {
        "segment": "SMB",
        "industry": "Software"
      }
    },
    {
      "firstName": "Bruno",
      "lastName": "Vale",
      "linkedinUrl": "https://www.linkedin.com/in/bruno-vale",
      "company": "Atlas Advisory",
      "position": "Founder",
      "tags": ["guide-multichannel", "linkedin-only"],
      "customVars": {
        "segment": "Founder-led"
      }
    },
    {
      "firstName": "Camila",
      "lastName": "Stone",
      "email": "camila.stone@example.com",
      "linkedinUrl": "https://www.linkedin.com/in/camila-stone",
      "company": "Signal Peak",
      "position": "VP Sales",
      "tags": ["guide-multichannel", "omnichannel"],
      "customVars": {
        "segment": "Mid-market",
        "priority": "High"
      }
    }
  ]
}
```

This request uses the public lead-creation rules exposed by the contact endpoint:

* `firstName` is required.
* At least one of `email` or `linkedinUrl` must be present.
* Each lead must include at least one tag through `tags` or `tagIds`.
* `customVars` is optional, but it cannot be an empty object.

The response returns created leads under the `contacts` field, with fields such as `id`, `email`, `linkedinUrl`, `tags`, and `customVars`.

The action examples later in this guide use `{{segment}}` in message content, so make sure each lead you plan to enroll has that custom variable populated.

Store the returned IDs for later steps. In this tutorial, refer to them as:

* `EMAIL_ONLY_LEAD_ID`
* `LINKEDIN_ONLY_LEAD_ID`
* `OMNICHANNEL_LEAD_ID`

If you want to confirm the leads are available in the workspace, you can also query the workspace contact-list endpoint.

`GET /workspaces/{workspaceID}/contacts`

## Step 2: Discover the available multichannel actions and conditions

Do not hardcode action or condition IDs in client code.

Use the reference endpoints first, then resolve the IDs you need from the response.

### List email actions

`GET /multichannel/actions?channel=email`

The action catalog includes a name, channel, branching type, and description. For an email step, the action you usually want is the one whose `name` is `send_email`.

### List LinkedIn actions

`GET /multichannel/actions?channel=linkedin`

Common LinkedIn actions include these names:

* `li_connection_request`
* `li_send_message`
* `li_send_inmail`
* `li_view_profile`
* `li_withdraw_connection_request`
* `li_like_latest_post`
* `li_follow_profile`

For this tutorial, resolve the `id` for `li_send_message` and store it as `LINKEDIN_MESSAGE_ACTION_ID`.

### List available conditions

`GET /multichannel/conditions`

Use this endpoint to resolve the condition IDs you want to use in the graph.

For the sample flow below, resolve the condition whose `name` is `has_linkedin_url` and store its ID as `HAS_LINKEDIN_URL_CONDITION_ID`.

Other useful conditions you may see in the same catalog include:

* `has_email_address`
* `request_accepted_within_days`
* `li_is_already_connected`
* `li_contact_replied_within_days`
* `check_email_validation_status`

## Step 3: Create the multichannel sequence

Create the sequence in draft state first.

`POST /multichannel/workspaces/{workspaceID}/sequences`

```json theme={null}
{
  "name": "Guide - LinkedIn then Email",
  "description": "A sample multichannel sequence that routes leads into LinkedIn or email outreach.",
  "timezone": "America/New_York",
  "settings": {
    "openTrackingEnabled": true,
    "plainTextEmailsEnabled": true,
    "optOutLinkEnabled": true,
    "optOutLinkText": "Unsubscribe",
    "trackOpportunitiesEnabled": false
  }
}
```

The create response returns the new sequence with fields such as:

* `id`
* `status`
* `timezone`
* `name`
* `description`
* `settings`

Store the returned `id` as `SEQUENCE_ID`.

<Note>
  `espMatchingEnabled` is part of the public settings model, but enabling it can be feature-gated by workspace plan. Keep it omitted or false unless you know the workspace supports it.
</Note>

If you need to refine sequence metadata after creation, use the update endpoint.

`PATCH /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}`

## Step 4: Set an explicit schedule

New sequences receive a default schedule, but production integrations should normally set the schedule explicitly.

`PUT /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}/schedule`

```json theme={null}
{
  "timezone": "America/New_York",
  "schedule": {
    "monday": { "enabled": true, "from": 9, "to": 17 },
    "tuesday": { "enabled": true, "from": 9, "to": 17 },
    "wednesday": { "enabled": true, "from": 9, "to": 17 },
    "thursday": { "enabled": true, "from": 9, "to": 17 },
    "friday": { "enabled": true, "from": 9, "to": 17 },
    "saturday": { "enabled": false },
    "sunday": { "enabled": false }
  }
}
```

The schedule model uses per-day `enabled`, `from`, and `to` values, where hours are integers from `0` to `23` and `to` must be greater than `from`.

To verify the schedule that is now attached to the sequence, call:

`GET /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}/schedule`

## Step 5: Assign an existing sender profile

The sender profile is the execution identity for the sequence. It is where mailbox and LinkedIn execution context come together.

First, list sender profiles for the workspace if you need to confirm the correct profile ID.

`GET /multichannel/workspaces/{workspaceID}/sender-profiles`

Then assign the sender profile to the sequence.

`POST /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}/sender-profiles`

```json theme={null}
{
  "senderProfileIds": [123]
}
```

Replace `123` with your actual sender profile ID or the `SENDER_PROFILE_ID` you already prepared.

To verify the assignment, call:

`GET /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}/sender-profiles`

## Step 6: Get the root branch before adding nodes

When a multichannel sequence is created, the root node is created automatically. You do not create that node yourself.

Before you can add an action or condition, you need the branch ID that leaves the root node.

`GET /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}/branches`

Each branch record includes:

* `id`
* `fromNodeId`
* `toNodeId`
* `name`
* `description`

Store the branch ID that starts from the root node as `ROOT_BRANCH_ID`.

## Desired sequence shape

```mermaid theme={null}
flowchart TD
  A[Root node] --> B{Has LinkedIn URL?}
  B -->|Yes| C[LinkedIn message]
  B -->|No| D[Email follow-up]
```

This is the sequence you are building in the next steps. The root node already exists when the sequence is created, then a condition node routes leads by LinkedIn availability: leads with a LinkedIn identity go to the LinkedIn message branch, while email-only leads go to the email branch.

## Step 7: Add the first condition node

This tutorial uses one condition node first so the sequence can route leads by channel availability.

Leads with a LinkedIn URL will go down the LinkedIn path. Leads without one will go down the email path.

`POST /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}/nodes/conditions`

```json theme={null}
{
  "branchId": 1001,
  "conditionId": 12,
  "minutesToWait": 0,
  "distributionStrategy": "equal"
}
```

Replace:

* `1001` with `ROOT_BRANCH_ID`
* `12` with `HAS_LINKEDIN_URL_CONDITION_ID`

The created node response returns the condition node itself. After that, list branches again.

`GET /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}/branches`

The condition node creates outgoing branches that you can identify by `name`, typically `yes` and `no`.

Store them as:

* `LINKEDIN_BRANCH_ID` for the `yes` branch
* `EMAIL_BRANCH_ID` for the `no` branch

## Step 8: Add a LinkedIn action and an email action

Now create one action node on each branch.

### LinkedIn message action

Use the branch for leads that do have a LinkedIn URL.

`POST /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}/nodes/actions`

```json theme={null}
{
  "branchId": 2001,
  "actionId": 31,
  "waitDays": 0,
  "distributionStrategy": "equal",
  "variants": [
    {
      "metadata": {
        "name": "LinkedIn intro",
        "message": "Hi {{first_name}}, I work with {{segment}} teams that want more control over multichannel outbound. Open to a short conversation?",
        "subject": ""
      },
      "exposureInPercentage": 100,
      "isEnabled": true
    }
  ]
}
```

Replace:

* `2001` with `LINKEDIN_BRANCH_ID`
* `31` with `LINKEDIN_MESSAGE_ACTION_ID`

### Email action

Use the branch for leads that do not have a LinkedIn URL.

`POST /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}/nodes/actions`

```json theme={null}
{
  "branchId": 2002,
  "actionId": 14,
  "waitDays": 1,
  "distributionStrategy": "equal",
  "variants": [
    {
      "metadata": {
        "name": "Email follow-up",
        "subject": "Quick intro for {{company}}",
        "message": "Hi {{first_name}}, I wanted to share a simple way {{segment}} teams use multichannel workflows without splitting leads across separate systems.",
        "allowed_validation_statuses": ["safe", "catch_all"]
      },
      "exposureInPercentage": 100,
      "isEnabled": true
    }
  ]
}
```

Replace:

* `2002` with `EMAIL_BRANCH_ID`
* `14` with the action ID whose `name` is `send_email`

This gives the sequence both channels while still handling mixed lead shapes in one graph.

If you want a lead to receive LinkedIn first and email later in the same branch, keep extending the graph by listing branches again after node creation, then creating new actions on the downstream branch IDs returned by the API.

## Step 9: Inspect the graph you just created

Before you validate or enroll leads, inspect the sequence structure.

Useful inspection endpoints are:

* `GET /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}`
* `GET /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}/nodes`
* `GET /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}/nodes/{nodeID}`
* `GET /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}/branches`

These responses let you verify:

* the sequence is still in `draft` status,
* the correct sender profile is attached,
* the nodes are present with the right `actionId` or `conditionId`,
* the branch IDs and `yes` or `no` routing look correct.

## Step 10: Optionally run validation before enrollment

Validation is optional, but it is useful if you want to enroll leads based on email quality or re-use a validation run as part of your selection logic.

<Note>
  Validation in this flow is email-only. Use it for leads that have an email address, and add LinkedIn-only leads through a separate enrollment filter instead of putting them in the validation run.
</Note>

Start the validation run first.

`POST /multichannel/workspaces/{workspaceID}/validations`

```json theme={null}
{
  "filters": {
    "leadIds": [
      "EMAIL_ONLY_LEAD_ID",
      "OMNICHANNEL_LEAD_ID"
    ]
  },
  "limit": 2
}
```

The create response returns `validationJobID`. Use that value as the run identifier in later steps.

To inspect the results, call:

`GET /multichannel/workspaces/{workspaceID}/validations/{runID}/results`

Validation filters can be broader than `leadIds`. The API also supports filters such as:

* `tagIds`
* `esps`
* `validationStatuses`
* `customVars`
* `searchQuery`
* `hasValidLinkedIn`
* `hasEmail`
* `excludeContacted`

That makes validation a reusable pre-enrollment selection layer rather than only a one-off email hygiene check.

## Step 11: Enroll leads into the sequence

The multichannel public API supports several enrollment styles.

### Option A: Enroll explicit lead IDs

Use this when you already know the leads you want in the sequence.

`POST /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}/enrollments`

```json theme={null}
{
  "filters": {
    "leadIds": [
      "EMAIL_ONLY_LEAD_ID",
      "LINKEDIN_ONLY_LEAD_ID",
      "OMNICHANNEL_LEAD_ID"
    ]
  },
  "limit": 3
}
```

This is the most explicit enrollment mode, and it is a good fit when another system already selected the audience.

### Option B: Enroll from a validation run

Use this when you want the validation job to become the source for enrollment.

`POST /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}/enrollments`

```json theme={null}
{
  "filters": {
    "validationRunId": "VALIDATION_JOB_ID",
    "validationStatuses": ["safe", "catch_all"]
  },
  "limit": 50
}
```

This pattern is useful when you want the validation run to supply the email-capable audience for the sequence.

### Option C: Add LinkedIn-only leads with another enrollment filter

Use this as a second enrollment call when you also want to add the LinkedIn-only segment to the same sequence.

`POST /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}/enrollments`

```json theme={null}
{
  "filters": {
    "tagIds": ["LINKEDIN_ONLY_TAG_ID"],
    "hasValidLinkedIn": true
  },
  "limit": 50
}
```

Use the tag ID that represents your `linkedin-only` segment in the workspace. This keeps the LinkedIn-only audience out of the email-validation step while still adding it through the same enrollment endpoint.

The enrollment filters support these selectors:

* `leadIds`
* `tagIds`
* `esps`
* `validationStatuses`
* `validationRunId`
* `hasValidLinkedIn`
* `hasEmail`
* `limit`

If you combine `validationRunId` with explicit `leadIds`, the service resolves the validation run and intersects it with the lead ID list instead of treating those filters as unrelated sets.

In practice, that means a common pattern is: first enroll the validated email-capable leads with `validationRunId`, then make a second enrollment call with a different filter such as `tagIds` to add the LinkedIn-only audience.

The enrollment response returns the `leadIds` that were actually enrolled.

## Step 12: Verify enrollments and launch the sequence

Before launch, re-check the sequence details.

`GET /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}`

The detailed response includes:

* `sequence`
* `nodes`
* `branches`
* `activeEnrollmentCount`

Once the graph, schedule, sender profile, and enrollments look correct, launch the sequence.

`PATCH /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}/launch`

This endpoint does not require a request body. A successful response returns the sequence in its launched state, with status set to `active`.

<Warning>
  Treat launch as the point where the sequence graph becomes locked. In the multichannel-api service, launched sequences cannot be structurally edited: creating nodes, deleting nodes, or rewiring branches is blocked after launch.

  Operational updates still remain available through public endpoints. The code continues to allow adding new enrollments after launch, and schedule updates also remain available. Public update endpoints for sequence metadata/settings and sender-profile assignment are still exposed as well, so the important restriction here is specifically on changing the workflow structure after launch.
</Warning>

The response returns the sequence in its launched state. That is the point where the sequence can begin active execution.

## Step 13: Know the follow-up endpoints you will use next

After the first launch, these are the endpoints you will usually use operationally:

* `GET /multichannel/workspaces/{workspaceID}/sequences` to list sequences
* `GET /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}` to inspect one sequence in detail
* `PATCH /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}/status` to pause or resume a sequence
* `POST /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}/enrollments/remove` to remove leads by filter
* `PATCH /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}/nodes/actions/{nodeID}` to update an action node
* `DELETE /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}/nodes/{nodeID}` to remove a node
* `DELETE /multichannel/workspaces/{workspaceID}/sequences/{sequenceID}` to delete the sequence

Those are maintenance endpoints. The create-to-launch flow stays the same: create leads, resolve action and condition IDs, create the sequence, set schedule, assign sender profile, build nodes, optionally validate, enroll, inspect, and launch.

## Related resources

* [Authentication](/authentication)
* [Salesforge API authentication](/salesforge-api/api-endpoints)
* [Multichannel Sequence (V2)](/salesforge-api/concepts/multichannel-sequence)
* [Sender Profile](/salesforge-api/concepts/sender-profile)
