Yes, Cargo writes enriched go-to-market records back to Pipedrive natively: upsertRecords updates the records whose chosen field matches your value and creates one when none does, updateRecords only updates, and insertRecord only creates. Update and upsert match on the one field you choose and nothing else, and with isFailingOnMultipleMatches turned on they write nothing and fail the row when several records match, instead of overwriting all of them.
The three write actions #
All three take an objectType (organization, person, deal or lead) and a list of mappings, each a Pipedrive field key and the value to write. Update and upsert also take one matchingObjectFieldName and one matchingValue.
| Action | No record matches | One record matches | Several records match |
|---|---|---|---|
insertRecord | Creates one | Creates one more | Creates one more |
updateRecords | Writes nothing, returns an empty array | Updates it | Updates every match; with isFailingOnMultipleMatches, fails the row instead |
upsertRecords | Creates one, including the matched value | Updates it | Updates every match; with isFailingOnMultipleMatches, fails the row instead |
How the match works:
- Only the chosen field counts. Cargo runs Pipedrive’s item search with exact match, restricted to that field where Pipedrive allows it, then checks every hit against the field itself. A record whose value appears in a note or in another field is not a match. The comparison ignores case and surrounding spaces.
idreads the record instead of searching. Matching onidfetches that one record, so it can never be ambiguous. An id that no longer exists matches nothing.- A custom field is matched by its key. Pass the field’s 40-character key (the value the field picker lists for it) as
matchingObjectFieldName, and only that custom field is compared. The genericcustom_fieldsoption matches the value in any custom field, so use the key. - Several matches are all updated unless you say otherwise.
isFailingOnMultipleMatchesis off by default. Turn it on and an ambiguous row writes nothing and fails with an error naming the count, for example3 records match the matching value; "Fail on multiple matches" is on, so nothing was written. There is no option to skip the row silently: a failed row is how the ambiguity reaches you. - The matching field is not rewritten on update unless you map it explicitly.
On create, upsertRecords writes every mapping plus the matching value, so the next run finds the record: name on organizations and persons, title on deals and leads, address on organizations, email and phone on persons (as the primary value), or the custom field whose key you matched on. A value you mapped yourself wins over the matching value.
skipIfExist on a mapping writes that field on update only when it is empty in Pipedrive. On create, every mapping is written.
Match by id, then domain, then name #
Pipedrive organizations have no domain field you can match on: the searchable organization fields are name, address, notes and custom fields. Keep the domain in a custom field, and use the strongest key each row has:
- Pipedrive id (
id). If an earlier sync stored the id on your account, update by it withupdateRecords. It is the only key that cannot be ambiguous. - Domain (your custom field key). Update on it with
isFailingOnMultipleMatcheson: one match is updated, none returns an empty array, several fail the row. - Name (
name). The weakest key, for rows the domain did not resolve. Same option, same three outcomes.
Send ambiguous rows to a person. A row that failed because two organizations share a name is a duplicate in Pipedrive: picking one of them by rule is how a sync writes enrichment onto the wrong account, and merging them is a separate decision. When no key matches anything, create the organization with upsertRecords on the domain key, so the new record carries its domain and the next run finds it.
Run it from the CLI #
Install the CLI, sign in, check the Pipedrive connector, and find the actions. Each result carries the action object the commands below take:
npm install -g @cargo-ai/cli
cargo-ai login --email you@company.com
cargo-ai connection connector list --integration-slug pipedrive
cargo-ai orchestration action list upsert --kind connector --integration-slug pipedrive
Every record you pass to execute-batch is the action’s input. Extra keys, like a rowId of your own, are carried through to the results file, which is how you join a result back to the account it belongs to. The examples use 9f1c2e7a4b3d5f6e8a0b1c2d3e4f5a6b7c8d9e0f as the key of a custom Domain field; use your own.
1. Accounts with a Pipedrive id: update by id.
cargo-ai orchestration action execute-batch \
--action '{"kind":"connector","integrationSlug":"pipedrive","actionSlug":"updateRecords"}' \
--records '[{"objectType":"organization","matchingObjectFieldName":"id","matchingValue":4182,"mappings":[{"propertyName":"9f1c2e7a4b3d5f6e8a0b1c2d3e4f5a6b7c8d9e0f","value":"acme.com"},{"propertyName":"name","value":"Acme","skipIfExist":true}]}]' \
--wait-until-finished
2. Accounts without an id: update by domain, failing on duplicates.
cargo-ai orchestration action execute-batch \
--action '{"kind":"connector","integrationSlug":"pipedrive","actionSlug":"updateRecords"}' \
--records '[{"rowId":"acc_1","objectType":"organization","matchingObjectFieldName":"9f1c2e7a4b3d5f6e8a0b1c2d3e4f5a6b7c8d9e0f","matchingValue":"acme.com","isFailingOnMultipleMatches":true,"mappings":[{"propertyName":"name","value":"Acme","skipIfExist":true}]}]' \
--wait-until-finished
cargo-ai orchestration batch download --uuid <batch-uuid>
batch download returns a URL to a CSV with your input columns, including rowId, and each row’s result.
3. Act on each row by what the update did.
- One record in
data: done. - The row failed with “records match the matching value”: skip it and list it for review.
datais empty: repeat step 2 with"matchingObjectFieldName":"name"and the name asmatchingValue. One match is done, a failure is a skip, and an empty result is a create:
cargo-ai orchestration action execute-batch \
--action '{"kind":"connector","integrationSlug":"pipedrive","actionSlug":"upsertRecords"}' \
--records '[{"objectType":"organization","matchingObjectFieldName":"9f1c2e7a4b3d5f6e8a0b1c2d3e4f5a6b7c8d9e0f","matchingValue":"acme.com","isFailingOnMultipleMatches":true,"mappings":[{"propertyName":"name","value":"Acme"}]}]' \
--wait-until-finished
Pass --limit 5 to any of these to run a pilot on the first five records and check the result in Pipedrive before the full set.
What each action returns #
insertRecord,updateRecordsandupsertRecordsreturndataas an array of the Pipedrive records they wrote, as Pipedrive’s API returns them:idand the standard fields, with custom fields of organizations, persons and deals undercustom_fields, keyed by field key. A create returns one record; an update returns one per record it changed; an update that matched nothing returns an empty array.- A row stopped by
isFailingOnMultipleMatchesreturns no data and fails with the error above.
None of the Pipedrive actions carries a credit price. The connector paces its calls to Pipedrive at 5 per second, and each update or upsert makes one search, a read of each candidate record, and one write per match, so a large batch is spread out rather than rejected.
The limits #
upsertRecordson a stale id creates a record. An id that no longer exists matches nothing, and upsert then creates a new record without that id. UseupdateRecordsfor id matches.- An empty
matchingValuematches nothing.updateRecordsthen writes nothing, andupsertRecordscreates a record. Filter out rows with an empty key before the batch. - Matching on the generic
custom_fieldsoption does not carry over on create. The created record does not get the value, so the next run creates another. Match on a specific custom field key. skipIfExistreads top-level fields. On organizations, persons and deals, Pipedrive returns custom fields undercustom_fields, so askipIfExistmapping on a custom field key does not see the stored value and writes the field anyway. Rely on it for standard fields.- The match is one page of search results. Hits come from a single Pipedrive item search, so a value shared by more records than one page holds is not fully counted. That is one more reason to match on a key that should be unique and to turn
isFailingOnMultipleMatcheson.
This page covers create, update and upsert. Delete records is a separate action and is not covered here.
Persons, deals and leads work the same way #
The same actions take "objectType":"person", "deal" or "lead". For persons, match by id first, then email, and fail what matches twice. To keep the sync running as accounts are enriched, put the same steps in a play that fires when a record changes, rather than a script on a laptop. The same job on other CRMs is in sync enriched accounts to HubSpot, sync enriched records to Salesforce and sync enriched accounts to Attio. The broader rules for keeping the CRM clean are in CRM hygiene as code, and the CRM enrichment cookbook fills the identifiers this matching depends on.
Frequently asked questions #
Can Cargo upsert records in Pipedrive?
Yes. The Pipedrive connector’s upsertRecords action updates the records whose chosen field equals your value and creates a record when none does, writing the matched value onto it. updateRecords only updates and insertRecord only creates.
What happens when two Pipedrive organizations match?
By default, both are updated. Set isFailingOnMultipleMatches to true and the row writes nothing and fails with the number of records that matched, so you can send it to a person.
Does a value in another field count as a match?
No. Update and upsert compare only the field named in matchingObjectFieldName. A value that appears in a note or in a different custom field is not a match.
Can Cargo match Pipedrive organizations by domain?
Only through a custom field. Organizations have no searchable domain field, so store the domain in a custom field and match on that field’s key.
Does syncing to Pipedrive cost Cargo credits?
No. The Pipedrive actions carry no credit price. You need a Pipedrive connector set up with your company domain and an API token.