Find stale Pipedrive deals and alert reps using a Claude Code skill
Install this skill
Download the skill archive and extract it into your .claude/skills/ directory.
stale-deals.skill.zipPrerequisites
This skill works with any agent that supports the Claude Code skills standard, including Claude Code, Claude Cowork, OpenAI Codex, and Google Antigravity.
- One of the agents listed above
- Pipedrive account with API access (Growth plan or higher)
- Slack bot with
chat:writepermission, added to the target channel
Why a Claude Code skill?
The other approaches in this guide are deterministic: they run the same logic every time, the same way. A Claude Code skill is different. You tell Claude what you want in plain language, and the skill gives it enough context to do it reliably.
That means you can say:
- "Show me all deals with no activity in the last 30 days"
- "Which reps have the most stale pipeline?"
- "Alert the team about deals that haven't been touched in 2 weeks"
The skill contains workflow guidelines, API reference materials, and a message template that the agent reads on demand. When you invoke the skill, Claude reads these files, writes a script on the fly, runs it, and reports results. If you ask for something different next time — a shorter threshold, a specific pipeline, a summary grouped by stage instead of owner — the agent adapts without you touching any code.
How it works
The skill directory has three parts:
SKILL.md— workflow guidelines telling the agent what steps to follow, which env vars to use, and what pitfalls to avoidreferences/— Pipedrive API patterns (REST endpoints, response formats, pagination) so the agent calls the right APIs with the right parameterstemplates/— a Slack message template so stale deal alerts are consistently formatted across runs
When invoked, the agent reads SKILL.md, consults the reference and template files as needed, writes a Python script, executes it, and reports what it posted. The reference files act as guardrails — the agent knows exactly which endpoints to hit and what the responses look like, so it doesn't have to guess.
What is a Claude Code skill?
A Claude Code skill is a reusable command you add to your project that Claude Code can run on demand. Skills live in a .claude/skills/ directory and are defined by a SKILL.md file that tells the agent what the skill does, when to run it, and what tools it's allowed to use.
In this skill, the agent doesn't run a pre-written script. Instead, SKILL.md provides workflow guidelines and points to reference files — API documentation, message templates — that the agent reads to generate and execute code itself. This is the key difference from a traditional script: the agent can adapt its approach based on what you ask for while still using the right APIs and message formats.
Once installed, you can invoke a skill as a slash command (e.g., /pd-stale-deal-alerts), or the agent will use it automatically when you give it a task where the skill is relevant. Skills are portable — anyone who clones your repo gets the same commands.
Step 1: Create the skill directory
mkdir -p .claude/skills/pd-stale-deal-alerts/{templates,references}This creates the layout:
.claude/skills/pd-stale-deal-alerts/
├── SKILL.md # workflow guidelines + config
├── references/
│ └── pipedrive-deals-api.md # Pipedrive API patterns
└── templates/
└── slack-stale-alert.md # Slack message template for stale deal alertsStep 2: Write the SKILL.md
Create .claude/skills/pd-stale-deal-alerts/SKILL.md:
---
name: pd-stale-deal-alerts
description: Find stale Pipedrive deals with no recent activity and alert owners in Slack
disable-model-invocation: true
allowed-tools: Bash, Read
---
## Goal
Find all open Pipedrive deals where `update_time` is older than a configurable threshold (default: 30 days). Group stale deals by owner, post a consolidated Slack message per rep listing their stale deals with values and days stale, and print a summary.
## Configuration
Read these environment variables:
- `PIPEDRIVE_API_TOKEN` — Pipedrive API token from Settings > Personal > Tools and Integrations > API (required)
- `PIPEDRIVE_COMPANY_DOMAIN` — your Pipedrive subdomain, e.g. "yourcompany" from yourcompany.pipedrive.com (required)
- `SLACK_BOT_TOKEN` — Slack bot token starting with xoxb- (required)
- `SLACK_CHANNEL_ID` — Slack channel ID starting with C (required)
Default stale threshold: 30 days. The user may request a different threshold.
## Workflow
1. Validate that all required env vars are set. If any are missing, print which ones and exit.
2. Fetch all open deals from Pipedrive, paginating if needed. See `references/pipedrive-deals-api.md`.
3. Fetch pipeline stages for human-readable context. See `references/pipedrive-deals-api.md`.
4. Filter deals where `update_time` is older than the threshold. Calculate days stale for each deal.
5. Group stale deals by `owner_id`. Fetch user names from Pipedrive so alerts address reps by name. See `references/pipedrive-deals-api.md`.
6. Post a consolidated Slack message per rep using the format in `templates/slack-stale-alert.md`.
7. Print a summary of how many reps were alerted and total stale deal count.
## Important notes
- `update_time` updates on ANY deal field change — including non-sales activity like editing the deal description or changing a custom field. Some deals may appear active despite no real engagement. Mention this caveat in your summary.
- Pipedrive's "rotting" visual indicator is configured per pipeline stage but is not exposed via API filters. The skill must calculate staleness from `update_time` directly.
- Paginate with `start` and `limit` query params (max 500 per page). Check `additional_data.pagination.more_items_in_collection` to determine if more pages exist.
- The API token does not expire (unlike OAuth tokens). It remains valid until the user regenerates it in Pipedrive settings.
- `SLACK_CHANNEL_ID` must be the channel ID (starts with `C`), not the channel name.
- The Slack bot must be invited to the target channel or `chat.postMessage` will fail with `not_in_channel`.
- Use `urllib.request` for HTTP calls (no external dependencies required).Understanding the SKILL.md
Unlike the script-based approach, this SKILL.md doesn't contain a Run: command pointing to a script. Instead, it provides:
| Section | Purpose |
|---|---|
| Goal | Tells the agent what outcome to produce |
| Configuration | Which env vars to read and what defaults to use |
| Workflow | Numbered steps with pointers to reference files |
| Important notes | Non-obvious context that prevents common mistakes |
The allowed-tools: Bash, Read setting lets the agent both read reference files and execute code. The agent writes its own script based on the workflow steps and reference materials.
Step 3: Add reference files
templates/slack-stale-alert.md
Create .claude/skills/pd-stale-deal-alerts/templates/slack-stale-alert.md:
# Slack Stale Deal Alert Template
Post one consolidated message per rep. Each message lists all of that rep's stale deals.
## Block Kit JSON
```json
{
"channel": "<SLACK_CHANNEL_ID>",
"text": "Stale Deal Alert: You have <count> deals with no activity in the last <threshold> days",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": ":warning: *Stale Deal Alert*\nYou have <count> deals with no activity in the last <threshold> days:"
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "1. *<deal_title>* ($<deal_value>) — <days_stale> days stale\n2. *<deal_title>* ($<deal_value>) — <days_stale> days stale\n3. *<deal_title>* ($<deal_value>) — <days_stale> days stale"
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "Total at-risk value: $<total_value>"
}
},
{
"type": "context",
"elements": [
{
"type": "mrkdwn",
"text": "Update these deals or close them to keep the pipeline accurate. <<pipedrive_link>|View your pipeline in Pipedrive>"
}
]
}
]
}
```
## Notes
- The top-level `text` field is required by the Slack API as a fallback for notifications and accessibility.
- Build one numbered list per rep. Sort deals by days stale (most stale first).
- Format values with comma separators (e.g., $75,000 not $75000).
- `value` may be null if not set on the deal. Display "N/A" when null.
- The Pipedrive pipeline link format: `https://<PIPEDRIVE_COMPANY_DOMAIN>.pipedrive.com/pipeline/default/user/everyone`.
- Individual deal links: `https://<PIPEDRIVE_COMPANY_DOMAIN>.pipedrive.com/deal/<DEAL_ID>`.
- If a rep has more than 10 stale deals, show the top 10 and add a line: "...and X more. View all in Pipedrive."
- Include the total at-risk value (sum of all stale deal values for that rep).references/pipedrive-deals-api.md
Create .claude/skills/pd-stale-deal-alerts/references/pipedrive-deals-api.md:
# Pipedrive Deals API Reference
## Fetch all open deals
Retrieve all deals with status "open" from Pipedrive. Paginate through results.
**Request:**
```
GET https://api.pipedrive.com/v1/deals?status=open&start=0&limit=500&api_token=<PIPEDRIVE_API_TOKEN>
```
**Response shape:**
```json
{
"success": true,
"data": [
{
"id": 98765,
"title": "Meridian Health — Data Platform",
"value": 75000,
"currency": "USD",
"status": "open",
"stage_id": 3,
"pipeline_id": 1,
"owner_id": 12345,
"org_id": 11111,
"person_id": 54321,
"expected_close_date": "2026-05-20",
"update_time": "2026-01-15 10:30:00"
}
],
"additional_data": {
"pagination": {
"start": 0,
"limit": 500,
"more_items_in_collection": false,
"next_start": 500
}
}
}
```
**Pagination:**
- `start` — offset for the first result (default: 0)
- `limit` — max results per page (max: 500)
- `more_items_in_collection` — if `true`, fetch next page with `start=<next_start>`
- Loop until `more_items_in_collection` is `false` or `data` is null/empty
## Fetch pipeline stages
Resolve numeric stage IDs to human-readable stage names.
**Request:**
```
GET https://api.pipedrive.com/v1/stages?api_token=<PIPEDRIVE_API_TOKEN>
```
**Response shape:**
```json
{
"success": true,
"data": [
{
"id": 1,
"name": "Qualified Lead",
"pipeline_id": 1,
"order_nr": 1
},
{
"id": 2,
"name": "Discovery",
"pipeline_id": 1,
"order_nr": 2
},
{
"id": 3,
"name": "Proposal Sent",
"pipeline_id": 1,
"order_nr": 3
}
]
}
```
Build a dictionary mapping `id` → `name` so you can include the stage in alert messages.
## Fetch user names
Resolve `owner_id` values to rep names for personalized alerts.
**Request:**
```
GET https://api.pipedrive.com/v1/users?api_token=<PIPEDRIVE_API_TOKEN>
```
**Response shape:**
```json
{
"success": true,
"data": [
{
"id": 12345,
"name": "Jane Smith",
"email": "jane@company.com",
"active_flag": true
}
]
}
```
Build a dictionary mapping `id` → `name` so each Slack message addresses the rep by name.
## Notes
- `update_time` is a datetime string in the format `YYYY-MM-DD HH:MM:SS` (Pipedrive local time). Parse it to compare against the stale threshold.
- `value` is a number (not a string). It may be null if the deal value hasn't been set.
- Stage IDs are numeric. Always resolve via the `/v1/stages` endpoint.
- API tokens do not expire. They remain valid until regenerated in Pipedrive settings.
- Rate limits: token-based daily budget (30,000 base tokens x plan multiplier x seats). A single deal list request costs 1 token. A 429 response means the daily budget is exhausted.Step 4: Test the skill
Invoke the skill conversationally:
/pd-stale-deal-alertsClaude will read the SKILL.md, check the reference files, write a script, run it, and report the results. A typical run looks like:
Checking for deals with no activity in the last 30 days...
Fetched 8 pipeline stages
Fetched 147 open deals (1 page)
Found 23 stale deals across 6 reps
Posted alert for Jane Smith (5 stale deals, $218,000 at risk)
Posted alert for Marcus Chen (4 stale deals, $142,500 at risk)
Posted alert for Sarah Lopez (4 stale deals, $95,000 at risk)
Posted alert for Dev Patel (4 stale deals, $87,000 at risk)
Posted alert for Emily Tran (3 stale deals, $63,500 at risk)
Posted alert for Raj Gupta (3 stale deals, $52,000 at risk)
Done. Alerted 6 reps about 23 stale deals ($658,000 total at-risk value).
Note: Staleness is based on update_time, which changes on any field edit —
some deals may have been modified without real sales activity.What the Slack alert looks like
Warning Stale Deal Alert
You have 3 deals with no activity in the last 30 days:
1. Meridian Health — Data Platform ($75,000) — 42 days stale
2. Contoso Ltd — Annual License ($28,000) — 35 days stale
3. Fabrikam Inc — Expansion ($15,000) — 31 days stale
Total at-risk value: $118,000
Update these deals or close them to keep the pipeline accurate. View your pipeline in Pipedrive
Because the agent generates code on the fly, you can also make ad hoc requests:
- "Show me deals stale for more than 60 days" — the agent adjusts the threshold
- "Which reps have the most stale pipeline value?" — the agent ranks reps by at-risk dollar amount
- "Alert only the Enterprise pipeline team" — the agent filters by pipeline before grouping
Check your Pipedrive pipeline for a deal you know hasn't been touched in a while, then run the skill. If no deals exceed the 30-day threshold, try a shorter window: "Show me deals with no activity in the last 7 days."
Step 5: Schedule it (optional)
Option A: Cron + Claude CLI
# Run every weekday at 9 AM (local time)
0 9 * * 1-5 cd /path/to/your/project && claude -p "Run /pd-stale-deal-alerts" --allowedTools 'Bash(*)' 'Read(*)'Option B: GitHub Actions + Claude
name: Pipedrive Stale Deal Alerts
on:
schedule:
- cron: '0 14 * * 1-5' # 9 AM ET (UTC-5) on weekdays
workflow_dispatch: {} # Manual trigger for testing
jobs:
alert:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: anthropics/claude-code-action@v1
with:
prompt: "Run /pd-stale-deal-alerts"
allowed_tools: "Bash(*),Read(*)"
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
PIPEDRIVE_API_TOKEN: ${{ secrets.PIPEDRIVE_API_TOKEN }}
PIPEDRIVE_COMPANY_DOMAIN: ${{ secrets.PIPEDRIVE_COMPANY_DOMAIN }}
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
SLACK_CHANNEL_ID: ${{ secrets.SLACK_CHANNEL_ID }}GitHub Actions cron schedules use UTC. If your sales team is in US Eastern, 9 AM ET = 2 PM UTC, so use 0 14 * * 1-5. Adjust accordingly for your timezone. Also, GitHub may delay scheduled runs by up to 15 minutes during periods of high load.
Option C: Cowork Scheduled Tasks
Claude Desktop's Cowork supports built-in scheduled tasks. Open a Cowork session, type /schedule, and configure the cadence — daily on weekdays at 9 AM is a good default for pipeline hygiene alerts. Each scheduled run has full access to your connected tools, plugins, and MCP servers.
Scheduled tasks only run while your computer is awake and Claude Desktop is open. If a run is missed, Cowork executes it automatically when the app reopens. For always-on scheduling, use GitHub Actions (Option B) instead. Available on all paid plans (Pro, Max, Team, Enterprise).
Troubleshooting
When to use this approach
- You want conversational flexibility — ad hoc queries like "which reps have the most stale pipeline?" alongside scheduled checks
- You want on-demand pipeline health checks during weekly reviews or 1:1s, not just automated notifications
- You're already using Claude Code and want skills that integrate with your workflow
- You want to run tasks in the background via Claude Cowork while focusing on other work
- You prefer guided references over rigid scripts — the agent adapts while staying reliable
When to switch approaches
- You need daily automated scans without human involvement → use n8n (recommended) for a fully autonomous scheduled workflow
- You want a zero-external-tools setup → use Pipedrive Workflow Automation (free on Growth+, but limited to date-based detection)
- You need per-stage thresholds with consistent, repeatable logic → use n8n where thresholds are configured once in the workflow
The Pipedrive API does not expose a "last sales activity" timestamp on the deal object. This skill uses update_time, which changes whenever any deal field is modified — including non-sales changes like updating a custom field or editing the description. For more precise activity-based detection, you would need to query the Activities API for each deal, which adds API calls and complexity. The n8n approach handles this in a structured workflow; with Claude Code, you can ask the agent to check activities on demand.
Common questions
Why not just use a script?
A script runs the same way every time. The Claude Code skill adapts to what you ask — different thresholds, filtered pipelines, summary by stage instead of owner, only deals above a certain value. The reference files ensure it calls the right APIs even when improvising, so you get flexibility without sacrificing reliability.
Does this use Claude API credits?
Yes. The agent reads skill files and generates code each time. Typical cost is $0.01-0.05 per invocation depending on how many deals are returned and how complex your request is. The Pipedrive and Slack APIs themselves are included in their respective plans.
How do I manage the Pipedrive API token?
Pipedrive API tokens do not expire — they remain valid until regenerated. Store the token in an environment variable and never commit it to source control. If you suspect the token is compromised, regenerate it in Settings > Personal > Tools and Integrations > API. The old token is immediately invalidated.
Can I run this skill on a schedule without a server?
Yes. GitHub Actions (Option B in Step 5) runs Claude on a cron schedule using GitHub's infrastructure. The free tier includes 2,000 minutes/month — more than enough for a daily stale deal check.
Can I set different thresholds per pipeline stage?
Yes. Ask the agent something like "alert on deals stale for 14 days in Discovery but 45 days in Negotiation." The agent will read the stages reference, apply per-stage thresholds, and group the results accordingly. This is one of the advantages of the conversational approach — you don't need to reconfigure a workflow to change the rules.
Cost
- Claude API — $0.01-0.05 per invocation (the agent reads files and generates code)
- Pipedrive API — included in Growth plan and above, token-based daily budget (30,000+ tokens/day)
- Slack API — included in all plans, no per-call cost
- GitHub Actions (if scheduled) — free tier includes 2,000 minutes/month
Looking to scale your AI operations?
We build and optimize automation systems for mid-market businesses. Let's discuss the right approach for your team.