Skip to main content

CRM Data Export

📋 Overview

Pipedrive CRM data export enables retrieving deals, contacts (persons), organizations, notes, pipelines, and stages with cursor-based pagination. All endpoints automatically refresh access tokens before making API requests.

Supported Export Types:

  • persons - Contact records
  • organizations - Company records
  • deals - Sales opportunities
  • notes - Notes attached to records
  • pipelines - Sales pipeline definitions
  • stages - Pipeline stages

🔌 Export Endpoint

Endpoint: GET /v1/e/pipedrive/export/:type

Purpose: Export CRM data with automatic pagination support

Headers:

Authorization: Bearer {jwt_token}

Path Parameters:

ParameterTypeRequiredDescription
typestringYesExport type: persons, organizations, deals, notes, pipelines, stages

Query Parameters:

ParameterTypeRequiredDefaultDescription
limitnumberNo100Number of records per page (max varies by type)
startnumberNo0Cursor position for pagination (use next_start from previous response)

Authentication: Requires JWT token with valid account context

How It Works

  1. Token Refresh: getToken middleware retrieves and refreshes access token
  2. API Domain: Middleware provides account-specific api_domain
  3. Validate Type: Controller checks if type is in allowed list
  4. Fetch Data: Call Pipedrive API with dynamic domain
  5. Return Response: Include data with pagination metadata

Example Request:

curl -X GET \
'https://api.dashclicks.com/v1/e/pipedrive/export/deals?limit=100&start=0' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'

📊 Export Types

1. Persons (Contacts)

Type: persons
Pipedrive API: GET /v1/persons
Description: Individual contact records

Request:

GET /v1/e/pipedrive/export/persons?limit=100&start=0

Response Structure:

{
"success": true,
"message": "SUCCESS",
"data": [
{
"id": 12345,
"name": "John Doe",
"first_name": "John",
"last_name": "Doe",
"email": [
{
"value": "john.doe@example.com",
"primary": true,
"label": "work"
}
],
"phone": [
{
"value": "+1234567890",
"primary": true,
"label": "work"
}
],
"org_id": {
"name": "Example Corp",
"value": 67890
},
"owner_id": {
"id": 111,
"name": "Sales Rep",
"email": "sales@dashclicks.com"
},
"add_time": "2024-01-15 10:30:00",
"update_time": "2024-03-20 14:45:00",
"visible_to": "3",
"label": 2,
"cc_email": "person-12345@pipedrivemail.com"
}
],
"pagination": {
"start": 0,
"limit": 100,
"more_items_in_collection": true,
"next_start": 100
}
}

Key Fields:

FieldTypeDescription
idnumberUnique person ID
namestringFull name
first_namestringFirst name
last_namestringLast name
emailarrayEmail addresses with labels
phonearrayPhone numbers with labels
org_idobjectAssociated organization
owner_idobjectPerson owner (sales rep)
add_timestringCreated timestamp
update_timestringLast updated timestamp
visible_tostringVisibility setting (1=owner, 3=entire company)
labelnumberLabel ID
cc_emailstringBCC email for this person

2. Organizations (Companies)

Type: organizations
Pipedrive API: GET /v1/organizations
Description: Company/organization records

Request:

GET /v1/e/pipedrive/export/organizations?limit=100&start=0

Response Structure:

{
"success": true,
"message": "SUCCESS",
"data": [
{
"id": 67890,
"name": "Example Corp",
"address": "123 Business St, City, State 12345",
"address_country": "United States",
"owner_id": {
"id": 111,
"name": "Sales Manager",
"email": "manager@dashclicks.com"
},
"people_count": 15,
"open_deals_count": 3,
"closed_deals_count": 12,
"won_deals_count": 10,
"lost_deals_count": 2,
"add_time": "2023-06-10 09:00:00",
"update_time": "2024-03-20 11:30:00",
"visible_to": "3",
"label": 1,
"cc_email": "org-67890@pipedrivemail.com"
}
],
"pagination": {
"start": 0,
"limit": 100,
"more_items_in_collection": true,
"next_start": 100
}
}

Key Fields:

FieldTypeDescription
idnumberUnique organization ID
namestringCompany name
addressstringFull address
address_countrystringCountry name
owner_idobjectOrganization owner
people_countnumberNumber of associated contacts
open_deals_countnumberNumber of open deals
closed_deals_countnumberNumber of closed deals
won_deals_countnumberNumber of won deals
lost_deals_countnumberNumber of lost deals
add_timestringCreated timestamp
update_timestringLast updated timestamp
visible_tostringVisibility setting
labelnumberLabel ID
cc_emailstringBCC email for this organization

3. Deals (Opportunities)

Type: deals
Pipedrive API: GET /v1/deals
Description: Sales opportunities

Request:

GET /v1/e/pipedrive/export/deals?limit=100&start=0

Response Structure:

{
"success": true,
"message": "SUCCESS",
"data": [
{
"id": 54321,
"title": "Enterprise Package - Q1 2024",
"value": 50000,
"currency": "USD",
"status": "open",
"stage_id": 5,
"stage_order_nr": 3,
"pipeline_id": 1,
"person_id": {
"name": "John Doe",
"value": 12345
},
"org_id": {
"name": "Example Corp",
"value": 67890
},
"owner_id": {
"id": 111,
"name": "Sales Rep",
"email": "sales@dashclicks.com"
},
"expected_close_date": "2024-03-31",
"add_time": "2024-01-10 14:00:00",
"update_time": "2024-03-15 16:20:00",
"close_time": null,
"won_time": null,
"lost_time": null,
"visible_to": "3",
"probability": 75,
"lost_reason": null
}
],
"pagination": {
"start": 0,
"limit": 100,
"more_items_in_collection": true,
"next_start": 100
}
}

Key Fields:

FieldTypeDescription
idnumberUnique deal ID
titlestringDeal name/title
valuenumberDeal monetary value
currencystringCurrency code (USD, EUR, etc.)
statusstringDeal status: open, won, lost, deleted
stage_idnumberCurrent pipeline stage ID
stage_order_nrnumberStage order in pipeline
pipeline_idnumberAssociated pipeline ID
person_idobjectAssociated contact
org_idobjectAssociated organization
owner_idobjectDeal owner
expected_close_datestringExpected close date (YYYY-MM-DD)
add_timestringCreated timestamp
update_timestringLast updated timestamp
close_timestringWhen deal was closed (won/lost)
won_timestringWhen deal was won
lost_timestringWhen deal was lost
visible_tostringVisibility setting
probabilitynumberWin probability percentage
lost_reasonstringReason for loss (if lost)

4. Notes

Type: notes
Pipedrive API: GET /v1/notes
Description: Notes attached to deals, persons, or organizations

Request:

GET /v1/e/pipedrive/export/notes?limit=100&start=0

Response Structure:

{
"success": true,
"message": "SUCCESS",
"data": [
{
"id": 98765,
"content": "Initial discovery call completed. Customer interested in enterprise plan.",
"add_time": "2024-03-18 10:15:00",
"update_time": "2024-03-18 10:15:00",
"user": {
"id": 111,
"name": "Sales Rep",
"email": "sales@dashclicks.com"
},
"deal": {
"title": "Enterprise Package - Q1 2024",
"id": 54321
},
"person": {
"name": "John Doe",
"id": 12345
},
"organization": {
"name": "Example Corp",
"id": 67890
},
"pinned_to_deal_flag": false,
"pinned_to_person_flag": false,
"pinned_to_organization_flag": false
}
],
"pagination": {
"start": 0,
"limit": 100,
"more_items_in_collection": true,
"next_start": 100
}
}

Key Fields:

FieldTypeDescription
idnumberUnique note ID
contentstringNote text content
add_timestringCreated timestamp
update_timestringLast updated timestamp
userobjectNote author
dealobjectAssociated deal (if any)
personobjectAssociated person (if any)
organizationobjectAssociated organization (if any)
pinned_to_deal_flagbooleanPinned to deal
pinned_to_person_flagbooleanPinned to person
pinned_to_organization_flagbooleanPinned to organization

5. Pipelines

Type: pipelines
Pipedrive API: GET /v1/pipelines
Description: Sales pipeline definitions

Request:

GET /v1/e/pipedrive/export/pipelines

Response Structure:

{
"success": true,
"message": "SUCCESS",
"data": [
{
"id": 1,
"name": "Sales Pipeline",
"url_title": "sales-pipeline",
"order_nr": 1,
"active": true,
"deal_probability": true,
"add_time": "2023-01-10 08:00:00",
"update_time": "2024-02-15 12:00:00",
"selected": true
},
{
"id": 2,
"name": "Partner Pipeline",
"url_title": "partner-pipeline",
"order_nr": 2,
"active": true,
"deal_probability": false,
"add_time": "2023-03-20 09:30:00",
"update_time": "2023-12-01 14:00:00",
"selected": false
}
],
"pagination": {
"start": 0,
"limit": 100,
"more_items_in_collection": false,
"next_start": null
}
}

Key Fields:

FieldTypeDescription
idnumberUnique pipeline ID
namestringPipeline name
url_titlestringURL-friendly pipeline name
order_nrnumberDisplay order
activebooleanWhether pipeline is active
deal_probabilitybooleanWhether deals use probability
add_timestringCreated timestamp
update_timestringLast updated timestamp
selectedbooleanCurrently selected pipeline

6. Stages

Type: stages
Pipedrive API: GET /v1/stages
Description: Pipeline stages (steps in sales process)

Request:

GET /v1/e/pipedrive/export/stages

Response Structure:

{
"success": true,
"message": "SUCCESS",
"data": [
{
"id": 1,
"order_nr": 1,
"name": "Lead In",
"active_flag": true,
"deal_probability": 10,
"pipeline_id": 1,
"rotten_flag": false,
"rotten_days": null,
"add_time": "2023-01-10 08:00:00",
"update_time": "2023-06-15 10:00:00",
"pipeline_name": "Sales Pipeline"
},
{
"id": 2,
"order_nr": 2,
"name": "Qualification",
"active_flag": true,
"deal_probability": 25,
"pipeline_id": 1,
"rotten_flag": true,
"rotten_days": 14,
"add_time": "2023-01-10 08:00:00",
"update_time": "2023-08-20 11:30:00",
"pipeline_name": "Sales Pipeline"
}
],
"pagination": {
"start": 0,
"limit": 100,
"more_items_in_collection": false,
"next_start": null
}
}

Key Fields:

FieldTypeDescription
idnumberUnique stage ID
order_nrnumberStage order in pipeline
namestringStage name
active_flagbooleanWhether stage is active
deal_probabilitynumberDefault win probability for this stage
pipeline_idnumberParent pipeline ID
rotten_flagbooleanWhether deals can become "rotten"
rotten_daysnumberDays until deal becomes rotten (if flag enabled)
add_timestringCreated timestamp
update_timestringLast updated timestamp
pipeline_namestringParent pipeline name

🔄 Pagination

Pipedrive uses cursor-based pagination with start and next_start parameters.

How Pagination Works

  1. First Request: Omit start parameter (defaults to 0)
  2. Check more_items_in_collection: If true, more pages exist
  3. Use next_start: For next request, use start={next_start} value
  4. Repeat: Continue until more_items_in_collection is false

Pagination Response Structure

{
"pagination": {
"start": 0, // Current cursor position
"limit": 100, // Records per page
"more_items_in_collection": true, // More pages available?
"next_start": 100 // Use this value for next request
}
}

Example: Paginate Through All Deals

async function getAllDeals(jwtToken) {
let allDeals = [];
let start = 0;
let hasMoreItems = true;

while (hasMoreItems) {
// Build URL with start parameter
const url = `/v1/e/pipedrive/export/deals?limit=100${start ? `&start=${start}` : ''}`;

const response = await axios.get(url, {
headers: { Authorization: `Bearer ${jwtToken}` },
});

// Append data
allDeals = [...allDeals, ...response.data.data];

// Check for more pages
const pagination = response.data.pagination;
if (pagination.more_items_in_collection) {
start = pagination.next_start;
} else {
hasMoreItems = false;
}
}

return allDeals;
}

Best Practices

  1. Use appropriate limit: 100-200 is recommended (check Pipedrive limits)
  2. Check more_items_in_collection: Don't assume next_start !== null
  3. Handle rate limits: Pipedrive may throttle requests
  4. Store cursor position: For resumable imports, save next_start value
  5. Avoid large limits: Smaller pages = faster response times

🎯 Use Cases

1. Export All CRM Data

// Export all supported data types
async function exportAllPipedriveData(jwtToken) {
const types = ['persons', 'organizations', 'deals', 'notes', 'pipelines', 'stages'];
const exportedData = {};

for (const type of types) {
exportedData[type] = [];
let start = 0;
let hasMoreItems = true;

while (hasMoreItems) {
const response = await axios.get(
`/v1/e/pipedrive/export/${type}?limit=200${start ? `&start=${start}` : ''}`,
{ headers: { Authorization: `Bearer ${jwtToken}` } },
);

exportedData[type] = [...exportedData[type], ...response.data.data];

if (response.data.pagination.more_items_in_collection) {
start = response.data.pagination.next_start;
} else {
hasMoreItems = false;
}
}

console.log(`Exported ${exportedData[type].length} ${type}`);
}

return exportedData;
}

2. Sync Deals to Database

// Sync Pipedrive deals to local database
async function syncDealsToDatabase(accountId, jwtToken) {
let start = 0;
let hasMoreItems = true;
let totalSynced = 0;

while (hasMoreItems) {
const response = await axios.get(
`/v1/e/pipedrive/export/deals?limit=100${start ? `&start=${start}` : ''}`,
{ headers: { Authorization: `Bearer ${jwtToken}` } },
);

const deals = response.data.data;

// Upsert deals to database
for (const deal of deals) {
await DealModel.findOneAndUpdate(
{
accountId: accountId,
pipedriveId: deal.id,
},
{
accountId: accountId,
pipedriveId: deal.id,
title: deal.title,
value: deal.value,
currency: deal.currency,
status: deal.status,
stageId: deal.stage_id,
pipelineId: deal.pipeline_id,
personId: deal.person_id?.value,
orgId: deal.org_id?.value,
expectedCloseDate: deal.expected_close_date,
lastSynced: new Date(),
rawData: deal,
},
{ upsert: true, new: true },
);
}

totalSynced += deals.length;

if (response.data.pagination.more_items_in_collection) {
start = response.data.pagination.next_start;
} else {
hasMoreItems = false;
}
}

console.log(`Synced ${totalSynced} deals`);
return totalSynced;
}

3. Build Pipeline Visualization

// Get pipeline structure with stages and deal counts
async function getPipelineVisualization(jwtToken) {
// 1. Get all pipelines
const pipelinesResponse = await axios.get('/v1/e/pipedrive/export/pipelines', {
headers: { Authorization: `Bearer ${jwtToken}` },
});

const pipelines = pipelinesResponse.data.data;

// 2. Get all stages
const stagesResponse = await axios.get('/v1/e/pipedrive/export/stages', {
headers: { Authorization: `Bearer ${jwtToken}` },
});

const stages = stagesResponse.data.data;

// 3. Get all deals
const deals = await getAllDeals(jwtToken);

// 4. Build visualization data
const visualization = pipelines.map(pipeline => ({
id: pipeline.id,
name: pipeline.name,
stages: stages
.filter(stage => stage.pipeline_id === pipeline.id)
.sort((a, b) => a.order_nr - b.order_nr)
.map(stage => ({
id: stage.id,
name: stage.name,
order: stage.order_nr,
probability: stage.deal_probability,
dealCount: deals.filter(deal => deal.stage_id === stage.id).length,
totalValue: deals
.filter(deal => deal.stage_id === stage.id)
.reduce((sum, deal) => sum + deal.value, 0),
})),
}));

return visualization;
}

// Helper function from previous example
async function getAllDeals(jwtToken) {
let allDeals = [];
let start = 0;
let hasMoreItems = true;

while (hasMoreItems) {
const response = await axios.get(
`/v1/e/pipedrive/export/deals?limit=100${start ? `&start=${start}` : ''}`,
{ headers: { Authorization: `Bearer ${jwtToken}` } },
);

allDeals = [...allDeals, ...response.data.data];

if (response.data.pagination.more_items_in_collection) {
start = response.data.pagination.next_start;
} else {
hasMoreItems = false;
}
}

return allDeals;
}

4. Export Contacts with Organizations

// Export persons with their associated organization details
async function exportContactsWithOrganizations(jwtToken) {
// 1. Get all organizations
let organizations = [];
let start = 0;
let hasMoreItems = true;

while (hasMoreItems) {
const response = await axios.get(
`/v1/e/pipedrive/export/organizations?limit=100${start ? `&start=${start}` : ''}`,
{ headers: { Authorization: `Bearer ${jwtToken}` } },
);

organizations = [...organizations, ...response.data.data];

if (response.data.pagination.more_items_in_collection) {
start = response.data.pagination.next_start;
} else {
hasMoreItems = false;
}
}

// Create organization lookup map
const orgMap = new Map();
organizations.forEach(org => orgMap.set(org.id, org));

// 2. Get all persons
let persons = [];
start = 0;
hasMoreItems = true;

while (hasMoreItems) {
const response = await axios.get(
`/v1/e/pipedrive/export/persons?limit=100${start ? `&start=${start}` : ''}`,
{ headers: { Authorization: `Bearer ${jwtToken}` } },
);

persons = [...persons, ...response.data.data];

if (response.data.pagination.more_items_in_collection) {
start = response.data.pagination.next_start;
} else {
hasMoreItems = false;
}
}

// 3. Enrich persons with full organization data
const enrichedContacts = persons.map(person => ({
id: person.id,
name: person.name,
email: person.email?.[0]?.value,
phone: person.phone?.[0]?.value,
organization: person.org_id?.value ? orgMap.get(person.org_id.value) : null,
}));

return enrichedContacts;
}

5. Generate Sales Report

// Generate sales report with pipeline metrics
async function generateSalesReport(jwtToken, startDate, endDate) {
// Get all deals
let deals = [];
let start = 0;
let hasMoreItems = true;

while (hasMoreItems) {
const response = await axios.get(
`/v1/e/pipedrive/export/deals?limit=200${start ? `&start=${start}` : ''}`,
{ headers: { Authorization: `Bearer ${jwtToken}` } },
);

deals = [...deals, ...response.data.data];

if (response.data.pagination.more_items_in_collection) {
start = response.data.pagination.next_start;
} else {
hasMoreItems = false;
}
}

// Filter deals by date range
const filteredDeals = deals.filter(deal => {
const wonTime = deal.won_time ? new Date(deal.won_time) : null;
return wonTime && wonTime >= startDate && wonTime <= endDate;
});

// Calculate metrics
const report = {
totalDeals: filteredDeals.length,
totalValue: filteredDeals.reduce((sum, deal) => sum + deal.value, 0),
averageDealValue:
filteredDeals.length > 0
? filteredDeals.reduce((sum, deal) => sum + deal.value, 0) / filteredDeals.length
: 0,
wonDeals: filteredDeals.filter(d => d.status === 'won').length,
lostDeals: filteredDeals.filter(d => d.status === 'lost').length,
openDeals: filteredDeals.filter(d => d.status === 'open').length,
byPipeline: {},
};

// Group by pipeline
filteredDeals.forEach(deal => {
const pipelineId = deal.pipeline_id;
if (!report.byPipeline[pipelineId]) {
report.byPipeline[pipelineId] = {
count: 0,
totalValue: 0,
wonCount: 0,
lostCount: 0,
};
}

report.byPipeline[pipelineId].count++;
report.byPipeline[pipelineId].totalValue += deal.value;
if (deal.status === 'won') report.byPipeline[pipelineId].wonCount++;
if (deal.status === 'lost') report.byPipeline[pipelineId].lostCount++;
});

return report;
}

⚠️ Error Handling

1. Invalid Export Type

Request:

GET /v1/e/pipedrive/export/invalid_type

Response: 400 Bad Request

{
"success": false,
"message": "Invalid type. Allowed types: persons, organizations, deals, notes, pipelines, stages",
"error": "INVALID_TYPE"
}

2. Token Not Found

Scenario: User hasn't connected Pipedrive

Response: 401 Unauthorized

{
"success": false,
"message": "User oauth token not found. Please redirect to login",
"error": "TOKEN_NOT_FOUND"
}

3. Token Invalidated

Scenario: User revoked Pipedrive access

Response: 401 Unauthorized

{
"success": false,
"message": "TOKEN_INVALIDATED",
"error": "TOKEN_INVALIDATED"
}

4. Pipedrive API Error

Scenario: Pipedrive API returns error

Response: 502 Bad Gateway

{
"success": false,
"message": "Pipedrive API error: {error_details}",
"error": "EXTERNAL_API_ERROR"
}

5. Rate Limit Exceeded

Scenario: Too many requests to Pipedrive

Response: 429 Too Many Requests

{
"success": false,
"message": "Rate limit exceeded. Please try again later.",
"error": "RATE_LIMIT_EXCEEDED"
}

📊 Performance Optimization

1. Batch Processing

Process records in batches to avoid memory issues:

async function processBatches(jwtToken, type, batchSize = 100) {
let start = 0;
let hasMoreItems = true;

while (hasMoreItems) {
const response = await axios.get(
`/v1/e/pipedrive/export/${type}?limit=${batchSize}${start ? `&start=${start}` : ''}`,
{ headers: { Authorization: `Bearer ${jwtToken}` } },
);

// Process batch immediately
await processBatch(response.data.data);

if (response.data.pagination.more_items_in_collection) {
start = response.data.pagination.next_start;
} else {
hasMoreItems = false;
}
}
}

2. Parallel Type Fetching

Fetch multiple types in parallel:

async function fetchMultipleTypes(jwtToken, types) {
const promises = types.map(type => getAllDataForType(jwtToken, type));
const results = await Promise.all(promises);

return types.reduce((acc, type, index) => {
acc[type] = results[index];
return acc;
}, {});
}

3. Incremental Sync

Use update_time to fetch only changed records:

// Store last sync time
const lastSyncTime = '2024-03-15 10:00:00';

// Filter deals updated after last sync
const recentDeals = allDeals.filter(deal => new Date(deal.update_time) > new Date(lastSyncTime));

💬

Documentation Assistant

Ask me anything about the docs

Hi! I'm your documentation assistant. Ask me anything about the docs!

I can help you with:
- Code examples
- Configuration details
- Troubleshooting
- Best practices

Try asking: How do I configure the API?
09:30 AM