Pre-Black Friday: 20% off, for as long as you stayEARLYBFon Solo and AgencyEnds Nov 28Start free

How-to guide

How to Enrich Copper CRM Contacts (Google Workspace Setup)

Most lists of Copper enrichment tools are six logos. This is one working loop: read from Copper, enrich by email, write back through the Copper API.

Get your free API key →Free to start. No credit card. 1,000 records to spend whenever you like.

TL;DR

  • Search the Copper API for people, enrich each work email with one LeadOcean call, and write the title and phone back with a Copper PUT. No middleware.
  • A first run over 100 contacts takes about 45 minutes and spends at most 100 of your 1,000 free records. A miss is a 404 and costs nothing.
  • You do not need six tools. Copper already syncs Gmail, Calendar and Contacts. You need one data source behind its API.

Search results for "Copper CRM enrichment tools" are mostly six-logo roundups. A technical buyer needs less: which call fills the empty fields, and where do the results land? Copper's own page describes the CRM as built around Gmail, Calendar and Drive (September 2026). That sync fills in names and email threads. It does not fill in job titles or phones, and that gap is what this recipe closes.

LeadOcean has no Copper app. This is a script you run against both APIs.

Prerequisites

  • A LeadOcean API key from app.leadocean.io. The free plan has no card and 1,000 records, one-off. Free keys get the enrich scope.
  • A Copper API key, and the email of the user who owns it. Copper calls these the access token and user email in its request docs (September 2026).
  • Python 3 with requests.
bash
export LEADOCEAN_API_KEY="paste-your-key-here"
export COPPER_TOKEN="paste-your-copper-token-here"
export COPPER_EMAIL="you@acme.com"
pip install requests

Step 1: Pull people from Copper

Copper's people search is a POST with a JSON body. Criteria combine with AND, and a page is 20 records by default, so set page_size (people search docs, September 2026). Copper caps any search at its first 100,000 records.

bash
curl -s -X POST "https://api.copper.com/developer_api/v1/people/search" \
  -H "X-PW-AccessToken: $COPPER_TOKEN" \
  -H "X-PW-Application: developer_api" \
  -H "X-PW-UserEmail: $COPPER_EMAIL" \
  -H "Content-Type: application/json" \
  -d '{"page_size": 100, "page_number": 1, "sort_by": "date_created", "sort_direction": "desc"}'

Copper has no documented filter for an empty job title. So pull the page and filter in code: keep people with a work email and no title.

python
import os, requests

COPPER = {
    "X-PW-AccessToken": os.environ["COPPER_TOKEN"],
    "X-PW-Application": "developer_api",
    "X-PW-UserEmail": os.environ["COPPER_EMAIL"],
    "Content-Type": "application/json",
}

def copper_people(page=1):
    r = requests.post(
        "https://api.copper.com/developer_api/v1/people/search",
        headers=COPPER,
        json={"page_size": 100, "page_number": page},
        timeout=30,
    )
    r.raise_for_status()
    return [p for p in r.json() if p.get("emails") and not p.get("title")]

Step 2: Enrich each email

Enrich takes exactly one of linkedin_url, email, phone or person_id. Send the Copper work email. On an email lookup reveal_email defaults to true, so phones come back at no extra record.

bash
curl -s -X POST "https://api.leadocean.io/v1/people/enrich" \
  -H "x-api-key: $LEADOCEAN_API_KEY" \
  -H "content-type: application/json" \
  -d '{"email": "jane@acme.com"}'

A 404 means we hold nothing for that person. It is not billed. Treat it as a skip.

python
LO = {"x-api-key": os.environ["LEADOCEAN_API_KEY"], "content-type": "application/json"}

def enrich(email):
    r = requests.post("https://api.leadocean.io/v1/people/enrich",
                      headers=LO, json={"email": email}, timeout=30)
    if r.status_code == 404:
        return None
    if r.status_code in (401, 402):
        raise SystemExit(f"stop: {r.status_code} {r.text[:120]}")
    r.raise_for_status()
    return r.json()["data"]["contact_data"]

Step 3: Write back with a Copper PUT

Send only the fields you change. Copper warns that reading a record and sending it whole can wipe connect fields, so never PUT the full object back (update docs, September 2026).

python
def push(person, cd):
    exp = (cd.get("contact_current_experiences") or [{}])[0]
    body = {}
    if exp.get("job_title"):
        body["title"] = exp["job_title"]
    phones = [p["phone"] for p in cd.get("contact_phones") or []]
    if phones and not person.get("phone_numbers"):
        body["phone_numbers"] = [{"number": phones[0], "category": "work"}]
    if not body:
        return False
    r = requests.put(
        f"https://api.copper.com/developer_api/v1/people/{person['id']}",
        headers=COPPER, json=body, timeout=30)
    r.raise_for_status()
    return True

Copper lists title as a standard person field and accepts phone_numbers as a list of number and category pairs in its create docs (September 2026). Check that your account's field layout shows both before a live run.

Step 4: Run it on 20 people first

Do a dry run. Print what would change and write nothing. Then switch writes on.

python
import time

DRY = True
spent = changed = 0
for person in copper_people()[:20]:
    cd = enrich(person["emails"][0]["email"])
    time.sleep(0.4)  # stays far under Copper's 180 requests a minute
    if cd is None:
        continue
    spent += 1
    exp = (cd.get("contact_current_experiences") or [{}])[0]
    print(person["id"], "->", exp.get("job_title"), exp.get("company_name"))
    if not DRY:
        changed += push(person, cd)
print(f"records spent: {spent}  written: {changed}")

Copper limits all calls to 180 a minute, and bulk endpoints to 3 a second (September 2026). The loop above makes two calls per person with a pause, so it stays under both.

Step 5: Find the contacts Copper does not have yet

Enrichment fixes the rows you hold. To add rows, size the list first. The count is free and spends no records.

bash
curl -s "https://api.leadocean.io/v1/people/search?count=true&limit=1" \
  -H "x-api-key: $LEADOCEAN_API_KEY" -H "content-type: application/json" \
  -X POST -d '{"jobLevel": ["VP"], "jobFunction": ["Sales & Business Development"], "country": ["US"]}'

Then search the same body without count, with limit up to 100 and the cursor from meta for the next page. Create each result in Copper with POST /developer_api/v1/people. That endpoint takes name, emails and phone_numbers (September 2026). Paid plans can also use POST /v1/exports for up to 50,000 rows a request and 500,000 a day, then import the CSV into Copper.

What you get

The dry run prints one line per contact, and the final line is your audit trail. This is the format with placeholder values.

text
4210001 -> Head of Sales Acme
4210002 -> VP Marketing Example Co
records spent: 2  written: 0

The enrich call returns this shape. It is the response shape from the API reference, with placeholder values.

json
{
  "success": true,
  "data": {
    "contact_data": {
      "email_status": "verified",
      "contact_phones": [{ "phone": "+1 415 555 0133" }],
      "contact_current_experiences": [
        { "company_name": "Acme", "company_domain": "acme.com", "job_title": "Head of Sales" }
      ]
    }
  }
}

Sizing run on 2026-10-21 with leadocean_count_leads: jobLevel VP, jobFunction Sales & Business Development, country US. It returned 129,494 people. The tool's default counts only verified, catch_all_valid and catch_all addresses, so that is a mailable count. The same filters with no email condition match 350,517. The REST meta.total is capped at 100,000, so the curl in Step 5 shows 100,000 for this segment.

Troubleshooting

ErrorCauseFix
401 from LeadOceanMissing or wrong x-api-key.Re-export $LEADOCEAN_API_KEY from app.leadocean.io.
402Out of records. On Free that is after your 1,000.Read GET /v1/account, then move to Solo at $97 a month.
429 from LeadOceanAbove the plan's rate limit: 5 a second on Free, 200 on Agency.Sleep and retry. A sequential loop will not hit it.
429 from CopperAbove 180 requests a minute, or 3 a second on bulk endpoints.Slow the loop. The pause in Step 4 is enough.
404 on enrichWe hold nothing for that email.Not billed. Skip the row.
Empty search resultsA bad enum value, such as vp for jobLevel.Use exact strings: VP, C-Team. Check leadocean_list_enum_values.

FAQ

Does LeadOcean have a native Copper integration?

No. There is no Copper app, and no Chrome extension. You call both APIs from a script, a scheduled job or a workflow tool such as n8n.

Will this overwrite titles my team typed in?

Not as written. Step 1 keeps only people with no title, and Step 3 sends a phone only when Copper holds none. Widen the filter on purpose, never by accident.

How many records does a run spend?

One per contact we hold. A 100-contact run spends at most 100 records. The dataset refreshes monthly, so re-running the same contact inside 30 days returns the same record and spends another.

What does it cost after the free 1,000?

Solo is $97 a month for one person. Agency is $497 a month for teams, with 200 requests a second. Paid plans have a fair-use pace, not credits. See pricing.

For other CRMs, see the best CRM enrichment tools, Zoho CRM enrichment tools and the CRM enrichment use case.

Fill your Copper contacts with titles and phones

Free to start. No credit card. 1,000 records to spend whenever you like.

Get your free API key →