Skip to content
pdcli
Get started

Quickstart for AI agents

pdcli is built to be driven by agents (Claude Code, Codex, CI bots). It is self-describing, emits machine-readable JSON, and uses deterministic exit codes. This page covers the conventions an agent needs.

If your host speaks the Model Context Protocol — Claude Desktop, Claude Code, and other GUI or chat clients — pdcli can register as an MCP server and hand it a set of typed tools, read-only by default. Register it with Claude Code in one line:

Terminal window
claude mcp add pipedrive -- pdcli mcp serve

Everything below still applies: the MCP tools re-invoke this same CLI under your auth profile, with the same host-lock and exit-code semantics. If your agent has a shell, driving pdcli from bash (as this page describes) is often simpler than MCP. See the MCP server guide for the full tool model, the write gate, and scoping flags.

Avoid the interactive auth login flow. Set credentials in the environment so no prompt blocks the run and no token lands in command history or a stored profile:

Terminal window
export PDCLI_COMPANY_DOMAIN=acme
export PDCLI_API_TOKEN=<personal-api-token>
pdcli deal list --status open

Env vars take precedence over a stored profile and require no keychain, which makes them the right choice for ephemeral or headless environments. The token must stay in the environment, never on a flag like --api-token where it would be visible in the process list.

In a TTY the default output is a table; when stdout is not a TTY (piped or captured), pdcli defaults to JSON. Be explicit to remove all doubt:

Terminal window
pdcli deal get 42 --output json

Refine without leaving the CLI using --jq (native jq), --fields, or --output yaml|csv. See Output & filtering.

pdcli uses sysexits codes so a script can react to the failure class without parsing text:

| Code | Meaning | | ---- | ------------------------------------------------------------------------------------------------- | | 0 | Success | | 1 | Generic error | | 3 | lookup: no record matched — not a failure; branch to create/upsert | | 8 | watch: new findings since the last run — the trigger for pdcli watch \|\| notify | | 64 | Usage / bad flags or arguments (unknown flag, missing arg, invalid value, missing CSV column) | | 65 | Bad input data (API 400 / 422) | | 69 | Service unavailable (API 5xx, or unreachable) | | 70 | Internal software error (unexpected — a genuine bug, not a usage mistake) | | 75 | Rate limited (API 429) — retry later | | 77 | Not authenticated / forbidden (API 401 / 403) | | 78 | Configuration error (e.g. API 402, missing domain) |

Errors print a JSON object on stderr whenever output is JSON — with --output json, a json profile default, or when stdout is piped (the same TTY→JSON rule as success output), so a non-interactive consumer always gets a parseable failure:

{
"error": "ApiError",
"message": "Pipedrive API 401: invalid token",
"exitCode": 77,
"statusCode": 401,
"path": "/api/v2/deals"
}

Every command and flag is discoverable:

Terminal window
pdcli --help
pdcli deal --help
pdcli deal create --help

The full reference is at /pdcli/reference/commands/. Beyond plain CRUD, the commands below are the ones built specifically for driving pdcli from an agent. They group into five jobs.

  • deal context <id> — one call returns a denormalized, prompt-ready bundle (deal + person + org + activities + notes + products + flags). Add --mail to fold in a mail summary — opt-in, since it needs the mail:read scope and email sync.
  • search — cross-entity search when you have a term but not an ID.
  • lookup — the light read-only existence probe: it finds a record by a field value and exits 0 when found (printing it) or 3 when nothing matched, so pdcli lookup person --field email --value … || pdcli person create … is a clean create-if-missing pattern that never mutates on its own.
  • Metrics and analyticsdeal summary (server-side per-currency value rollup); time-intelligence metrics metrics aging/slippage/conversion-matrix plus audit stage-skips, all mined from per-deal changelogs; metrics forecast for a per-currency commit/best-case/weighted close-month forecast; and rep scorecard for per-owner performance.
  • digest — the whole Monday packet in one fetch (--format md|html --out for a cron → Slack/email artifact).
  • Deeper record detaildeal history <id> (field-change audit trail), deal product (line items), deal participant and deal/person/org follower (the people around a record), and task (project action items).
  • person/org/deal upsert match a record by --by (a built-in key or a searchable custom field) and then create or PATCH only what changed — and refuse with exit 65 when more than one record matches, so an agent never silently writes the wrong one.
  • person import/org import --upsert --match-on <field> apply the same match-or-create per CSV row, reporting created/updated/unchanged counts. Pair upsert with an external key (a custom field carrying your system’s ID) for clean, repeatable sync.
  • --dry-run previews an upsert or import without writing a byte; --yes skips the confirmation prompt so a destructive op never blocks a headless run.
  • Schema and type changesfield create/field update/field option manage the custom-field schema itself; lead convert/deal convert (with --wait) change a record’s type.
  • changes — an incremental cross-entity change feed with a self-advancing watermark, so you ask “what changed since last time” with no receiver to host, unlike webhooks.
  • watch — an exit-code-gated anomaly poller that fires exit 8 only on findings new since the last run: pdcli watch || notify.
  • --updated-since on the list commands for plain incremental polling.
  • sync warehouse — an incremental NDJSON export with per-entity high-water marks.
  • backup diff — a zero-API, field-level diff of two snapshots.

Hand these tools to a GUI or chat host that can’t run a shell — see Expose pdcli over MCP above and the full MCP server guide.

When no dedicated command exists, call any endpoint directly. The request is host-locked to your authenticated company domain (or the OAuth api_domain), so a hallucinated host cannot leak the token. There is no generic data host.

Terminal window
pdcli api GET /api/v2/pipelines
pdcli api POST /api/v2/deals --body '{"title":"Raw deal"}'

api always prints raw JSON. Both v1 and v2 paths work.

The whole documentation site is available as plain text for ingestion:

Find open deals owned by user 42 that are missing a value, and report how many:

Terminal window
export PDCLI_COMPANY_DOMAIN=acme
export PDCLI_API_TOKEN=$PIPEDRIVE_TOKEN
deals=$(pdcli deal list --status open --owner 42 --output json)
status=$?
if [ "$status" -ne 0 ]; then
echo "pdcli failed (exit $status)" >&2 # branch on the sysexits code
exit "$status"
fi
count=$(echo "$deals" | jq '[.[] | select(.value == null)] | length')
echo "Open deals missing a value: $count"
pdcli v0.22.0 · MIT · not affiliated with Pipedrive