Find stale Pipedrive deals and alert reps using a Claude Code skill

low complexityCost: Usage-based

Prerequisites

Compatible agents

This skill works with any agent that supports the Claude Code skills standard, including Claude Code, Claude Cowork, OpenAI Codex, and Google Antigravity.

Prerequisites
  • One of the agents listed above
  • Pipedrive account with API access (Growth plan or higher)
  • Slack bot with chat:write permission, added to the target channel
Environment Variables
# Pipedrive API token (Settings > Personal > Tools and Integrations > API)
PIPEDRIVE_API_TOKEN=your_value_here
# Your Pipedrive subdomain (e.g. 'yourcompany' from yourcompany.pipedrive.com)
PIPEDRIVE_COMPANY_DOMAIN=your_value_here
# Slack bot token starting with xoxb- (chat:write scope required)
SLACK_BOT_TOKEN=your_value_here
# Slack channel ID starting with C (right-click channel > View channel details)
SLACK_CHANNEL_ID=your_value_here

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:

  1. SKILL.md — workflow guidelines telling the agent what steps to follow, which env vars to use, and what pitfalls to avoid
  2. references/ — Pipedrive API patterns (REST endpoints, response formats, pagination) so the agent calls the right APIs with the right parameters
  3. templates/ — 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 alerts

Step 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:

SectionPurpose
GoalTells the agent what outcome to produce
ConfigurationWhich env vars to read and what defaults to use
WorkflowNumbered steps with pointers to reference files
Important notesNon-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-alerts

Claude 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

What you'll get
#sales-hygiene
Pipeline Botapp9:41 AM

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
Test with a known stale deal

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 schedules run in UTC

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
Staleness is based on update_time, not actual sales activity

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.