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.
Expose pdcli over MCP
Section titled “Expose pdcli over MCP”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:
claude mcp add pipedrive -- pdcli mcp serveEverything 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.
Authenticate with environment variables
Section titled “Authenticate with environment variables”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:
export PDCLI_COMPANY_DOMAIN=acmeexport PDCLI_API_TOKEN=<personal-api-token>pdcli deal list --status openEnv 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.
Output is JSON when piped
Section titled “Output is JSON when piped”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:
pdcli deal get 42 --output jsonRefine without leaving the CLI using --jq (native jq), --fields, or
--output yaml|csv. See Output & filtering.
Branch on exit codes
Section titled “Branch on exit codes”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"}Self-description
Section titled “Self-description”Every command and flag is discoverable:
pdcli --helppdcli deal --helppdcli deal create --helpThe 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.
Read and gather context
Section titled “Read and gather context”deal context <id>— one call returns a denormalized, prompt-ready bundle (deal + person + org + activities + notes + products + flags). Add--mailto fold in a mail summary — opt-in, since it needs themail:readscope 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, sopdcli lookup person --field email --value … || pdcli person create …is a clean create-if-missing pattern that never mutates on its own.- Metrics and analytics —
deal summary(server-side per-currency value rollup); time-intelligence metricsmetrics aging/slippage/conversion-matrixplusaudit stage-skips, all mined from per-deal changelogs;metrics forecastfor a per-currency commit/best-case/weighted close-month forecast; andrep scorecardfor per-owner performance. digest— the whole Monday packet in one fetch (--format md|html --outfor a cron → Slack/email artifact).- Deeper record detail —
deal history <id>(field-change audit trail),deal product(line items),deal participantanddeal/person/org follower(the people around a record), andtask(project action items).
Write safely (idempotent, retry-safe)
Section titled “Write safely (idempotent, retry-safe)”person/org/deal upsertmatch 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-runpreviews an upsert or import without writing a byte;--yesskips the confirmation prompt so a destructive op never blocks a headless run.- Schema and type changes —
field create/field update/field optionmanage the custom-field schema itself;lead convert/deal convert(with--wait) change a record’s type.
React and poll
Section titled “React and poll”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-sinceon the list commands for plain incremental polling.
Sync and export
Section titled “Sync and export”sync warehouse— an incremental NDJSON export with per-entity high-water marks.backup diff— a zero-API, field-level diff of two snapshots.
Expose the whole surface over MCP
Section titled “Expose the whole surface over MCP”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.
The host-locked api escape hatch
Section titled “The host-locked api escape hatch”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.
pdcli api GET /api/v2/pipelinespdcli api POST /api/v2/deals --body '{"title":"Raw deal"}'api always prints raw JSON. Both v1 and v2 paths work.
Machine-readable docs
Section titled “Machine-readable docs”The whole documentation site is available as plain text for ingestion:
- /pdcli/llms.txt — index of pages.
- /pdcli/llms-full.txt — the full docs in one file.
- /pdcli/llms-small.txt — a compact variant.
End-to-end example
Section titled “End-to-end example”Find open deals owned by user 42 that are missing a value, and report how many:
export PDCLI_COMPANY_DOMAIN=acmeexport 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"