# Assign a Slack channel to a client over the API

Discover an organization's integrations and a Slack integration's channels, then bind a channel to a client so its messages are assigned automatically — entirely over the API or MCP.

Salfio assigns incoming communications to clients using **assignment rules**. For Slack, a rule binds a **channel** to a **client**, so every message in that channel is attributed to the right client without any manual sorting.

This guide does the whole thing programmatically — no dashboard step — which is what you want when you're onboarding many clients at once. It walks the three calls end to end:

1.  **List integrations** to find your Slack integration's id.
2.  **List that integration's channels** to find the channel names.
3.  **Set the client's assignment rules** to bind a channel to a client.

Every call is shown as both a REST request and its MCP-tool equivalent — the two surfaces are 1:1 and share the same authorization and rate limits.

## Prerequisites

- A Salfio organization with **Slack** connected — see [Connect Slack](/docs/getting-started/connect-integration). The Slack bot must be a **member** of the channels you want to bind (invite it with `/invite @Salfio` in Slack); the channels endpoint only lists channels the bot is in.
- A Salfio **API key** (`sk_live_…`). See [Authentication](/docs/api/authentication) for how to mint one and how to present it.
- The **client id** you want to bind the channel to. List your clients with [`GET /v1/clients`](/docs/api/reference/listClients) if you don't have it yet.

All examples use the base URL `https://api.salfio.com/v1` and pass the key as `Authorization: Bearer sk_live_…`.

## 1. Find your Slack integration

List the organization's integrations and pick the one whose `type` is `slack`. The `integrationId` is what you'll bind the rule to.

    curl -s https://api.salfio.com/v1/integrations \
      -H "Authorization: Bearer sk_live_…"

    {
      "data": [
        {
          "integrationId": "8f2a…",
          "type": "slack",
          "providerEmail": "acme.slack.com",
          "connectionStatus": "active",
          "enabled": true,
          "connectedAt": "2026-05-01T09:12:00Z",
          "lastSyncedAt": "2026-06-28T20:00:00Z"
        }
      ],
      "meta": {}
    }

The list includes integrations of **every** status — check `connectionStatus` and `enabled`. A `disconnected`, `expired`, or disabled Slack integration can't serve channels until it's reconnected in the dashboard.

**MCP:** call the `list_integrations` tool (no arguments).

## 2. List the channels the bot is in

Pass the Slack `integrationId` to the channels endpoint. It returns the channels the Salfio bot is a **member** of — the valid set of names to bind.

    curl -s https://api.salfio.com/v1/integrations/8f2a…/channels \
      -H "Authorization: Bearer sk_live_…"

    {
      "data": [
        { "id": "C0123ABCD", "name": "acme-project", "isPrivate": false, "isExtShared": false, "memberCount": 12 },
        { "id": "C0456EFGH", "name": "general",      "isPrivate": false, "isExtShared": false, "memberCount": 48 }
      ],
      "meta": {}
    }

If a channel you expect is missing, the bot probably isn't in it — invite it in Slack and try again. Channels are cached briefly, so a freshly-invited channel may take a moment to appear.

This endpoint is **Slack-only**. Pointing it at a Gmail or meeting integration returns `400 invalid_argument` — those integration types have no channel concept.

**MCP:** call `list_integration_channels` with `{ "integration_id": "8f2a…" }`.

## 3. Bind the channel to a client

Create the rule with the `slackChannels` shorthand — a list of channel names. The server expands each name into the matching criteria, so you never hand-build the query structure.

**This is a replace-all write.** The `rules` array becomes the client's *entire* rule set — any integration you omit has its existing rule removed. Always read the current rules first ([`GET …/assignment-rules`](/docs/api/reference/getAssignmentRules)), modify, then send the full set back. Sending one rule when the client already had others will delete the others.

    curl -s -X PUT https://api.salfio.com/v1/clients/ad44…/assignment-rules \
      -H "Authorization: Bearer sk_live_…" \
      -H "Content-Type: application/json" \
      -d '{
        "rules": [
          { "integrationId": "8f2a…", "slackChannels": ["acme-project"] }
        ]
      }'

The response echoes the saved rules (with the server-expanded `filterCriteria`) plus a `reapplyStats` summary of how many existing conversations were re-evaluated against the new rules:

    {
      "data": {
        "rules": [
          {
            "ruleId": "7c1d…",
            "integrationId": "8f2a…",
            "integrationType": "slack",
            "providerEmail": "acme.slack.com",
            "filterCriteria": { "condition": "or", "rules": [ { "field": "channel", "operator": "equals", "value": "acme-project" } ] },
            "enabled": true,
            "priority": 0,
            "createdAt": "2026-06-29T08:00:00Z",
            "updatedAt": "2026-06-29T08:00:00Z"
          }
        ],
        "reapplyStats": {
          "conversationsEvaluated": 31,
          "conversationsMatched": 9,
          "assignmentsCreated": 9,
          "assignmentsRemoved": 0
        }
      },
      "meta": {}
    }

A save also kicks off a Slack history backfill for the bound channels, so recent messages get attributed too — not just new ones.

**MCP:** call `set_assignment_rules` with `{ "client_id": "ad44…", "rules": [ { "integration_id": "8f2a…", "slack_channels": ["acme-project"] } ] }`.

## Verify

Read the rules back. The Slack rule now carries the expanded `filterCriteria`:

    curl -s https://api.salfio.com/v1/clients/ad44…/assignment-rules \
      -H "Authorization: Bearer sk_live_…"

In the Salfio dashboard, the same binding shows up under the client's **Assignment Rules** — the API and the dashboard write the same data.

## Errors you might hit

| Status | Code | When |
|----|----|----|
| `400` | `invalid_argument` | The channels endpoint was called on a non-Slack integration, or you sent both `slackChannels` and `filterCriteria` on one rule (they're mutually exclusive). |
| `404` | `not_found` | The integration or client doesn't exist **or** belongs to a different organization. The API returns 404 either way — it never reveals cross-tenant existence. |
| `409` | `conflict` | The Slack integration isn't active (disconnected, expired, or disabled). Reconnect it in the dashboard. |
| `429` | `rate_limited` | You hit the org/tool rate limit. Honour `Retry-After`. See [Rate Limits](/docs/api/rate-limits). |

## Tips

**Bind several channels at once.** `slackChannels` takes a list: `["acme-project", "acme-support"]`. All of them are OR-ed into the client's Slack rule.

**Non-Slack integrations.** There's no channel concept for Gmail, meetings, etc. — bind those with a raw `filterCriteria` object instead of `slackChannels`. The two are mutually exclusive on a single rule.

**Onboarding many clients.** The three calls compose cleanly in a loop: list integrations once, list channels once, then `PUT` per client. Mind the [rate limits](/docs/api/rate-limits) — 100 requests/minute per organization.

## Related

- [`GET /v1/integrations`](/docs/api/reference/listIntegrations) — list integrations.
- [`GET /v1/integrations/{integrationId}/channels`](/docs/api/reference/listIntegrationChannels) — list a Slack integration's channels.
- [`GET`](/docs/api/reference/getAssignmentRules) / [`PUT /v1/clients/{clientId}/assignment-rules`](/docs/api/reference/setAssignmentRules) — read and replace a client's rules.
- [Model Context Protocol (MCP)](/docs/mcp) — the same tools from an MCP client.
- [Authentication](/docs/api/authentication) — minting and presenting an API key.

## Related pages

- [Administrator Tools](./administrator-tools.md)
- [Agent Tools](./mcp-external-servers.md)
- [Authenticated health check](./api-reference-gethealth.md)
- [Authentication](./api-authentication.md)
- [Cards](./cards.md)
- [Changelog](./changelog.md)
- [Changelog](../changelog.md)
- [Connect a workspace](./getting-started-connect-workspace.md)
- [Connect an integration](./getting-started-connect-integration.md)
- [Connect from Claude Desktop](./mcp-connect-claude-desktop.md)

# Agent Instructions

This portal answers questions programmatically. To receive a synthesized,
source-cited answer instead of crawling page by page, append the `?ask=`
query parameter to any page URL on this site:

    /guides/quickstart?ask=how+do+I+authenticate

Optional parameters:

- `&goal=<what-you-are-trying-to-do>` steers the answer toward your
  objective (e.g. `&goal=write+a+python+client`).
- `&version=<label>` scopes the answer to a mounted version when the
  portal publishes more than one.

The response is `text/markdown`: the answer followed by a `# Sources` list
of the portal pages it was grounded in. Status codes are the contract:

- `200` — the answer; `402` — the portal owner’s plan or answer credits are
  exhausted (surface this to your operator; do NOT retry); `429` — you are
  rate-limited; back off for the `Retry-After` seconds; `503` — the answer
  lane is temporarily unavailable; fall back to crawling the `.md` pages.

For the full corpus map read `llms.txt` at the site root; for the tool
surface (search + page fetch as MCP tools) see `/mcp`.
