You get a nightly job that fills the gaps in Pipedrive. It reads people that have a LinkedIn URL, sends each to LeadOcean, and writes the work email and phone back with Pipedrive's update-person call. LeadOcean is a plain REST API, so any script that can make HTTP calls does it. The Free plan covers a first run of 1,000 records.
Related: the integrations hub, Retool, Notion, Folk and pricing.
How it works
The job reads from Pipedrive, enriches at LeadOcean and patches the same person back. Updating by person id is the dedupe: the job never creates a record, so it cannot create a duplicate. Each person is read once and written once, and a person that already has an email is skipped, so a second night costs nothing for people you already filled.
- Get a LeadOcean key. Sign up at app.leadocean.io, open API keys and copy one.
- Add a custom field in Pipedrive. Create a text field called
LinkedIn URLon People and fill it for the people you want enriched. Pipedrive refers to custom fields by a generated 40-character key, so copy that key from the field settings. - Copy your Pipedrive API token. Pipedrive takes it in the
x-api-tokenheader. The token belongs to one user and gives access to all of that user's data, so use a dedicated user. - Read people. Call
GET /api/v2/personswithlimit=500andcustom_fieldsset to your key, then page withcursor. Skip people with no LinkedIn URL or an email already. - Enrich each person. Call
POST /v1/people/enrichwith thelinkedin_urlandreveal_email: true. A person LeadOcean holds nothing for returns 404 and costs nothing. - Patch the person. Call
PATCH /api/v2/persons/{id}withemailsandphones. - Schedule it. Run the script from cron or any scheduler once a night. Filtering on an empty email means each person is enriched once. Pipedrive also has webhooks under Tools and apps, if you would rather trigger on new people.
Configuration
The script below is the whole job. Set LEADOCEAN_API_KEY, PIPEDRIVE_TOKEN, PIPEDRIVE_DOMAIN (your company domain) and LI_FIELD (the custom field key) in the environment.
import os, requests
LO = "https://api.leadocean.io"
PD = f"https://{os.environ['PIPEDRIVE_DOMAIN']}.pipedrive.com/api/v2"
LI = os.environ["LI_FIELD"]
lo = {"x-api-key": os.environ["LEADOCEAN_API_KEY"]}
pd = {"x-api-token": os.environ["PIPEDRIVE_TOKEN"]}
cursor = None
while True:
params = {"limit": 500, "custom_fields": LI}
if cursor:
params["cursor"] = cursor
page = requests.get(f"{PD}/persons", headers=pd, params=params).json()
for p in page["data"]:
url = (p.get("custom_fields") or {}).get(LI)
has_email = any(e.get("value") for e in p.get("emails") or [])
if not url or has_email:
continue
e = requests.post(f"{LO}/v1/people/enrich", headers=lo,
json={"linkedin_url": url, "reveal_email": True})
if e.status_code == 404:
continue # nobody matched, nothing billed
e.raise_for_status()
d = e.json()["data"]["contact_data"]
emails = d.get("contact_emails") or []
phones = d.get("contact_phones") or []
body = {}
if emails:
body["emails"] = [{"value": emails[0]["email"], "primary": True, "label": "work"}]
if phones:
body["phones"] = [{"value": phones[0]["phone"], "primary": True, "label": "work"}]
if body:
requests.patch(f"{PD}/persons/{p['id']}", headers=pd, json=body).raise_for_status()
cursor = (page.get("additional_data") or {}).get("next_cursor")
if not cursor:
breakTest the LeadOcean key first with one call:
curl -X POST https://api.leadocean.io/v1/people/enrich \
-H "x-api-key: $LEADOCEAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"linkedin_url": "https://www.linkedin.com/in/jane-doe", "reveal_email": true}'Send exactly one identifier per call: linkedin_url, email, phone or person_id. reveal_email: true costs no extra record. Pipedrive v2 returns the next page marker as additional_data.next_cursor, and it is null on the last page (Pipedrive pagination, October 2026).
Field mapping
The enrich response has success, data and meta. Emails and phones are arrays, so the script takes the first item.
| LeadOcean field | Pipedrive field | Note |
|---|---|---|
data.contact_data.contact_emails[0].email | emails[0].value | Only returned with reveal_email: true. |
data.contact_data.contact_emails[0].status | A custom text field | Email status has 13 values. Mail only verified and catch_all_valid. |
data.contact_data.contact_phones[0].phone | phones[0].value | Pipedrive stores phones as an array with a primary flag. |
data.contact_data.contact_current_experiences[0].job_title | job_title | Only exists if contact sync is on. Without it Pipedrive answers 403. Not in the script. |
linkedin_url (input) | Custom text field | The lookup key. Dedupe is the person id. |
Cost at 10,000 rows
Enriching 10,000 Pipedrive people costs LeadOcean one Solo month at $97, and about 50,200 Pipedrive API tokens. The Free plan covers the first 1,000 records, once.
| Item | Free | Solo ($97 a month) |
|---|---|---|
| LeadOcean records | 1,000 for life | Fair-use pace: 10k enrichments every 5 hours, 50k a week |
| Rate limit | 5 requests a second | 50 requests a second |
| 10,000 rows | Covers the first 1,000 | One run fits the pace |
Rows with no match are 404s and cost nothing, so 10,000 is a ceiling. Paid plans have no credits and no overage invoice. Agency at $497 a month suits teams that enrich across many sellers.
Pipedrive meters API calls in tokens (Source: Pipedrive rate limiting, September 2026). The daily budget is 30,000 base tokens times a plan multiplier times seats: Lite 1, Growth 2, Premium 5, Ultimate 7. When it runs out, calls return 429 until midnight server time.
The reference lists the v2 list call at 10 tokens and update-person at 5 (Pipedrive Persons API, October 2026). Reading 10,000 people at 500 a page is 20 calls, 200 tokens. Patching them is 50,000. One Lite seat has 30,000 a day, so the run takes two nights. A Growth seat (60,000) does it in one.
Seats are the larger bill, and this job does not add any. It runs as one dedicated user.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| 429 from Pipedrive | The daily token budget is spent. | Wait for the midnight reset, or run fewer patches a night. |
| 401 from Pipedrive | Wrong or rotated API token. | Copy the current token. A new one kills the old. |
403 on job_title | Contact sync is off. | Turn on contact sync or drop that field. |
| 400 from LeadOcean | Two identifiers or none in the body. | Send only linkedin_url. |
| 404 from LeadOcean | LeadOcean holds nothing for that person. | Skip the row. It was not billed. |
| 429 from LeadOcean | You went over your plan's request rate. | Back off for the Retry-After seconds. |
| Custom field comes back empty | Wrong key, or the field is not in custom_fields. | Pass the 40-character key, not the field name. |
FAQ
Does LeadOcean have a Pipedrive app?
No. LeadOcean is a REST API with an x-api-key header. There is no listing in Pipedrive's marketplace, so you call it from a script or a scheduler that can reach both APIs.
Can I enrich by email instead of LinkedIn URL?
Yes. Send email instead of linkedin_url. Read the email from the person's emails array and use one identifier per call.
Will it create duplicate people?
No. The job only patches people by id. If you add new people from LeadOcean search, check first with Pipedrive's person search and exact_match on the email. That call costs 20 tokens.
How do I find people to add, not just fill in?
Size the list first with the free company count (GET /v1/companies/count), then search. A search page of 25 is 25 records, so use the count to see what a list costs before you pull it. Create the people in Pipedrive only after the exact-match check above, so the job stays free of duplicates. The pricing page shows the pace on each plan.
Fill your Pipedrive gaps with 1,000 free records
Free to start. No credit card. 1,000 records to spend whenever you like.
Get your free API key →