# Pipedrive

- Tool: Bubble
- Difficulty: Beginner
- Time required: 1–2 hours
- Last updated: July 2026

## TL;DR

Connect Bubble to Pipedrive using a single API token appended as a URL parameter to every request — no OAuth, no refresh tokens, no regional complexity. It is the easiest CRM integration in Bubble. The main gotcha: five Repeating Groups each calling separate deal APIs on page load can hit Pipedrive's 80 requests per 2-second rate limit. Consolidate into one getDeals call and filter results in Bubble using Custom States.

## The simplest CRM integration in Bubble — and the one rate limit trap to avoid

Compared to Salesforce's Connected App setup, Zoho's OAuth exchange, or Dynamics 365's Azure AD admin consent requirements, Pipedrive's integration in Bubble is refreshingly simple: copy your API token from Pipedrive settings, paste it into the API Connector as a URL parameter, and you are authenticated. There is no OAuth flow, no token refresh logic, no regional domain matching, and no backend proxy required.

The integration works on Bubble's Free plan — you do not need Backend Workflows just to authenticate with Pipedrive. This makes Pipedrive the fastest path from 'I want a custom CRM view' to a working Bubble app.

There is one rate limit trap that catches Bubble developers building kanban-style pipeline views: Pipedrive allows 80 requests per 2-second window. A common pattern for building a kanban board is to place one Repeating Group per pipeline stage, each with its own data source calling `GET /v1/deals?stage_id=X`. If there are 5 stages and each Repeating Group fires on page load simultaneously, that is 5 API calls in less than a second — repeated for every user loading the page. With 20 concurrent users, you exceed 80 requests in 2 seconds immediately.

The solution is straightforward: make one `GET /v1/deals` call that retrieves all deals for the selected pipeline, store the results in a Custom State list, and filter that Custom State locally in Bubble for each stage. The API call count drops from 5 per page load to 1, and filtering in Bubble costs no additional API requests.

## Before you start

- A Pipedrive account (all plans include full API access)
- The API Connector plugin installed in your Bubble app (Plugins tab → Add plugins → search 'API Connector' by Bubble)
- Your Bubble app can be on any plan including Free — Backend Workflows are not required for Pipedrive authentication

## Step-by-step guide

### 1. Get your Pipedrive API token and configure the API Connector

In your Pipedrive account, click your profile icon in the top-right corner, then click 'Personal Preferences.' In the left sidebar, click 'API.' You will see your personal API token. If you have not generated one yet, click 'Generate new token.' Copy the token.

In your Bubble editor, go to Plugins → API Connector. If you have not installed the API Connector yet, click 'Add plugins,' search for 'API Connector' (by Bubble), and install it. Then click 'Add another API' and name it `Pipedrive`.

Set the base URL to: `https://api.pipedrive.com`

Now add a default URL parameter that will apply to every call in this group. Click 'Add a shared parameter (all calls).' Set:
- Parameter name: `api_token`
- Parameter value: paste your Pipedrive API token
- Check 'Private' — this keeps the token out of browser DevTools

With this setup, every API call you add to the Pipedrive group will automatically include `?api_token=your_token` in the URL. You do not need to add it to each call individually.

To verify the setup is correct, add your first call: click 'Add another call,' name it `Get Pipelines`. Set Method to GET and URL to `/v1/pipelines`. Click Initialize call. If you see a response with a `data` array containing your Pipelines, authentication is working correctly.

```
// Pipedrive API Connector configuration:
{
  "api_name": "Pipedrive",
  "base_url": "https://api.pipedrive.com",
  "shared_url_params": {
    "api_token": "<your_api_token>"  // mark Private
  }
}

// Test call: Get Pipelines
// GET /v1/pipelines
// Expected response:
// {
//   "success": true,
//   "data": [
//     { "id": 1, "name": "Sales Pipeline", "active": true },
//     { "id": 2, "name": "Enterprise Pipeline", "active": true }
//   ]
// }
```

**Expected result:** The Pipedrive API Connector group is created with the api_token as a shared Private URL parameter. The Get Pipelines initialization call returns a `data` array with your pipeline names and IDs visible in the API Connector UI.

### 2. Build the getStages and getDeals calls

Now add the core data calls for displaying deals by pipeline stage. These two calls are the foundation of any Pipedrive dashboard in Bubble.

Call 1 — Get Stages: Click 'Add another call' in the Pipedrive group. Name it `Get Stages`. Method: GET. URL: `/v1/stages`. Add a URL parameter `pipeline_id` (dynamic) — this filters stages to a specific pipeline. Set 'Use as' to Data. Initialize call — pass any of your pipeline IDs as the test value. The response contains a `data` array of stage objects with `id`, `name`, `pipeline_id`, and `order_nr` fields.

Call 2 — Get Deals: Click 'Add another call.' Name it `Get Deals`. Method: GET. URL: `/v1/deals`. Add URL parameters:
- `pipeline_id` (dynamic) — filter by pipeline
- `stage_id` (dynamic, optional) — filter by specific stage
- `status` = `open` (default, change to `won` or `lost` as needed)
- `start` = `0` (pagination offset, dynamic)
- `limit` = `100` (max records per page)
- `sort` = `update_time DESC` (most recently updated first)

Set 'Use as' to Data. Initialize with a real pipeline_id. The response `data` array contains deal objects including `id`, `title`, `value`, `currency`, `status`, `stage_id`, `pipeline_id`, `owner_name`, `person_id`, and `org_id`.

Note: deal `value` is a number (not a formatted currency string). Use Bubble's `:formatted as currency` expression when displaying it in Text elements.

Call 3 — Get Deal Activities: GET `/v1/deals/<deal_id>/activities` (mark deal_id as dynamic). Useful for showing activity history on a deal detail page. Set as Data.

```
// Call 1: Get Stages
{
  "method": "GET",
  "url": "/v1/stages",
  "params": {
    "pipeline_id": "<pipeline_id>"  // e.g., 1
  }
}
// Response: { "success": true, "data": [ { "id": 1, "name": "Lead In", "order_nr": 1 }, ... ] }

// Call 2: Get Deals
{
  "method": "GET",
  "url": "/v1/deals",
  "params": {
    "pipeline_id": "<pipeline_id>",
    "status": "open",         // open | won | lost | deleted | all_not_deleted
    "start": "0",             // pagination offset
    "limit": "100",           // max 500
    "sort": "update_time DESC"
  }
}
// Response:
// {
//   "success": true,
//   "data": [ { "id": 1, "title": "Acme Corp Deal", "value": 5000, "stage_id": 3, ... } ],
//   "additional_data": {
//     "pagination": { "start": 0, "limit": 100, "more_items_in_collection": true, "next_start": 100 }
//   }
// }
```

**Expected result:** Get Stages and Get Deals calls initialize successfully and return real stage and deal data. The `data` array is visible in the API Connector response schema with all key deal fields (id, title, value, stage_id, pipeline_id, owner_name) auto-detected.

### 3. Build the deal write operations (create, update, log activity)

Add calls for creating and updating deal records in Pipedrive.

Call 4 — Create Deal: Method POST. URL: `/v1/deals`. Body type: JSON. Required fields: `title` (text), `pipeline_id` (number), `stage_id` (number). Optional but useful: `value` (number), `currency` (text, e.g., `USD`), `person_id` (number), `org_id` (number), `owner_id` (number, Pipedrive user ID). Set 'Use as' to Action.

Note: unlike Zoho CRM, Pipedrive does NOT require a `data` array wrapper — the JSON body is a flat object. Pipedrive also returns the created deal in the response `data` field, so you can extract the new deal's `id` immediately.

Call 5 — Update Deal (Move Stage): Method PATCH. URL: `/v1/deals/<deal_id>` (mark deal_id as dynamic URL parameter). Body: JSON with only the fields to change — for stage moves: `{ "stage_id": "<new_stage_id>" }`. For value updates: `{ "value": "<new_value>" }`. Set 'Use as' to Action.

Call 6 — Create Activity: Method POST. URL: `/v1/activities`. Body: `{ "subject": "<subject>", "type": "<type>", "due_date": "<date>", "due_time": "<time>", "deal_id": "<deal_id>", "note": "<note>" }`. Activity types: `call`, `meeting`, `task`, `deadline`, `email`, `lunch`. Date format: `YYYY-MM-DD`. Set 'Use as' to Action.

```
// Call 4: Create Deal (flat JSON body - no data[] wrapper)
{
  "method": "POST",
  "url": "/v1/deals",
  "body": {
    "title": "<deal_title>",
    "pipeline_id": "<pipeline_id>",
    "stage_id": "<stage_id>",
    "value": "<deal_value>",
    "currency": "USD",
    "person_id": "<person_id>",
    "org_id": "<org_id>"
  }
}
// Response: { "success": true, "data": { "id": 123, "title": "...", ... } }

// Call 5: Update Deal Stage (PATCH - only changed fields)
{
  "method": "PATCH",
  "url": "/v1/deals/<deal_id>",
  "body": {
    "stage_id": "<new_stage_id>"
  }
}

// Call 6: Create Activity
{
  "method": "POST",
  "url": "/v1/activities",
  "body": {
    "subject": "<activity_subject>",
    "type": "<activity_type>",  // call | meeting | task | deadline | email | lunch
    "due_date": "<YYYY-MM-DD>",
    "due_time": "<HH:MM>",
    "deal_id": "<deal_id>",
    "note": "<optional_note>"
  }
}
```

**Expected result:** Create Deal, Update Deal, and Create Activity calls are configured as Actions in the API Connector. A test deal creation returns the new deal's ID in the response. Stage updates reflect immediately in your Pipedrive account.

### 4. Build the pipeline dashboard with Custom State caching

Now build the Bubble UI to display a Pipedrive pipeline dashboard. The key architectural decision: load ALL deals from one API call and filter them locally in Bubble rather than making one API call per stage.

Step-by-step UI setup:

1. On your dashboard page, add a Dropdown element linked to a Custom State `selected_pipeline_id`. Populate it from the Get Pipelines API call: data source = 'Get data from external API → Pipedrive → Get Pipelines,' display field = `name`, value field = `id`.

2. Add a Custom State named `all_deals` of type 'text' (store as JSON) — actually better, create a new Bubble Thing type `Pipedrive_Deal` with fields for the deal properties you need (title, value, stage_id, owner_name). Then the Custom State can be a list of these Things. Alternatively, store the raw API response in the Custom State by using the API connector's 'Use as Data' mode and binding the Repeating Group directly to the API response with filters.

3. The simpler approach: use one Repeating Group bound to 'Get data from external API → Pipedrive → Get Deals' with pipeline_id = `selected_pipeline_id` Custom State value. Set it to 'hidden' (height 0 or visibility off). This loads all deals from the API.

4. Create additional Repeating Groups for each stage (or a single RG with a dropdown filter), all sourcing data from the hidden parent RG's list filtered by stage_id: `Parent Repeating Group's Data:filtered where stage_id = [stage id constant]`.

5. Add a 'Load More' button that reads `additional_data.pagination.next_start` from the API response and re-queries with `start` set to that value.

RapidDev's team has built dozens of custom CRM dashboards in Bubble for Pipedrive users — if you want help designing the right data architecture for your pipeline view, visit rapidevelopers.com/contact.

```
// Page architecture for pipeline dashboard:

// 1. Custom State: selected_pipeline_id (number)
//    Source: Pipeline Dropdown value

// 2. Master Repeating Group (can be hidden):
//    Data source: Get data from external API → Pipedrive → Get Deals
//    pipeline_id: selected_pipeline_id state value
//    Visibility: hidden (or collapse when hidden)

// 3. Per-stage Repeating Group:
//    Data source: Master RG's list of Data:filtered where stage_id = 1234
//    (filter by the specific stage ID for that column)

// 4. Rate limit protection (consolidate API calls):
//    WRONG: 5 separate Repeating Groups each with their own API call source
//    CORRECT: 1 master API call, 5 RGs filter the same data client-side

// 5. Pagination: Load More button workflow
//    Action: Refresh data for Repeating Group with new 'start' param
//    start = Get Deals additional_data pagination next_start

// Deal value formatting:
// Text element: [Deal's value]:formatted as currency [Deal's currency]
```

**Expected result:** The pipeline dashboard loads all deals in one API call and displays them in stage-filtered groups. The Dropdown lets users switch between pipelines and refreshes the deal list. A 'Load More' button appears when more deals exist. Stage updates (button click → PATCH) reflect immediately in both the Bubble UI and Pipedrive.

## Best practices

- Mark the api_token shared URL parameter as Private in Bubble's API Connector even though Bubble's server-side architecture already prevents it from reaching the browser. The Private checkbox adds an extra layer of security and prevents the token from appearing in Bubble's editor previews or workflow logs.
- Use a dedicated Pipedrive integration account for the API token rather than a personal user account. If the account owner leaves the organization and is deactivated in Pipedrive, the integration breaks immediately. A service account is more resilient.
- Consolidate API calls for pipeline dashboards into a single getDeals call and filter client-side in Bubble. Multiple Repeating Groups each making separate stage-filtered API calls can easily exceed Pipedrive's rate limit with modest user concurrency.
- Cache stage data in a Custom State on page load rather than calling GET /stages in every workflow that needs stage names. Stage lists rarely change — fetching them once per page load and storing in a Custom State is sufficient for display purposes.
- Use Pipedrive's `start` and `limit` pagination parameters for large deal lists. The default limit is 100 deals per page; set it up to 500 for a full pipeline view. Implement a 'Load More' button using `additional_data.pagination.next_start` rather than loading all deals at once.
- Format deal values using Bubble's `:formatted as currency` expression. Pipedrive returns `value` as a plain number and `currency` as a separate text field — combine them for user-friendly display.
- Validate that stage_id values belong to the target deal's pipeline before sending PATCH requests. Cross-pipeline stage IDs silently move deals to a different pipeline rather than returning an error.
- Create a separate Bubble workflow for post-deal activity logging (phone calls, meetings, emails). Use Pipedrive's `/v1/activities` endpoint to create searchable activity records linked to the deal_id — this maintains a complete interaction history in Pipedrive without requiring agents to leave your Bubble app.

## Use cases

### Visual sales pipeline kanban board

Build a Bubble page that mirrors Pipedrive's native kanban view but with your own branding and custom fields visible. A single API call fetches all deals for the selected pipeline, and Bubble's :filtered operator segments them by stage into separate visual columns. Drag-and-drop stage changes (via a dropdown or button) trigger PATCH calls to update the deal's stage in Pipedrive.

Prompt example:

```
Create a horizontal scrolling page with one Group per pipeline stage. Each Group contains a Repeating Group filtered to show deals from a Custom State list where stage_id matches the column's stage. A 'Move to Next Stage' button on each deal card triggers a PATCH call to update the deal's stage_id to the next stage in the sequence.
```

### Field rep mobile CRM app

Give field sales representatives a mobile-optimized Bubble app for logging calls, creating follow-up activities, and updating deal notes after client visits — without needing the full Pipedrive desktop interface. The app reads their assigned deals and posts new Activity records to Pipedrive after each interaction.

Prompt example:

```
Show a Repeating Group of the current user's assigned deals filtered by status=open and owner_id matching the logged-in user's Pipedrive person ID. Include a 'Log Activity' button that opens a popup with activity type (call, email, meeting), due date, and notes — submitting triggers a POST to /v1/activities with the deal_id linked.
```

### Deal creation form for marketing landing pages

Embed a Bubble form on a marketing landing page that captures lead information and creates a Pipedrive Deal and Person record simultaneously. Incoming leads from paid ads or events go directly into the sales pipeline with source attribution, skipping manual data entry.

Prompt example:

```
When a user submits the lead capture form, POST to Pipedrive /v1/persons with name and email, then use the returned person_id to POST to /v1/deals with the deal title, pipeline_id, stage_id, and value. Set deal_source to 'Landing Page'. Show a confirmation screen after both calls complete.
```

## Troubleshooting

### API calls return 401 Unauthorized or 'Invalid API key'

Cause: The api_token parameter is missing, contains extra whitespace, or the Pipedrive user whose token is being used has been deactivated. Another cause: the token was recently regenerated in Pipedrive and the old value is still in Bubble's API Connector.

Solution: Go to Pipedrive → Personal Preferences → API and verify your current API token. Re-copy it without trailing spaces. In Bubble's API Connector, expand the Pipedrive group and check the `api_token` parameter value. Also verify the Pipedrive account is active — deactivated users have their API tokens invalidated immediately.

### Rate limit errors (429) when loading multiple Repeating Groups on one page

Cause: Multiple Repeating Groups each making separate API calls to `/v1/deals` (with different stage_id filters) fire simultaneously on page load, exceeding Pipedrive's 80 requests per 2-second limit. With 5 stages and 15 users loading the page simultaneously, this can generate 75 requests in under 1 second.

Solution: Consolidate to a single `getDeals` API call without a stage_id filter. Store the full results and filter them client-side in Bubble using Repeating Group data source `:filtered where stage_id = [specific stage id]`. This reduces API calls from N (one per stage) to 1 per user page load.

```
// Wrong pattern (separate call per stage):
// RG1: GET /v1/deals?stage_id=1
// RG2: GET /v1/deals?stage_id=2
// RG3: GET /v1/deals?stage_id=3

// Correct pattern (one call, filter in Bubble):
// Master RG: GET /v1/deals?pipeline_id=1&limit=500
// RG1: Master RG's List:filtered where stage_id = 1
// RG2: Master RG's List:filtered where stage_id = 2
// RG3: Master RG's List:filtered where stage_id = 3
```

### 'There was an issue setting up your call' when initializing Get Deals

Cause: The Initialize call needs a valid pipeline_id to return deals. If your pipeline has no deals yet, or the pipeline_id parameter is left empty during initialization, Pipedrive returns an empty `data` array — and Bubble cannot detect field schema from an empty array.

Solution: Create at least one test deal in Pipedrive before initializing. During initialization, temporarily hardcode a real pipeline_id as the parameter value. Once the Initialize call returns deal data, Bubble auto-detects the schema. You can then switch back to a dynamic pipeline_id parameter.

### Deal stage update (PATCH) succeeds but the deal appears in the wrong pipeline

Cause: Stage IDs are unique per pipeline. Passing a stage_id from Pipeline A to a deal in Pipeline B silently moves the deal to Pipeline A, which appears as if the stage update 'failed' from the original pipeline's view.

Solution: Always verify that the stage_id you are sending belongs to the same pipeline as the deal being updated. Fetch stages with `GET /v1/stages?pipeline_id=X` and use only stage IDs from that response. Build a Dropdown that shows stages filtered to the current deal's pipeline_id to prevent cross-pipeline stage assignment.

### Deal `value` displays as a raw number without currency formatting

Cause: Pipedrive returns `value` as a plain number (e.g., `5000`) with a separate `currency` field (e.g., `USD`). Bubble displays this as-is without formatting.

Solution: In the Text element displaying the deal value, use Bubble's dynamic expression: `[deal's value]:formatted as currency`. If you need to include the currency code, append `[deal's currency]` as a separate text element or combine: `[deal's currency] [deal's value]:formatted as currency`.

## Frequently asked questions

### Does Pipedrive work on Bubble's Free plan?

Yes. Pipedrive uses API token authentication with no OAuth redirect and no Backend Workflows required for authentication. All Pipedrive read and write operations work from standard Bubble client-side workflows, making it one of the few CRM integrations that works fully on Bubble Free plan. You only need a paid Bubble plan if you want to use Backend Workflows for scheduled syncs or webhook receivers.

### What is Pipedrive's API rate limit?

Pipedrive allows 80 API requests per 2-second window. This applies per API token, not per user. A Bubble app with 10 concurrent users, each triggering 9 API calls on page load, would hit this limit immediately. The solution is to reduce calls per page load — consolidate multiple stage-filtered queries into one paginated deals call and filter client-side in Bubble.

### How do I get the current logged-in user's Pipedrive person ID to filter their assigned deals?

Call GET `/v1/users/me` using the same API token — this returns the current token owner's user information including their `id`. Store this as an app-level Custom State on page load. Then filter deals with `?owner_id=<the_user_id>` in the API call. If you want each Bubble user to see their own deals, you would need to use OAuth (per-user tokens), since a single API token always acts as the same Pipedrive user.

### Can I receive Pipedrive webhook events in Bubble?

Yes, but this requires a Bubble paid plan (Starter or above) for Backend Workflows. Pipedrive can send webhook events to a public URL when deals are created, updated, deleted, or stage-changed. In Bubble, create a Backend Workflow with 'Expose as public API' enabled, use the workflow URL as Pipedrive's webhook destination, and use 'Detect request data' to capture the payload schema. Pipedrive webhooks send a POST with deal change data in the request body.

### How do I display deal value in the user's currency format?

Pipedrive returns `value` as a plain number and `currency` as a separate text string (e.g., `USD`, `EUR`, `GBP`). In Bubble, use the `:formatted as currency` dynamic expression on the value number for proper formatting. Pipedrive supports multi-currency deals, so always display the `currency` field alongside the value to avoid confusion in international pipelines.

### What happens if I pass a stage_id from one pipeline to update a deal in another pipeline?

Pipedrive silently moves the deal to the pipeline that the stage belongs to, rather than returning an error. This means the deal disappears from its original pipeline and appears in the wrong one. Always validate that the stage_id you are sending comes from the same pipeline as the deal. Build your stage-selection UI to only show stages from the current deal's pipeline.

---

Source: https://www.rapidevelopers.com/bubble-integrations/pipedrive
© RapidDev — https://www.rapidevelopers.com/bubble-integrations/pipedrive
