Skip to content
pdcli
Get started

Sales analytics

pdcli ships a handful of read-only analytics commands. Each fetches deals (and stages/activities or goals where needed) and computes the numbers locally, so they work against any account without extra setup. All accept the global --output/--jq/--fields flags.

The Sales Velocity Equation: how much deal value your pipeline produces per day.

velocity/day = (open opportunities × win rate × avg won value) / avg cycle days

The four levers are measured over a trailing window (--period, default 90d, accepts Nd or Nm), scoped optionally to a --pipeline and/or --owner:

  • Open opportunities — count of currently open deals.
  • Win rate — won / (won + lost) among deals decided in the window, keyed on won_time/lost_time.
  • Avg won value — mean value of deals won in the window.
  • Avg cycle days — mean of won_time − add_time for those won deals.
Terminal window
pdcli metrics velocity --period 30d --pipeline 1
┌────────────────────┬───────────────────┐
│ Metric │ Value │
├────────────────────┼───────────────────┤
│ Open opportunities │ 58 │
│ Win rate (30d) │ 41.2% (7W/10L) │
│ Avg won value │ 8200 │
│ Avg cycle (days) │ 34.5 │
│ Velocity / day │ 5676 │
└────────────────────┴───────────────────┘

If a lever can't be computed (no decided deals, no won deals, or a zero cycle), it shows n/a and velocity is n/a rather than a misleading zero.

A one-call rollup of deal value, computed server-side: Pipedrive returns per-currency totals, a probability-weighted total, and a deal count, so there's no list to page through. It's the cheapest way to ask "what's my pipeline worth right now?"

Terminal window
pdcli deal summary
pdcli deal summary --status open --pipeline 1
┌──────────┬─────────┬──────────┬───────┐
│ Currency │ Total │ Weighted │ Count │
├──────────┼─────────┼──────────┼───────┤
│ EUR │ €148000 │ €132000 │ 24 │
│ USD │ $52000 │ $41600 │ 9 │
└──────────┴─────────┴──────────┴───────┘

Narrow the set with --status (open/won/lost), --pipeline, --stage, or a saved --filter. Totals come pre-formatted per currency from the API; --output json returns the raw summary object (per-currency totals, grand totals, and counts) for scripting.

Stage-to-stage conversion. This is an approximation, stated honestly: Pipedrive doesn't hand back per-deal stage history in a single cheap call, so the funnel infers stage reach from each closed deal's final stage — a deal counts as having reached every stage up to and including the one it ended in, and won deals count for every stage. Accurate per-stage flow would need per-deal history mining; this stays one list call per status.

Closed deals are taken over --period (default 90d). The current open distribution per stage is shown alongside.

Terminal window
pdcli funnel --pipeline 1 --period 180d
┌──────────────────────┬──────────────────────────┬─────────────────┬──────────┬────────────┐
│ Stage │ Reached (closed, 180d) │ Conv. from prev │ Open now │ Open value │
├──────────────────────┼──────────────────────────┼─────────────────┼──────────┼────────────┤
│ Qualified │ 120 │ │ 54 │ 4391545 │
│ Proposal Made │ 64 │ 53% │ 12 │ 980000 │
│ Negotiations Started │ 28 │ 44% │ 2 │ 81909 │
└──────────────────────┴──────────────────────────┴─────────────────┴──────────┴────────────┘

When your account has more than one pipeline, --pipeline <id> is required — without it pdcli lists the pipeline IDs and exits 64. With a single pipeline it's inferred.

When the approximation isn't good enough, --exact mines the real stage transitions from each deal's changelog instead of inferring reach from the final stage. A deal counts as entering a stage only when it was actually observed there: every stage the deal moved into, plus its starting stage. A deal created directly in stage 3 — with no stage transitions — counts as entering stage 3 only, not stages 1 and 2.

Terminal window
pdcli funnel --pipeline 1 --exact

The accuracy costs one request per deal. Above 100 deals, pdcli warns on stderr about the request volume before it starts mining (the rate limiter then paces the calls); the table trades the approximate Reached / Open columns for an observed Entered count, with the won total reported on a single summary line under the table. The per-stage ratio is labelled Entered vs prev rather than a conversion percentage, because exact entries are non-monotonic and the ratio can exceed 100%.

In --exact mode, --period scopes only the closed (won/lost) deals it mines; open deals are always included regardless of the window. If a single deal's changelog can't be fetched, that deal is skipped (not the whole run) and pdcli notes how many were skipped on stderr.

Three commands answer questions Pipedrive's UI simply doesn't: where are deals rotting? (metrics aging), which close dates keep slipping? (metrics slippage), and what's the real transition graph between stages, backward edges and all? (metrics conversion-matrix).

All three reconstruct stage and close-date history by mining each deal's changelog — one request per deal, 20 tokens each. That's the cost of accuracy: the changelog is the only source that records when a deal actually entered a stage or had its close date moved. Above 100 deals, pdcli warns on stderr before it starts (the rate limiter then paces the calls); a deal whose changelog can't be fetched is skipped, counted, and reported on stderr rather than aborting the run. --pipeline <id> is required when the account has more than one pipeline, inferred otherwise.

Days-in-current-stage for every open deal, bucketed, so you can see at a glance how much value is going stale and where. For each stage it also mines the completed dwell distribution (entry → next-entry across all deals) and reports per-stage p50/p90, then flags how many open deals have now sat in the stage longer than its own p90 — the deals most likely to be quietly dying.

Terminal window
pdcli metrics aging --pipeline 1 --buckets 30,60,90
┌──────────────────────┬───────────┬───────────┬───────────┬───────────┬──────────────────┐
│ Stage │ 0-30 │ 30-60 │ 60-90 │ 90+ │ > p90 dwell │
├──────────────────────┼───────────┼───────────┼───────────┼───────────┼──────────────────┤
│ Qualified │ 31 (820000) │ 12 (410000) │ 6 (180000) │ 5 (240000) │ 4 (p90 47d) │
│ Proposal Made │ 7 (390000) │ 3 (120000) │ 1 (60000) │ 1 (80000) │ 2 (p90 22d) │
│ Negotiations Started │ 1 (40000) │ 1 (42000) │ 0 │ 0 │ — │
└──────────────────────┴───────────┴───────────┴───────────┴───────────┴──────────────────┘

Each bucket cell is count (summed value); 0 when empty. Buckets are 0-N1 / N1-N2 / … / last+ with the lower bound inclusive and the upper exclusive, so a deal sitting exactly 30 days lands in 30-60. The > p90 dwell column shows the count past p90 with the threshold itself ( when the stage has no completed-dwell history to learn a p90 from).

One limitation, stated honestly: a deal's starting stage shows an Unknown dwell. The changelog records stage transitions, and a deal is created in its first stage — there is no entry-transition into it — so pdcli can't timestamp when it arrived. Those deals are counted in an Unknown column (shown only when present) rather than bucketed against a guess.

Open deals whose expected_close_date keeps getting pushed out. pdcli walks each deal's close-date changes, counts the forward pushes, and reports the net days slipped (original → current). --min-pushes (default 1) filters to the serial offenders — the deals whose timeline you can no longer trust.

Terminal window
pdcli metrics slippage --pipeline 1 --min-pushes 2
┌──────┬──────────────────┬───────┬────────┬──────────────────┬──────────────────────────┐
│ Deal │ Title │ Owner │ Pushes │ Net days slipped │ Close date │
├──────┼──────────────────┼───────┼────────┼──────────────────┼──────────────────────────┤
│ 204 │ Acme renewal │ 42 │ 4 │ 96 │ 2026-02-15 → 2026-05-22 │
│ 311 │ Globex expansion │ 17 │ 3 │ 61 │ 2026-03-01 → 2026-05-01 │
└──────┴──────────────────┴───────┴────────┴──────────────────┴──────────────────────────┘

A high push count with a large net slip is the classic "always closing next month" deal. Owner is a user ID — resolve it with user find / user list (see the audit guide).

The real stage-transition graph. Unlike funnel, which hides backward moves and collapses re-entries, this counts every stage_id hop as a directed edge and reports its occurrence count — so a deal that bounces 1→2→1→2 contributes two 1→2 edges and one 2→1 edge. Won/Lost are added as terminal columns, attributed to the stage the deal sat in when it closed. The full source×destination grid is too wide to read, so the table renders a long-format edge list with a forward/backward tag, plus a per-source forward-rate summary underneath.

Terminal window
pdcli metrics conversion-matrix --pipeline 1
┌──────────────────────┬──────────────────────┬───────┬───────────┐
│ From │ To │ Edges │ Direction │
├──────────────────────┼──────────────────────┼───────┼───────────┤
│ Qualified │ Proposal Made │ 64 │ forward │
│ Qualified │ Lost │ 22 │ forward │
│ Proposal Made │ Negotiations Started │ 28 │ forward │
│ Proposal Made │ Qualified │ 9 │ backward │
│ Negotiations Started │ Won │ 18 │ forward │
│ Negotiations Started │ Proposal Made │ 4 │ backward │
└──────────────────────┴──────────────────────┴───────┴───────────┘
┌──────────────────────┬───────────┬───────────┐
│ Source stage │ Edges out │ Forward % │
├──────────────────────┼───────────┼───────────┤
│ Qualified │ 86 │ 100% │
│ Proposal Made │ 41 │ 78% │
│ Negotiations Started │ 22 │ 82% │
└──────────────────────┴───────────┴───────────┘

--output json returns the raw object — sources, destinations, the dense matrix, and the edges / backwardEdges lists — for feeding into a graph or a spreadsheet. The backward edges are the ones worth a second look: they're deals your reps walked back down the pipeline, often the precursor to a slip or a loss.

The process-compliance side of this same changelog data — gate-skips and regressions with actor attribution — lives in audit stage-skips.

Are you carrying enough pipeline to hit your number? metrics coverage weighs your open pipeline against the revenue still needed to reach a quota and reports the coverage ratio. The classic 3× rule is defined on raw pipeline value, so that drives the verdict; a probability-weighted coverage figure (same weighting pipeline health uses) is shown alongside as the risk-adjusted view.

The quota comes from your active revenue goal via the Goals API, measured over --period (default 90d). --target <amount> overrides it with a manual number and skips the Goals API entirely — useful when no goal is configured.

Coverage collapses open value into a single ratio, so it can't mix currencies. If the pipeline holds deals in more than one currency the command exits 64; scope it to one with --currency <code> (e.g. --currency USD).

Terminal window
pdcli metrics coverage --pipeline 1
pdcli metrics coverage --target 500000
┌────────────────────┬──────────┐
│ Metric │ Value │
├────────────────────┼──────────┤
│ Open pipeline │ 1480000 │
│ Weighted pipeline │ 1320000 │
│ Quota │ 500000 │
│ Progress │ 180000 │
│ Remaining │ 320000 │
│ Coverage ratio │ 4.6x │
│ Weighted coverage │ 4.1x │
│ Verdict │ healthy │
└────────────────────┴──────────┘

The verdict applies the classic 3× rule of thumb against open pipeline ÷ remaining gap: ≥ 3× is healthy, 2–3× is borderline, below 2× is low (and covered once progress already meets the quota). When there's no active revenue goal and you didn't pass --target, the command exits 64 with guidance to create a goal or supply a target.

A per-stage snapshot of your open deals — what's there, what it's worth, and what's neglected.

Terminal window
pdcli pipeline health --pipeline 1
┌──────────────────────┬──────┬────────────┬──────────┬────────────┬──────────────┬────────────┐
│ Stage │ Open │ Value │ Weighted │ Stale >14d │ No next step │ Past close │
├──────────────────────┼──────┼────────────┼──────────┼────────────┼──────────────┼────────────┤
│ Qualified │ 54 │ 4391545 │ 878309 │ 12 │ 31 │ 4 │
│ Proposal Made │ 12 │ 980000 │ 490000 │ 3 │ 5 │ 1 │
│ Negotiations Started │ 2 │ 81909 │ 65527 │ 1 │ 2 │ 0 │
└──────────────────────┴──────┴────────────┴──────────┴────────────┴──────────────┴────────────┘

Column by column:

ColumnMeaning
OpenOpen deals currently in the stage.
ValueSum of those deals' value.
WeightedValue × probability. Per-deal probability wins; otherwise the stage's deal_probability; otherwise 100%.
Stale >14dOpen deals not updated in more than 14 days.
No next stepOpen deals with no future, not-done activity scheduled.
Past closeOpen deals whose expected_close_date is before today.

Like funnel, --pipeline is required when there's more than one pipeline and inferred otherwise.

These same hygiene signals drive the data-hygiene auditaudit applies them account-wide with severities and a CI gate.

Pipedrive's forecast view is UI-only. metrics forecast reconstructs it on the command line: your open pipeline bucketed by close-month into three views.

  • Commit — full value of deals whose effective win-probability clears --commit-threshold (default 70): the deals you're confident will close.
  • Best case — every open deal at full value: the optimistic ceiling.
  • Weighted — each deal's value × probability (deal probability ?? stage default ?? 100).

Values are segregated per currency — a USD deal and an EUR deal are different units, so they are never summed together (the same rule metrics coverage and deal summary follow). A deal with no expected_close_date lands in a no-date bucket; one with no currency under (none).

Terminal window
pdcli metrics forecast --pipeline 1
pdcli metrics forecast --commit-threshold 80 --output json
┌─────┬─────────┬───────┬──────────┬──────────┐
│ Cur │ Month │ Deals │ Commit │ Best case│ … Weighted
├─────┼─────────┼───────┼──────────┼──────────┤
│ EUR │ 2026-07 │ 2 │ 40000 │ 55000 │ 48200
│ USD │ 2026-07 │ 6 │ 120000 │ 180000 │ 142500
│ USD │ 2026-08 │ 4 │ 90000 │ 140000 │ 101000
│ USD │ no-date │ 1 │ 0 │ 20000 │ 12000
└─────┴─────────┴───────┴──────────┴──────────┘
Totals by currency:

--pipeline is required only when the account has more than one. For quota-vs-pipeline coverage, see metrics coverage; to combine forecast with everything else in one shot, see digest.

Per-rep performance across all pipelines (narrow with --pipeline/--owner). It reuses the velocity equation per owner and adds the hygiene a manager actually chases.

Terminal window
pdcli rep scorecard --period 90d
pdcli rep scorecard --owner 42 --output json
┌───────┬────────┬──────┬───────────────────┬───────────┬────────────┬───────┬────────────┬─────────┬────────────┐
│ Rep │ Active │ Open │ Win rate │ Cycle (d) │ Velocity/d │ Stale │ Past close │ No date │ No contact │
├───────┼────────┼──────┼───────────────────┼───────────┼────────────┼───────┼────────────┼─────────┼────────────┤
│ Alice │ yes │ 14 │ 58% (11W/8L) │ 34 │ 2380 │ 2 │ 1 │ 0 │ 0 │
│ Bob │ no │ 9 │ n/a │ n/a │ n/a │ 4 │ 2 │ 3 │ 1 │
└───────┴────────┴──────┴───────────────────┴───────────┴────────────┴───────┴────────────┴─────────┴────────────┘

Win rate and cycle are decided over the trailing --period (default 90d). Owners with no matching user fall back to #<id>; deals with no owner roll up under Unassigned. Reps are ordered by velocity per day (those with too little data to compute it sort last).

The Monday packet in one command. A single pipeline-scoped fetch is fanned into velocity, pipeline health, coverage, funnel, forecast and the must-fix hygiene checks — no need to run six commands.

Terminal window
pdcli digest --pipeline 1 # structured table in the terminal
pdcli digest --output json # the whole packet for agents
pdcli digest --deep # + changelog-mined aging/slippage/stage-skips
pdcli digest --format md --out monday.md # shareable markdown artifact
pdcli digest --format html --out monday.html

By default digest skips changelog mining and stays cheap. --deep mines each deal's history (one request per deal, the usual >100-deal warning applies) to add aging, close-date slippage and stage-skip sections. The quota comes from your revenue goal over --period, or --target <amount>; if no goal is configured the coverage section is simply omitted (the digest never fails for that reason).

--format md|html renders the packet as a shareable document — pipe it to Slack/email from cron, or write it to a file with --out. These artifact formats are distinct from the global --output table|json|yaml|csv (which stays for scripting the structured packet).

pdcli v0.18.0 · MIT · not affiliated with Pipedrive