Skip to main content
Business & GrowthCrm Services186 lines

Pipedrive

Integrate with Pipedrive CRM API for managing deals, persons, organizations,

Quick Summary26 lines
You are an expert in Pipedrive CRM API integration. You build reliable sales pipeline automations that handle deal flow, contact management, activity tracking, and real-time webhook processing using Pipedrive's REST API v1.

## Key Points

- **Ignoring `success` field in responses**: Pipedrive returns HTTP 200 even on logical errors. Always check `data.success === true` before processing results.
- **Not handling null data arrays**: When no results match, `data.data` is `null`, not an empty array. Always default to `data.data || []`.
- **Polling for changes instead of webhooks**: Pipedrive rate limits are 80 requests/2 seconds. Polling wastes quota; use webhooks for real-time updates.
- **Creating duplicate persons**: Pipedrive does not deduplicate by email automatically. Always search by email before creating a new person record.
- Automating sales pipeline progression based on external signals or events
- Syncing contacts and deals between Pipedrive and marketing automation tools
- Building activity dashboards that track rep performance across deal stages
- Processing real-time deal updates via webhooks for downstream notifications
- Migrating sales data from another CRM into Pipedrive programmatically

## Quick Example

```bash
npm install axios
```

```
PIPEDRIVE_API_TOKEN=your_api_token_here
PIPEDRIVE_COMPANY_DOMAIN=yourcompany
```
skilldb get crm-services-skills/PipedriveFull skill: 186 lines
Paste into your CLAUDE.md or agent config

Pipedrive CRM Integration

You are an expert in Pipedrive CRM API integration. You build reliable sales pipeline automations that handle deal flow, contact management, activity tracking, and real-time webhook processing using Pipedrive's REST API v1.

Core Philosophy

Deal-Centric Data Model

Pipedrive organizes everything around deals. Persons and organizations exist to support deals. Always ensure your data model links contacts to deals via participant associations rather than treating them as standalone entities.

Custom Field Awareness

Pipedrive uses hashed keys for custom fields (e.g., abc123_custom_field). Always resolve field keys by calling the entity fields endpoint before reading or writing custom data. Cache the key-to-label mapping.

Webhook-Driven Reactivity

Prefer webhooks over polling for real-time updates. Pipedrive webhooks fire on create, update, and delete for all major objects, enabling event-driven architectures without API quota waste.

Setup

npm install axios
PIPEDRIVE_API_TOKEN=your_api_token_here
PIPEDRIVE_COMPANY_DOMAIN=yourcompany

Initialize the client:

import axios, { AxiosInstance } from "axios";

const pipedrive: AxiosInstance = axios.create({
  baseURL: `https://${process.env.PIPEDRIVE_COMPANY_DOMAIN}.pipedrive.com/api/v1`,
  params: { api_token: process.env.PIPEDRIVE_API_TOKEN },
});

Key Patterns

Do: Use field keys API to resolve custom field names

async function getCustomFieldKeys(entityType: "deal" | "person" | "organization") {
  const { data } = await pipedrive.get(`/${entityType}Fields`);
  return new Map<string, string>(
    data.data.map((f: { key: string; name: string }) => [f.name, f.key])
  );
}

Not: Hardcoding custom field hash keys

// WRONG - keys differ between Pipedrive accounts
await pipedrive.put(`/deals/${id}`, { abc123_custom_field: "value" });

Do: Use pagination with start and limit params

async function getAllDeals() {
  const allDeals = [];
  let start = 0;
  let hasMore = true;
  while (hasMore) {
    const { data } = await pipedrive.get("/deals", {
      params: { start, limit: 100, status: "open" },
    });
    allDeals.push(...(data.data || []));
    hasMore = data.additional_data?.pagination?.more_items_in_collection ?? false;
    start += 100;
  }
  return allDeals;
}

Common Patterns

Create a Deal with Person and Organization

async function createFullDeal(
  title: string,
  personName: string,
  orgName: string,
  pipelineId: number,
  stageId: number,
  value: number,
  currency: string
) {
  const { data: org } = await pipedrive.post("/organizations", { name: orgName });
  const { data: person } = await pipedrive.post("/persons", {
    name: personName,
    org_id: org.data.id,
  });
  const { data: deal } = await pipedrive.post("/deals", {
    title,
    person_id: person.data.id,
    org_id: org.data.id,
    pipeline_id: pipelineId,
    stage_id: stageId,
    value,
    currency,
  });
  return deal.data;
}

Schedule an Activity on a Deal

async function scheduleActivity(
  dealId: number,
  subject: string,
  type: string,
  dueDate: string,
  duration: string,
  assignedUserId: number
) {
  const { data } = await pipedrive.post("/activities", {
    deal_id: dealId,
    subject,
    type,
    due_date: dueDate,
    duration,
    user_id: assignedUserId,
  });
  return data.data;
}

Register a Webhook

async function registerWebhook(
  subscriptionUrl: string,
  eventAction: "added" | "updated" | "deleted",
  eventObject: "deal" | "person" | "organization" | "activity"
) {
  const { data } = await pipedrive.post("/webhooks", {
    subscription_url: subscriptionUrl,
    event_action: eventAction,
    event_object: eventObject,
  });
  return data.data;
}

Search Across All Entities

async function globalSearch(term: string, entityType?: string) {
  const params: Record<string, unknown> = { term, limit: 50 };
  if (entityType) params.item_types = entityType;
  const { data } = await pipedrive.get("/itemSearch", { params });
  return data.data?.items || [];
}

Move Deal Through Pipeline Stages

async function advanceDeal(dealId: number, targetStageId: number) {
  const { data } = await pipedrive.put(`/deals/${dealId}`, {
    stage_id: targetStageId,
  });
  if (!data.success) throw new Error(`Failed to advance deal ${dealId}`);
  return data.data;
}

Anti-Patterns

  • Ignoring success field in responses: Pipedrive returns HTTP 200 even on logical errors. Always check data.success === true before processing results.
  • Not handling null data arrays: When no results match, data.data is null, not an empty array. Always default to data.data || [].
  • Polling for changes instead of webhooks: Pipedrive rate limits are 80 requests/2 seconds. Polling wastes quota; use webhooks for real-time updates.
  • Creating duplicate persons: Pipedrive does not deduplicate by email automatically. Always search by email before creating a new person record.

When to Use

  • Automating sales pipeline progression based on external signals or events
  • Syncing contacts and deals between Pipedrive and marketing automation tools
  • Building activity dashboards that track rep performance across deal stages
  • Processing real-time deal updates via webhooks for downstream notifications
  • Migrating sales data from another CRM into Pipedrive programmatically

Install this skill directly: skilldb add crm-services-skills

Get CLI access →