Skip to content
pdcli
Get started

Data-hygiene audit

pdcli audit runs 11 data-hygiene checks across your deals, persons, organizations, and activities, and prints a findings count per check. Run a subset with --checks, see the flagged items with --verbose, and fail CI on serious findings with --strict.

Terminal window
pdcli audit
┌─────┬──────────────────────────────────────────────────────┬──────────┐
│ Sev │ Check │ Findings │
├─────┼──────────────────────────────────────────────────────┼──────────┤
│ ● │ Open deals untouched for >14 days │ 18 │
│ ● │ Open deals with no future activity scheduled │ 31 │
│ ● │ Open deals past their expected close date │ 4 │
│ ● │ Open deals missing owner, person/org, value, or currency │ 7 │
│ ● │ Open deals far older than the typical won cycle │ 2 │
│ ○ │ Closed deals missing their close timestamp │ 0 │
│ ● │ Persons sharing the same email │ 3 │
│ ● │ Persons with neither email nor phone │ 12 │
│ ○ │ Organizations with the same normalized name │ 5 │
│ ○ │ Overdue open activities piling up per owner │ 44 │
│ ○ │ Deals with a value but no currency │ 0 │
└─────┴──────────────────────────────────────────────────────┴──────────┘

is a must-severity check, is a should. Only must checks gate --strict.

NameSevDetection rule
stale-dealsmustOpen deal where now − update_time > 14 days.
no-next-activitymustOpen deal with no not-done activity due today or later linked to it.
past-close-datemustOpen deal whose expected_close_date is before today.
missing-fieldsmustOpen deal missing any of: owner; both person and org; a value > 0; or currency when it has a value.
ancient-dealsmustOpen deal whose age (now − add_time) exceeds 2× the avg won cycle, or 168 days when no won deals exist.
missing-close-timeshouldA won deal with no won_time, or a lost deal with no lost_time.
duplicate-personsmustTwo or more persons sharing the same email (trimmed, lowercased).
uncontactable-personsmustPerson with neither a non-empty email nor phone.
duplicate-orgsshouldTwo or more orgs with the same normalized name (lowercased, common suffixes like Inc/Ltd/LLC/GmbH stripped, non-alphanumerics removed), plus fuzzy near-matches that normalize differently but score Jaro-Winkler ≥ 0.92.
overdue-activitiesshouldOpen (not-done) activities past due, counted per owner.
currency-missingshouldOpen deal with a value > 0 but no currency.

The thresholds are constants in the audit: stale = 14 days, ancient = 2× the avg won cycle (won_time − add_time across won deals) with a 168-day fallback when there are no won deals to learn from.

The Findings count is the number of flagged items per check — except overdue-activities, where it's the total overdue activities summed across owners.

duplicate-orgs reports two shapes of finding. Exact groups are tagged kind: exact with the shared normalized name and the ids. Fuzzy near-matches — pairs whose names normalize differently but still score Jaro-Winkler ≥ 0.92 — are tagged kind: fuzzy, carry both names and ids, and include the score:

{"kind":"exact","name":"acme","ids":[12,48]}
{"kind":"fuzzy","names":["acmecorp","acmecrop"],"ids":[12,93],"score":0.9667}

The duplicate checks pair naturally with the merge commands — close a duplicate finding by folding the loser into the survivor:

Terminal window
pdcli person merge 123 --into 456
pdcli org merge 12 --into 48

The positional <id> is the losing record and Pipedrive deletes it; --into is the survivor whose data wins on conflict. Both commands confirm before deleting unless you pass --yes. So a duplicate-persons or duplicate-orgs finding becomes a one-liner: merge the duplicate id into the one you want to keep.

When the right fix is to change a record's type — a misfiled deal that's really an early lead, or a duplicate that should fold back into the lead inbox — the convert commands move it across without re-keying. lead convert <id> turns a lead into a deal (and deal convert <id> goes the other way); both run as async jobs, so add --wait to block until the conversion finishes before you act on the result. On success the source record is deleted, so deal convert confirms first unless you pass --yes:

Terminal window
pdcli lead convert adf21080-0e10-11eb-879b-05d71fb426ec --wait # promote a lead to a deal
pdcli deal convert 204 --wait --yes # demote a misfiled deal to a lead

When a deal finding needs investigating before you touch it — why is this deal stale, or who changed its stage — deal history <id> prints its field-change audit trail (who changed what, when), newest-first. Narrow to one field with --field:

Terminal window
pdcli deal history 204 # full change log for a flagged deal
pdcli deal history 204 --field stage_id # just the stage moves

Findings carry owner user IDs, not names. Resolve them with user list (the whole directory) or user find <term> (by name, or --by-email for an address):

Terminal window
pdcli user list --output json
pdcli user find "jane" --output json
Terminal window
pdcli audit --checks stale-deals,duplicate-persons --verbose

Pass a comma-separated list of check names (the Name column above). An unknown name fails fast with exit 64 and lists the valid names.

In table output, --verbose prints the flagged items under each check that has findings, as one JSON object per line, truncated at 25 items with a "… N more" footer:

Open deals untouched for >14 days
{"id":204,"title":"Acme renewal","days":31}
{"id":311,"title":"Globex expansion","days":19}
{"id":340,"title":"Initech seats","days":17}
Persons sharing the same email
{"email":"jane@acme.com","ids":[88,142]}

A check with more than 25 findings prints the first 25 followed by a … N more footer (N = total − 25).

For full machine-readable output (no truncation), use --output json — every check includes its full items array.

--strict exits 1 if any must-severity check has findings, naming them; otherwise exit 0. should findings never fail the gate.

Terminal window
pdcli audit --strict
Error: 7 must-severity checks found issues: stale-deals, no-next-activity, past-close-date, missing-fields, ancient-deals, duplicate-persons, uncontactable-persons

Run a scoped, strict audit on a schedule and let the exit code decide whether to alert. With env-var auth it's fully non-interactive:

Terminal window
PDCLI_COMPANY_DOMAIN=acme PDCLI_API_TOKEN=$PD_TOKEN \
pdcli audit --checks stale-deals,no-next-activity,duplicate-persons --strict --output json \
> hygiene-$(date +%F).json \
|| echo "Hygiene issues found — see report"

See CI recipes and Exit codes for wiring this into a pipeline.

Where the 11 checks above grade your data, audit stage-skips grades your process: did deals move through the pipeline the way they're supposed to? It mines each deal's changelog (open, won, and lost — history matters in every state) and walks the stage_id transitions chronologically, flagging two kinds of out-of-band move with who made them:

  • Forward skips — a deal that jumped a gate (destination order > source order + 1), listing the stages it leapt over. A clean single-stage advance is normal and never flagged.
  • Backward moves — a deal pulled back to an earlier stage (often sandbagging: dragging a deal down to look conservative, or to reset its age).
Terminal window
pdcli audit stage-skips --pipeline 1
┌──────┬──────────┬──────────────────────────┬──────────────────────┬──────────────────────┬───────┐
│ Deal │ Kind │ From │ To │ Skipped gates │ Actor │
├──────┼──────────┼──────────────────────────┼──────────────────────┼──────────────────────┼───────┤
│ 204 │ skip │ Qualified (1) │ Negotiations Started (3) │ Proposal Made │ 42 │
│ 311 │ backward │ Negotiations Started (3) │ Qualified (1) │ │ 17 │
└──────┴──────────┴──────────────────────────┴──────────────────────┴──────────────────────┴───────┘

From/To carry the stage's order number in parentheses; Skipped gates names the stages a forward skip leapt over (empty for a backward move). Actor is the user ID who made the change — resolve it with user find / user list, exactly as for the deal findings above.

This is informational and always exits 0 — it surfaces moves for review rather than gating CI. Cross-pipeline hops and moves touching a since-deleted stage are ignored (stage order is only comparable within one pipeline). Like the time-intelligence metrics, it costs one changelog request per deal (20 tokens each) and warns on stderr above 100 deals. The conversion-side view of the same data — every transition counted, with backward edges and Won/Lost terminals — is metrics conversion-matrix.

pdcli v0.18.0 · MIT · not affiliated with Pipedrive