# Pipedrive — CRM API: gotchas

> Canonical URL: https://developers.apideck.com/apis/crm/pipedrive/gotchas

## GET /crm/companies

Pipedrive does not expose a cross-page total record count in list responses. `meta.total_count` will not appear.

Companies are paginated with an opaque cursor. A cursor obtained before the move to cursor pagination is rejected with `InvalidCursorError`; restart the listing from the first page instead of resuming it.

Sorting is limited to `created_at` and `updated_at`; `sort[by]=name` returns an unsupported-sort error.

`filter[name]` is served by Pipedrive's search: it is a partial, case-insensitive match and needs at least 2 characters. Filtered results contain only `id`, `name`, `owner_id` and the address as a single `addresses[0].string`; they omit `created_at`, `updated_at`, `deleted`, `websites`, `social_links`, `industry`, `annual_revenue`, `number_of_employees` and `custom_fields`. A supported `sort` (`created_at` / `updated_at`) combined with the filter is ignored; `sort[by]=name` still returns the unsupported-sort error. Pipedrive's search index can keep returning deleted companies for a while, without a `deleted` value; confirm with `GET /crm/companies/{id}` (`deleted: true`).

The built-in Pipedrive organization fields are mapped to `websites[0]`, `social_links[]` (type `linkedin`), `industry`, `annual_revenue` and `number_of_employees`. `industry` and `annual_revenue` are Pipedrive option ids, not labels. The same values are also available in `custom_fields` under the ids `website`, `linkedin`, `industry`, `annual_revenue`, `employee_count`, `label_ids` (only when labels are set) and `visible_to`, so readers of those ids keep working. Custom-field option values are returned as numeric option ids; monetary and date-range fields are split into `<key>` plus `<key>_currency` / `<key>_until` entries.

A deleted company disappears from the list but `GET /crm/companies/{id}` keeps returning it with `deleted: true`. A non-numeric id such as `/crm/companies/abc` is answered with a 403 by Pipedrive.

## POST /crm/companies

`owner_id` must be a numeric Pipedrive user id; `industry` and `annual_revenue` must be Pipedrive option ids and `number_of_employees` a whole number (thousands separators are accepted, decimals are rounded). Any other value is forwarded and rejected by Pipedrive with a 400.

Only `addresses[0]` is stored: `string` becomes the full address (composed from the parts when omitted), `line1` the street, `line2` the suite/apartment, plus `street_number`, `city`, `state`, `county`, `postal_code` and `country` (stored as sent, not geocoded). Further entries are ignored. `websites[0].url` and the first `social_links` entry of type `linkedin` are stored; other websites and social types are ignored, as are `phone_numbers`, `emails`, `description`, `tags`, `status` and `parent_id`.

`custom_fields[].id` must be the Pipedrive custom field key and the value must match the field type: text fields take a string, single-option fields the numeric option id (a label or a quoted id is rejected), monetary fields `{"value": 2300, "currency": "EUR"}` or the flat entries `<key>` plus `<key>_currency`, date-range fields `{"value", "until"}` or `<key>` plus `<key>_until`. A bare number for a monetary field, a `<key>_currency` entry without its base entry, or an unknown key is rejected with a 400. The ids `name`, `owner_id`, `address` (a string or a Pipedrive address object, plus the v1 `address_*` part keys), `label` (one or more label ids), `website`, `linkedin`, `industry`, `annual_revenue`, `employee_count`, `label_ids` and `visible_to` inside `custom_fields` write the corresponding built-in field; the unified field wins when both are sent, and a null or empty value for `name`, `owner_id`, `address` or `label` is ignored (clear through the unified field). Any other non-custom key inside `custom_fields` (for example `cc_email`) is rejected by Pipedrive with a 400.

## PATCH /crm/companies/{id}

Updates are partial: only the fields you send change. The value rules of create apply (numeric `owner_id`, option ids for `industry` and `annual_revenue`, whole `number_of_employees`, typed `custom_fields` values).

Clearing: `websites: []` or `social_links: []` clears the website or LinkedIn URL; `custom_fields: [{"id": "<key>", "value": null}]` clears a custom field and `{"id": "label_ids", "value": null}` removes all labels. `industry`, `annual_revenue`, `number_of_employees` and `owner_id` cannot be cleared with `null` (Pipedrive rejects it with a 400); Pipedrive accepts `0` for the three numeric fields, which then reads back as `"0"`. The address cannot be cleared, and `addresses[0]` replaces the whole stored address: send every part you want to keep, or the other parts are wiped. A date-range custom field sent as `{"value": ...}` without `until` clears the end date.

`visible_to` in `custom_fields` is applied only for values the Pipedrive plan allows; other values are accepted but ignored. A deleted company cannot be updated (403) or deleted again (400).

## GET /crm/contacts

Pipedrive does not expose a cross-page total record count in list responses. `meta.total_count` will not appear.

Contacts are paginated with an opaque cursor. A cursor obtained before the move to cursor pagination is rejected with `InvalidCursorError`; restart the listing from the first page instead of resuming it.

Every phone number and email address stored on the Pipedrive person is returned, and `phone_numbers[].type` / `emails[].type` reflect the label set in Pipedrive. Labels that match a unified type (`work`, `home`, `mobile`, `fax`, `primary`, `billing`, ..., compared case-insensitively) are preserved; a custom label that has no unified equivalent, or an empty label, is reported as `other`, and updating such a contact writes `other` back as the label. Pipedrive's per-entry "primary" flag is not surfaced because the unified schema has no field for it.

Deleting a contact is a soft delete in Pipedrive: the contact disappears from the list, but `GET /crm/contacts/{id}` keeps returning it with `active: false`, and Pipedrive's search index can keep returning it for filtered requests for a while, without an `active` value.

Sorting is limited to `created_at` and `updated_at`; `sort[by]=name`, `first_name`, `last_name` or `email` returns an unsupported-sort error.

Filters are served by Pipedrive's search and cannot be combined: use one of `name`, `email` or `phone_number` per request (combining them searches for all values together and typically returns nothing). `filter[email]` and `filter[phone_number]` are exact matches (case-insensitive): only contacts whose stored email or phone number equals the value are returned, so a partial value returns nothing, and a phone number must be formatted exactly as stored in Pipedrive (e.g. `+15550100100` does not match `15550100100`). `filter[name]` is a partial match. Filtered results omit `first_name`, `last_name`, `created_at`, `active` and custom fields, carry `company_name` (which unfiltered results do not), and their phone numbers and emails have no labels, so `type` is always `other`. Pipedrive's search cannot be sorted, so a `sort` combined with a filter is ignored.

## POST /crm/contacts

Pipedrive accepts either `name` or the pair `first_name` + `last_name`, never both. When `name` is present it is sent and `first_name`/`last_name` are ignored; without `name`, both `first_name` and `last_name` are required.

`owner_id` and `company_id` must be numeric Pipedrive ids; any other value is passed to Pipedrive and rejected with a 400.

`custom_fields[].id` must be the Pipedrive custom field key. Multi-part fields (monetary, date range, time, address) can be sent as the combined object, e.g. `{"value": 2300, "currency": "EUR"}`, or as flat entries `<key>` plus `<key>_currency`, `<key>_until`, ..., which are folded together.

`phone_numbers[].type` and `emails[].type` are written as the Pipedrive label. Only `number` / `email` are stored: `country_code`, `area_code` and `extension` are dropped. `title`, `department`, `description`, `websites`, `addresses` and `social_links` are not supported by Pipedrive persons and are ignored.

## PATCH /crm/contacts/{id}

Updates are partial: fields left out of the request are not changed. Sending an empty `phone_numbers` or `emails` array clears the entries; a shorter array replaces the stored set rather than merging with it.

Sending `"value": null` for a custom field clears it; sending `company_id: null` unlinks the organization. An owner cannot be cleared (`owner_id: null` is rejected by Pipedrive).

A contact read from the API can be sent back unchanged: `name`, `first_name` and `last_name` are all returned, and on update only `name` is written when it is present. Multi-part custom fields round-trip as `<key>` plus `<key>_currency` / `<key>_until` entries.

Pipedrive accepts either `name` or the pair `first_name` + `last_name`, never both; `owner_id`/`company_id` must be numeric Pipedrive ids.

## GET /crm/opportunities

Pipedrive supports filtering by either title or company_id, but not both simultaneously.

Pipedrive does not expose a cross-page total record count in list responses. `meta.total_count` will not appear.

With `filter[company_id]` the list is served by Pipedrive's v2 deals endpoint: it is paginated with an opaque cursor (a cursor from before the change is rejected with `InvalidCursorError`), sorting is limited to `created_at` and `updated_at` (`sort[by]=title` returns an unsupported-sort error), `company_name` is not returned, `deleted` is returned, custom-field option values are numeric option ids with monetary and date-range fields split into `<key>` plus `<key>_currency` / `<key>_until` entries, and an unknown or non-numeric `company_id` is answered with a 400 instead of a 404. Deals of a deleted company return an empty list; archived deals are not included.

## POST /crm/opportunities

The `primary_contact_id` is required in the request body
## GET /crm/leads

Pipedrive does not expose a cross-page total record count in standard list responses. `meta.total_count` will not appear. Surfacing the total requires additional request parameters or a separate API call — not currently implemented.

## POST /crm/leads

When creating a lead, it is required to include an existing `company_id`. More info on the [Pipedrive developer docs](https://pipedrive.readme.io/docs/adding-a-lead).
## GET /crm/pipelines

Pipedrive does not expose a cross-page total record count in standard list responses. `meta.total_count` will not appear. Surfacing the total requires additional request parameters or a separate API call — not currently implemented.

## GET /crm/notes

Pipedrive does not expose a cross-page total record count in standard list responses. `meta.total_count` will not appear. Surfacing the total requires additional request parameters or a separate API call — not currently implemented.

## GET /crm/users

Pipedrive does not expose a cross-page total record count in standard list responses. `meta.total_count` will not appear. Surfacing the total requires additional request parameters or a separate API call — not currently implemented.

## GET /crm/activities

`filter[contact_id]` is served by Pipedrive's v2 activities API and returns only activities whose **primary participant** is the given person. It can be combined with `filter[owner_id]` and `filter[updated_since]`, but not with other activities filters such as `filter[company_id]` or `filter[type]` — those combinations are rejected with an unsupported-filters error. A `contact_id` that does not exist in Pipedrive returns a 400 error from the downstream API (Pipedrive validates the person ID) rather than an empty list. Cursors are tied to the filter combination that produced them and cannot be reused across filtered and unfiltered requests.
## GET /crm/activities

Pipedrive does not expose a cross-page total record count in standard list responses. `meta.total_count` will not appear. Surfacing the total requires additional request parameters or a separate API call — not currently implemented.


[API reference](https://developers.apideck.com/apis/crm/reference.md) · [Coverage matrix](https://developers.apideck.com/apis/crm/coverage)
