You can keep SugarCRM contacts enriched without a plugin by chaining five tools: the Sugar REST API, a free list sizer, an enrichment API, a CSV export for backfills, and cron. The script below is about 40 lines.
This guide uses LeadOcean for the data. Sugar does not need a marketplace app, because its REST API reads and writes any module.
TL;DR
- It takes about 45 minutes: 15 to size and authenticate, 20 to run the script on ten contacts, 10 to schedule it.
- The test run spends up to 10 LeadOcean records. A contact we hold nothing for is a 404 and costs 0.
- Sizing, filter lookups and company counts are free, so check the audience before you spend anything.
Prerequisites
- A SugarCRM instance you can log in to, and a user allowed to edit Contacts. The Sugar docs list REST versions per release (v11.24 on 14.0.x, September 2026).
- A LeadOcean key in
$LEADOCEAN_API_KEY. The free plan has 1,000 records for life, no card, 5 requests a second. - Sugar credentials in
$SUGAR_URL,$SUGAR_USERand$SUGAR_PASS. Use a dedicated API user. - Python 3 with
requests, andcurl.
The five tools
| # | Tool | Job in this recipe |
|---|---|---|
| 1 | Sugar REST API | Read contacts, write enriched fields back |
| 2 | LeadOcean count tools | Size the audience for free |
| 3 | LeadOcean enrich API | Find email status and phones by email |
| 4 | LeadOcean Exports | Backfill a whole segment as a CSV |
| 5 | cron | Run the script every week |
Step 1: Size what you are about to enrich (tool 2)
Count before you pull. Counting is free however often you run it. This leadocean_count_companies call, run through the free MCP tool on 2026-10-21, asks how many US companies have SugarCRM detected on their site.
{"technologies": ["SugarCRM"], "hqCountry": ["US"]}Use the same filters to build a net-new list, not only to clean the contacts you hold. Company counts are exact. GET /v1/companies/count does the same over REST.
Step 2: Authenticate to Sugar (tool 1)
Sugar uses two-legged OAuth2. You POST to /rest/<version>/oauth2/token and keep the access_token and refresh_token (Sugar developer guide, September 2026). Use the platform custom so your script does not log out a browser session.
curl -s -X POST "$SUGAR_URL/rest/v11_24/oauth2/token" \
-H "Content-Type: application/json" \
-d '{"grant_type":"password","client_id":"sugar","client_secret":"",
"username":"'"$SUGAR_USER"'","password":"'"$SUGAR_PASS"'","platform":"custom"}'Sugar's docs say custom must be registered through the Platform extension or the Administration panel first. Change v11_24 to the version for your release.
Step 3: Read contacts that need data (tool 1)
GET /<module> lists records with a filter, max_num (default 20) and offset, and a fields list (Sugar docs, September 2026). Pull ten contacts that have no work phone.
curl -s -G "$SUGAR_URL/rest/v11_24/Contacts" \
-H "OAuth-Token: $SUGAR_TOKEN" \
--data-urlencode 'filter=[{"phone_work":{"$empty":""}}]' \
--data-urlencode "fields=id,first_name,last_name,email1,phone_work" \
--data-urlencode "max_num=10"Sugar sends the token in the OAuth-Token header (Sugar CRUD cookbook, October 2026). Contacts carry phone_work (Sugar recent records example, October 2026). Custom fields differ per instance, so check /rest/v11_24/help on yours.
Step 4: Enrich each contact by email (tool 3)
POST /v1/people/enrich takes exactly one of linkedin_url, email, phone or person_id. Send reveal_email: true to get addresses and numbers back. The script below ties steps 3 and 4 together and writes back with PUT /<module>/:record (Sugar docs, September 2026).
import os, time, requests
SUGAR = os.environ["SUGAR_URL"] + "/rest/v11_24"
LO = "https://api.leadocean.io"
LH = {"x-api-key": os.environ["LEADOCEAN_API_KEY"], "Content-Type": "application/json"}
tok = requests.post(SUGAR + "/oauth2/token", json={
"grant_type": "password", "client_id": "sugar", "client_secret": "",
"username": os.environ["SUGAR_USER"], "password": os.environ["SUGAR_PASS"],
"platform": "custom"}).json()["access_token"]
SH = {"OAuth-Token": tok, "Content-Type": "application/json"}
rows = requests.get(SUGAR + "/Contacts", headers=SH, params={
"filter": '[{"phone_work":{"$empty":""}}]',
"fields": "id,email1,description", "max_num": 10}).json()["records"]
for c in rows:
if not c.get("email1"):
continue
for _ in range(5):
r = requests.post(LO + "/v1/people/enrich", headers=LH,
json={"email": c["email1"], "reveal_email": True})
if r.status_code != 429:
break
time.sleep(int(r.headers.get("Retry-After", "1")))
if r.status_code == 404: # nobody matched, costs 0
continue
r.raise_for_status()
cd = r.json()["data"]["contact_data"]
phones = [p["phone"] for p in cd.get("contact_phones", [])]
patch = {}
if not c.get("description"): # never overwrite a rep's notes
patch["description"] = "LeadOcean email_status: " + str(cd.get("email_status"))
if phones:
patch["phone_work"] = phones[0]
if patch:
requests.put(SUGAR + "/Contacts/" + c["id"], headers=SH, json=patch)Run it with python3 enrich_sugar.py. It stops at ten contacts because of max_num.
Step 5: Backfill a whole segment with Exports (tool 4)
Enrichment by email fixes the contacts you hold. To add new ones, use the Exports page in the LeadOcean app: filter builder, column picker, a record price preview and a CSV download. Bulk CSV export is not on Free. POST /v1/exports takes up to 50,000 rows a request and 500,000 a day.
Then create the rows in Sugar with POST /<module>, which takes a name and value list of fields (Sugar docs, September 2026). Check email_status first. LeadOcean gives no refund for bounced emails.
Step 6: Schedule it with cron (tool 5)
Cron does not load your shell profile. Keep the five variables as export lines in ~/.sugar.env and run chmod 600 on it.
0 6 * * 1 cd ~/sugar-enrich && . ~/.sugar.env && python3 enrich_sugar.py >> run.log 2>&1Raise max_num once ten contacts look right. Sugar's cap is 20 by default per call, so page with offset for more.
What you get
The size check from step 1 ran on 2026-10-21 through leadocean_count_companies. It spent 0 records.
{"total":1106,"totalIsExact":true,
"facets":{"hq_state":[{"value":"California","count":262},{"value":"New York","count":113},{"value":"Texas","count":74}]}}A people count on 2026-10-21 for VPs in Sales & Business Development, US, 51-200 employees, also free:
{"total":18567,"totalIsExact":true,"totalAll":60456,"distinctCompanies":12192}The 18,567 counts mailable people only (verified, catch_all_valid or catch_all). Without an email filter the same filters match 60,456.
Each enriched contact returns the shape below. This is the response shape from the API reference, with placeholder values.
{
"success": true,
"data": {
"contact_data": {
"has_email": true,
"has_phone": true,
"email_status": "verified",
"contact_emails": [{ "email": "jane.doe@acme.com", "type": "work", "status": "verified" }],
"contact_phones": [{ "phone": "+14155550133" }]
}
}
}Troubleshooting
| Error | Cause | Fix |
|---|---|---|
| 401 from LeadOcean | The x-api-key header is missing or the key is wrong. | Copy a fresh key from the app and reload $LEADOCEAN_API_KEY. In cron, check the env file path. |
| 429 | More than 5 requests a second on Free, or 200 on Agency. | Wait Retry-After seconds and retry. The script already does. |
| 402 | Records spent. The Free 1,000 are one-off. | Check GET /v1/account, or move to a paid plan on pricing. |
| Empty or zero counts | A filter value is not an exact enum string. | Call leadocean_list_enum_values and pass the exact string. |
| Sugar login fails | The custom platform is not registered. | Register it in the Platform extension or the Administration panel. |
| Export refused | The ceiling is 50,000 rows a request and 500,000 a day. | Split the job across requests and days, or narrow the filters. |
FAQ
Does LeadOcean have a SugarCRM app?
No. LeadOcean has no CRM connector and no Chrome extension. You call the API from a script, as above.
Can I do this with Zapier or n8n?
Yes, through a plain HTTP step. Our Zapier recipe shows the webhook call. Both tools need to reach Sugar's REST API too.
How many records does a run spend?
One per contact that matches. A miss is a 404 and costs 0. On paid plans there is no per-record price, only a fair-use pace.
How fresh is the data?
The dataset is refreshed monthly. Each record also carries its own fetched_at date.
Where do I start if I am new to this?
Read what is data enrichment and the Claude enrichment guide. For the wider picture, see B2B lead enrichment to data collection.
Enrich your SugarCRM contacts for free
Free to start. No credit card. 1,000 records to spend whenever you like.
Get your free API key →