Rebalance Salesforce lead assignments using an agent skill

low complexityCost: Usage-based

Prerequisites

Prerequisites
  • Claude Code or another agent that supports the Agent Skills standard
  • Salesforce Connected App with OAuth or a session token stored as SALESFORCE_ACCESS_TOKEN environment variable
  • Salesforce instance URL stored as SALESFORCE_INSTANCE_URL environment variable
  • Slack Bot Token stored as SLACK_BOT_TOKEN environment variable

Overview

This approach creates an agent skill that redistributes and rebalances lead assignments. Unlike the always-on trigger approaches, this is useful for bulk reassignment — when a rep leaves, when you add someone new to the team, or when assignments have drifted out of balance over time.

The skill queries all Leads assigned in the last 7 days, groups them by owner, identifies imbalances, proposes reassignments, and posts a summary to Slack.

Step 1: Create the skill

Create .claude/skills/rebalance-sf-leads/SKILL.md:

---
name: rebalance-sf-leads
description: Audit and rebalance Salesforce lead assignments across the sales team
disable-model-invocation: true
allowed-tools: Bash(python *)
---
 
Audit recent Salesforce lead assignments and rebalance if distribution is uneven.
 
Usage:
- `/rebalance-sf-leads` — audit lead distribution from the last 7 days, report only
- `/rebalance-sf-leads --fix` — rebalance leads and notify reps in Slack
 
Run: `python $SKILL_DIR/scripts/rebalance.py $@`

Step 2: Write the script

Create .claude/skills/rebalance-sf-leads/scripts/rebalance.py:

#!/usr/bin/env python3
"""Audit and rebalance Salesforce lead assignments."""
import os, sys, json, requests
from datetime import datetime, timedelta
from collections import Counter
 
SF_TOKEN = os.environ.get("SALESFORCE_ACCESS_TOKEN")
SF_URL = os.environ.get("SALESFORCE_INSTANCE_URL", "").rstrip("/")
SLACK_TOKEN = os.environ.get("SLACK_BOT_TOKEN")
 
if not all([SF_TOKEN, SF_URL]):
    print("ERROR: Set SALESFORCE_ACCESS_TOKEN and SALESFORCE_INSTANCE_URL")
    sys.exit(1)
 
SF_HEADERS = {
    "Authorization": f"Bearer {SF_TOKEN}",
    "Content-Type": "application/json",
}
 
REPS = [
    {"name": "Alice", "sf_user_id": "005xx0000012345", "slack_user_id": "U01AAAA"},
    {"name": "Bob",   "sf_user_id": "005xx0000023456", "slack_user_id": "U02BBBB"},
    {"name": "Carol", "sf_user_id": "005xx0000034567", "slack_user_id": "U03CCCC"},
    {"name": "Dave",  "sf_user_id": "005xx0000045678", "slack_user_id": "U04DDDD"},
]
 
rep_ids = {r["sf_user_id"] for r in REPS}
fix_mode = "--fix" in sys.argv
since = (datetime.utcnow() - timedelta(days=7)).strftime("%Y-%m-%dT%H:%M:%SZ")
 
# Query recent leads assigned to reps in the roster
soql = (
    f"SELECT Id, Name, Email, Company, OwnerId "
    f"FROM Lead "
    f"WHERE CreatedDate >= {since} "
    f"AND OwnerId IN ({','.join(repr(r) for r in rep_ids)}) "
    f"ORDER BY CreatedDate ASC"
)
resp = requests.get(
    f"{SF_URL}/services/data/v59.0/query",
    headers=SF_HEADERS,
    params={"q": soql},
)
resp.raise_for_status()
leads = resp.json().get("records", [])
 
if not leads:
    print("No leads found in the last 7 days.")
    sys.exit(0)
 
# Count distribution
dist = Counter(l["OwnerId"] for l in leads)
rep_map = {r["sf_user_id"]: r["name"] for r in REPS}
 
print(f"Lead distribution (last 7 days) — {len(leads)} total leads:\n")
for rep in REPS:
    count = dist.get(rep["sf_user_id"], 0)
    print(f"  {rep['name']}: {count} leads")
 
ideal = len(leads) // len(REPS)
remainder = len(leads) % len(REPS)
max_diff = max(dist.values()) - min(dist.values()) if dist else 0
 
print(f"\nIdeal per rep: {ideal} (remainder: {remainder})")
print(f"Max imbalance: {max_diff} leads")
 
if max_diff <= 1:
    print("\nDistribution is balanced. No action needed.")
    sys.exit(0)
 
if not fix_mode:
    print("\nImbalance detected. Run with --fix to rebalance.")
    sys.exit(0)
 
# Rebalance: redistribute all leads evenly
print("\nRebalancing...")
assignments = {r["name"]: [] for r in REPS}
for i, lead in enumerate(leads):
    rep = REPS[i % len(REPS)]
    if lead["OwnerId"] != rep["sf_user_id"]:
        patch = requests.patch(
            f"{SF_URL}/services/data/v59.0/sobjects/Lead/{lead['Id']}",
            headers=SF_HEADERS,
            json={"OwnerId": rep["sf_user_id"]},
        )
        patch.raise_for_status()
    assignments[rep["name"]].append(lead.get("Name", "Unknown"))
 
# Post summary to Slack
if SLACK_TOKEN:
    from slack_sdk import WebClient
    slack = WebClient(token=SLACK_TOKEN)
    summary_lines = [f"*{name}*: {len(assigned)} leads" for name, assigned in assignments.items()]
    slack.chat_postMessage(
        channel="#sales-ops",
        text=f"Lead rebalance complete — {len(leads)} leads redistributed",
        blocks=[{
            "type": "section",
            "text": {
                "type": "mrkdwn",
                "text": (
                    f"*Lead Rebalance Complete*\n"
                    f"{len(leads)} leads redistributed across {len(REPS)} reps:\n\n"
                    + "\n".join(summary_lines)
                ),
            },
        }],
    )
    print("Slack summary posted to #sales-ops")
 
    # DM each rep with their updated assignments
    for rep in REPS:
        rep_leads = assignments[rep["name"]]
        if not rep_leads:
            continue
        lead_list = "\n".join(f"  - {l}" for l in rep_leads[:20])
        if len(rep_leads) > 20:
            lead_list += f"\n  ...and {len(rep_leads) - 20} more"
        slack.chat_postMessage(
            channel=rep["slack_user_id"],
            text=f"Your leads have been rebalanced — you now have {len(rep_leads)} leads",
            blocks=[{
                "type": "section",
                "text": {
                    "type": "mrkdwn",
                    "text": f"*Your Leads Have Been Rebalanced*\nYou now have {len(rep_leads)} leads:\n{lead_list}",
                },
            }],
        )
 
print(f"\nDone. Final distribution: {', '.join(f'{r}: {len(a)}' for r, a in assignments.items())}")

Step 3: Run it

# Audit lead distribution (report only, no changes)
/rebalance-sf-leads
 
# Rebalance leads and notify reps in Slack
/rebalance-sf-leads --fix

When to use this approach

  • A rep leaves or joins and you need to rebalance the existing book of leads
  • You have a backlog of leads that were unevenly assigned and need redistribution
  • You want a one-time audit before turning on an always-on round-robin automation
  • End-of-quarter cleanup to ensure fair distribution before reporting

Need help implementing this?

We build and optimize automation systems for mid-market businesses. Let's discuss the right approach for your team.