# Pipedrive

- Tool: FlutterFlow
- Difficulty: Beginner
- Time required: 30 minutes
- Last updated: July 2026

## TL;DR

Connect FlutterFlow to Pipedrive using a FlutterFlow API Call and Pipedrive's REST v1 API. Auth is a single api_token query parameter — no OAuth required. Build API Groups for GET /deals, GET /persons, and POST /deals, then bind results to ListViews. For production apps, route the token through a Firebase Cloud Function proxy to keep it out of the compiled binary.

## Build a Mobile Sales Pipeline App with FlutterFlow and Pipedrive

Pipedrive is designed around the concept of a visual sales pipeline — deals move through stages, reps track contacts, and managers see forecasts. By connecting FlutterFlow to Pipedrive, you can put that pipeline in the pocket of every sales rep: a native iOS or Android app that lists open deals, shows person details, and lets reps create new deals from the field without opening a browser.

What makes Pipedrive the easiest CRM to wire into FlutterFlow is its authentication model. There is no OAuth dance, no token refresh, no client secret to manage — just a single api_token that you append as a URL query parameter to every request. Pipedrive plans start from around $14 per user per month, and the REST API is available on every plan tier. The base URL takes the form https://{company}.pipedrive.com/api/v1, where {company} is your Pipedrive subdomain.

One thing to plan for early: Pipedrive returns up to 100 items per page. If your pipeline has more than 100 deals, you will need to pass start and limit variables and build a 'Load more' pattern in your app. Another detail to note is that nested objects like person_id return the full name object on fresh fetches but may return only a bare integer on cached or paginated responses — your JSON path mapping needs to account for both cases. Finally, while appending the token as a query parameter works perfectly for demos and internal tools, a shipped consumer app exposes that token inside the compiled binary. For real production apps, the right move is a Firebase Cloud Function that holds the token server-side and returns deal data to your FlutterFlow API Call.

## Before you start

- A Pipedrive account (any plan) with at least a few deals and contacts already in your pipeline
- Your Pipedrive API token (found in Pipedrive → top-right avatar → Personal preferences → API)
- A FlutterFlow project with at least one screen ready to display data
- Basic understanding of FlutterFlow's API Calls panel and widget binding
- A Firebase project if you plan to build the Cloud Function proxy for production

## Step-by-step guide

### 1. Get your Pipedrive API token

Log in to your Pipedrive account in a browser. Click your avatar in the top-right corner and select Personal preferences from the dropdown menu. In the preferences panel, click the API tab on the left sidebar — you will see your personal API token displayed as a long alphanumeric string. Click the copy icon next to the token to copy it to your clipboard. Keep this token private: anyone who has it can read and write all deals, persons, and organizations in your Pipedrive account under your user identity. Also note your company subdomain — it appears in your Pipedrive URL as https://{yoursubdomain}.pipedrive.com. You will need both the token and the subdomain in the next step. Before moving on, do a quick sanity check: open a new browser tab and paste https://{yoursubdomain}.pipedrive.com/api/v1/pipelines?api_token={yourtoken} into the address bar. You should see a JSON response listing your pipelines. If you see a 401 error, the token is wrong or your subdomain is incorrect — fix that now rather than after building the FlutterFlow API Group.

**Expected result:** You have your Pipedrive API token copied and confirmed it works by making a test request to /api/v1/pipelines in the browser.

### 2. Create an API Group for Pipedrive in FlutterFlow

Open your FlutterFlow project. In the left navigation bar, click API Calls (the plug icon). Click the + Add button at the top of the panel and select Create API Group. Name the group Pipedrive and set the Base URL to https://{yoursubdomain}.pipedrive.com/api/v1, replacing {yoursubdomain} with your actual Pipedrive company subdomain — for example, https://acmesales.pipedrive.com/api/v1. Do not add the api_token here at the group level. Instead, you will add it as a variable on each individual API Call so you can control how it is passed. Under the Headers section of the API Group, you do not need to add anything for now — Pipedrive's token goes as a query parameter, not a header (though Pipedrive also accepts x-api-token as a header if you prefer that pattern; both work). Click Save to save the API Group. You should now see the Pipedrive group listed in the API Calls panel. All individual API Calls you add next will inherit this base URL, so you only need to specify relative paths like /deals or /persons in each call.

**Expected result:** The Pipedrive API Group appears in your API Calls panel with the correct base URL set.

### 3. Add a GET /deals API Call and map JSON paths

Inside the Pipedrive API Group, click + Add API Call. Name it GetDeals. Set the HTTP method to GET and the endpoint to /deals. Now click the Variables tab. Add a variable named api_token with a default type of String. Back in the endpoint field, append ?api_token={{api_token}} so the full URL reads /deals?api_token={{api_token}}. Add two more variables: start (String, default '0') and limit (String, default '50'). Update the endpoint to /deals?api_token={{api_token}}&start={{start}}&limit={{limit}}. This gives you pagination control right from the start. Now click Response & Test. In the Test tab, enter your actual API token in the api_token field, leave start as 0 and limit as 10, then click Test API. You should see a JSON response with a data array containing your deals. Click the magic wand / Generate JSON Paths button to let FlutterFlow automatically create JSON path variables from the response structure. Key paths to verify: $.data[:].id (deal ID), $.data[:].title (deal name), $.data[:].value (deal value), $.data[:].status (open/won/lost), $.data[:].expected_close_date, $.data[:].person_id.name (contact name — note this is a nested object), $.data[:].org_id.name (organization name). Click Save. You can now reference this API Call in your widget's backend query or action flows.

```
{
  "group": "Pipedrive",
  "name": "GetDeals",
  "method": "GET",
  "endpoint": "/deals?api_token={{api_token}}&start={{start}}&limit={{limit}}",
  "variables": [
    { "name": "api_token", "type": "String" },
    { "name": "start", "type": "String", "default": "0" },
    { "name": "limit", "type": "String", "default": "50" }
  ],
  "response_json_paths": [
    "$.data[:].id",
    "$.data[:].title",
    "$.data[:].value",
    "$.data[:].status",
    "$.data[:].expected_close_date",
    "$.data[:].person_id.name",
    "$.data[:].org_id.name"
  ]
}
```

**Expected result:** GetDeals API Call is saved with pagination variables and JSON paths generated. Running the test returns a list of your Pipedrive deals.

### 4. Bind deals to a ListView on your app screen

Open the screen in FlutterFlow where you want to display deals. Add a Column widget and inside it a ListView (or a Wrap widget for card grids). Select the ListView and open the Backend Query section in the right panel. Click + Add Query, select the Pipedrive → GetDeals API Call, and set the api_token value. You can hardcode it here for a prototype, but for production you will replace this with the proxy pattern in the next step. Set the query to Trigger on Page Load so deals load automatically when the screen opens. Now select the child template widget inside the ListView (usually a Container or ListTile). Bind the text widgets to the JSON path variables: set the deal title Text widget's value to GetDeals.data.title, the value widget to GetDeals.data.value, and the status badge color to a conditional expression on GetDeals.data.status (green for 'open', blue for 'won', red for 'lost'). Add a Text widget for the contact name bound to GetDeals.data.person_id.name with a null-check fallback of 'No contact' in case person_id returns a bare integer on a paginated response. Test in Run mode to see your deals populate. If the ListView shows empty, check the API Call test response first — a successful test but empty ListView usually means the JSON path is mapping to the wrong level of the response object. Switch from $.data[:].title to $.data[0].title temporarily in the path tester to isolate the issue.

**Expected result:** Your screen displays a populated ListView of Pipedrive deals with title, value, status, and contact name visible on each card.

### 5. Add a POST /deals API Call to create deals from the app

In the Pipedrive API Group, click + Add API Call. Name it CreateDeal. Set the method to POST and the endpoint to /deals?api_token={{api_token}}. Click the Body tab and set the body type to JSON. In the body editor, paste the following structure and add corresponding variables for each field:

{
  "title": "{{deal_title}}",
  "value": "{{deal_value}}",
  "currency": "{{currency}}",
  "person_id": {{person_id}},
  "expected_close_date": "{{expected_close_date}}"
}

Add variables: api_token, deal_title (String), deal_value (String), currency (String, default 'USD'), person_id (Integer, optional), expected_close_date (String, optional). Click Test to confirm a 201 response. On your app's lead-capture form screen, add a Form widget with text fields for deal title, value, and expected close date. Add a Button widget. In the Button's action flow, add an API Call action pointing to Pipedrive → CreateDeal. Map each form field's value to the corresponding API Call variable. After the API Call action, add a Show Snack Bar action to display 'Deal created!' on success, and a Navigate Back action to return to the deals list. Rebuild the query on the deals list screen to refresh on return.

```
{
  "group": "Pipedrive",
  "name": "CreateDeal",
  "method": "POST",
  "endpoint": "/deals?api_token={{api_token}}",
  "headers": {
    "Content-Type": "application/json"
  },
  "body": {
    "title": "{{deal_title}}",
    "value": "{{deal_value}}",
    "currency": "{{currency}}",
    "person_id": "{{person_id}}",
    "expected_close_date": "{{expected_close_date}}"
  }
}
```

**Expected result:** Submitting the form creates a new deal in Pipedrive and the user sees a success confirmation. The deal appears in Pipedrive's web interface immediately.

### 6. Protect your API token with a Firebase Cloud Function proxy

For any app you plan to distribute — even to a small internal team — shipping the Pipedrive api_token inside the compiled FlutterFlow app is a security risk. Anyone who extracts the app binary can pull out the token and use it to read and modify all deals in your Pipedrive account. The correct pattern is to move the token server-side into a Firebase Cloud Function, and have FlutterFlow call the function URL instead of Pipedrive directly.

In Firebase Console, go to your project → Functions → Get started. Deploy the following Cloud Function (Node.js 18). Once deployed, you will receive a function URL like https://us-central1-{project}.cloudfunctions.net/pipedriveProxy.

Back in FlutterFlow, create a new API Group called PipedriveProxy with the Cloud Function URL as the base URL. Add a GetDeals call to /deals that accepts start and limit as variables but no api_token variable — the function supplies the token server-side. Update your ListView query to point to PipedriveProxy → GetDeals instead of the direct Pipedrive group. The JSON response structure is identical so your existing JSON paths continue to work unchanged.

If you would rather skip building the Cloud Function yourself, the RapidDev team builds FlutterFlow integrations like this every week — you can book a free scoping call at rapidevelopers.com/contact.

```
// Firebase Cloud Function: functions/index.js
const functions = require('firebase-functions');
const axios = require('axios');

const PIPEDRIVE_TOKEN = functions.config().pipedrive.token;
const PIPEDRIVE_SUBDOMAIN = functions.config().pipedrive.subdomain;

exports.pipedriveProxy = functions.https.onRequest(async (req, res) => {
  res.set('Access-Control-Allow-Origin', '*');
  if (req.method === 'OPTIONS') {
    res.set('Access-Control-Allow-Methods', 'GET, POST');
    res.set('Access-Control-Allow-Headers', 'Content-Type');
    return res.status(204).send('');
  }

  const path = req.query.path || '/deals';
  const start = req.query.start || '0';
  const limit = req.query.limit || '50';

  try {
    const response = await axios.get(
      `https://${PIPEDRIVE_SUBDOMAIN}.pipedrive.com/api/v1${path}`,
      {
        params: {
          api_token: PIPEDRIVE_TOKEN,
          start,
          limit
        }
      }
    );
    return res.json(response.data);
  } catch (error) {
    return res.status(error.response?.status || 500).json({
      error: error.message
    });
  }
});

// Deploy:
// firebase functions:config:set pipedrive.token="YOUR_TOKEN" pipedrive.subdomain="yourcompany"
// firebase deploy --only functions
```

**Expected result:** The FlutterFlow app calls your Firebase Cloud Function, which fetches deal data from Pipedrive server-side and returns it. The Pipedrive token is never visible in the compiled app binary.

## Best practices

- Never hardcode the Pipedrive api_token in the FlutterFlow API Call URL or headers for a shipped app — use the Firebase Cloud Function proxy pattern so the token stays server-side.
- Always include start and limit variables in your collection API Calls (GET /deals, GET /persons) to handle pipelines with more than 100 records.
- Add null-check fallbacks for nested Pipedrive objects like person_id.name and org_id.name, since paginated or cached responses may return a bare integer ID instead of the full object.
- Test your API Calls using the Response & Test tab in FlutterFlow before building the widget layer — confirm the JSON structure matches your JSON paths.
- Filter deals by status (open, won, lost) using the status query parameter rather than filtering client-side on a full dump, to reduce data transfer on mobile.
- Cache the api_token value in App State or App Values rather than prompting the user to enter it each time, so the app can call Pipedrive across multiple screens without repeated token lookups.
- Handle the success/failure status of POST /deals in your action flow — show a Snack Bar on success and a Dialog on error — so reps know whether a new deal was created before they move on.

## Use cases

### Mobile deal-tracking app for field sales reps

Build a FlutterFlow app where sales reps can view their open deals organized by pipeline stage, tap into a deal card to see the linked person and organization, and update the deal status directly from their phone. The app queries GET /deals and filters by status and stage_id to show each rep only their active pipeline.

Prompt example:

```
A mobile app screen that shows a list of Pipedrive deals for the logged-in sales rep, each card showing deal title, value, and expected close date, with a button to mark the deal as won.
```

### Lead capture app that creates Pipedrive deals from a form

Build a FlutterFlow form where sales reps enter a new lead's name, company, deal value, and expected close date at a trade show or meeting. On submit, the app calls POST /deals to create the deal in Pipedrive instantly, with the person and organization already linked, so no lead is ever forgotten.

Prompt example:

```
A FlutterFlow form screen with fields for contact name, company, deal value, and expected close date, with a Submit button that creates a new deal in Pipedrive and shows a success confirmation.
```

### Organization and contacts directory app

Build a searchable contacts app that pulls persons and organizations from Pipedrive using GET /persons and GET /organizations, displays them in a searchable ListView, and lets reps tap a contact to see their linked deals and email address — all without opening Pipedrive in a browser.

Prompt example:

```
A FlutterFlow screen with a search bar and a ListView of Pipedrive contacts showing name, organization, and email, with a detail page showing their associated deals.
```

## Troubleshooting

### 401 Unauthorized response on every Pipedrive API Call

Cause: The api_token value is missing, incorrect, or expired. Pipedrive also returns 401 if the token is passed as a header with the wrong name or the URL query param is mistyped.

Solution: First, test the token directly in your browser: paste https://{yoursubdomain}.pipedrive.com/api/v1/pipelines?api_token={yourtoken} into the address bar. If that returns JSON, the token is valid. In FlutterFlow, open the GetDeals API Call → Response & Test tab and confirm the api_token field in the test form shows your actual token value (not an empty string or {{api_token}} literal). Also double-check that your API Group base URL uses the correct Pipedrive subdomain — a wrong subdomain resolves to a different company's Pipedrive instance, which rejects your token.

### ListView only shows 100 deals and more deals exist in Pipedrive

Cause: Pipedrive paginates all collection endpoints at 100 items maximum per request. Without a start parameter, you always get the first 100 and there is no error — the response silently omits the rest.

Solution: Add start and limit variables to your GetDeals API Call as described in Step 3. On your deals list screen, add a Page State variable called currentStart (Integer, default 0). When the user taps 'Load more', fire the GetDeals API Call with start = currentStart + limit, then append the returned deals to your existing list using a Custom Action or an App State list variable. Increment currentStart by limit after each successful load.

### person_id.name shows null or empty for some deals

Cause: Pipedrive returns person_id as a nested object with a name field on direct GET requests, but on paginated responses or cached data it may return person_id as a bare integer (the person's numeric ID) rather than the full object. When FlutterFlow tries to access .name on an integer, it gets null.

Solution: In your JSON path for contact name, use $.data[:].person_id.name. In the widget binding, add a null-check: if the value is null or empty, display a fallback text like 'No contact linked'. For a more robust solution, make a separate GET /persons/{id} call when the user taps into a deal detail screen to always fetch the full person object by ID.

### POST /deals returns XMLHttpRequest error in FlutterFlow web preview (Run mode)

Cause: Pipedrive's API does not return CORS headers that allow browser-origin requests from FlutterFlow's web preview domain. Native mobile builds do not have this issue — CORS is only enforced by browsers.

Solution: Test POST /deals on a device build or using the API Call Test tab in FlutterFlow (which bypasses CORS). For web builds in production, your Firebase Cloud Function proxy handles the POST server-side and returns a CORS-safe response to the browser. Add Access-Control-Allow-Origin: * to your Cloud Function response headers as shown in the proxy code in Step 6.

## Frequently asked questions

### Is there a free tier on Pipedrive that includes API access?

Pipedrive does not offer a permanently free tier. API access is included on all paid plans, which start at around $14 per user per month for the Essential plan. You can start a trial to test the integration before committing to a paid plan.

### Can I use the same Pipedrive API token for all users of my FlutterFlow app?

Technically yes, but it means all actions taken through the app appear as coming from the single user who owns the token. For a multi-user sales team app, consider implementing Pipedrive's OAuth 2.0 flow so each user authenticates individually — this gives correct activity attribution in Pipedrive. The OAuth flow requires a Cloud Function proxy to handle the token exchange securely.

### Why do some deals show a blank contact name in my FlutterFlow app?

Pipedrive returns person_id as a nested object (with id, name, and email) on fresh direct GET requests, but may return just the numeric ID in some paginated or filtered responses. Map your JSON path to $.data[:].person_id.name and add a null fallback in your widget binding to display 'No contact' when the field is missing.

### Can FlutterFlow update a Pipedrive deal when a user swipes or taps a button in the app?

Yes. Add a PATCH /deals/{id} API Call to your Pipedrive API Group with the deal ID as a path variable and the fields to update (status, stage_id, value, etc.) in the JSON body. Wire it to a button's action flow using the same pattern as the POST /deals call. Remember to secure it with the Cloud Function proxy in production so the token is not exposed in the compiled binary.

### How do I get deal stages to populate a dropdown in FlutterFlow?

Add a GET /stages API Call to your Pipedrive API Group. The response returns an array of stage objects with id, name, and pipeline_id fields. Call this API on screen load, store the result in a Page State variable, and use it as the data source for a DropDown widget on your deal creation form. Pass the selected stage id as the stage_id body field when creating a new deal.

---

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