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_TOKENenvironment variable - Salesforce instance URL stored as
SALESFORCE_INSTANCE_URLenvironment variable - Slack Bot Token stored as
SLACK_BOT_TOKENenvironment 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 --fixWhen 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.