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

How-to guide

How to Enrich Nimble CRM Contacts With Social and Firmographic Data (Nimble API and LeadOcean)

Find Nimble people with a LinkedIn URL, pull their work email, phone and company data, write it back. About 40 minutes, 1,000 records free.

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

You can fill social and company data on Nimble contacts with one Node script. It reads people from Nimble's REST API, calls LeadOcean once per person and once per company, and writes the result back. No automation vendor sits in the middle.

Searches for Nimble enrichment tools return roundups of nine products. This is the version a technical buyer can bookmark: one recipe, real requests, real output. For the tool comparison, read best CRM data enrichment tools.

TL;DR

  • Time: about 40 minutes to build and test. A 50-contact run takes under a minute.
  • Cost: 1 LeadOcean record per person found and 1 per company looked up. A person or company we hold nothing for is a 404 and costs nothing. Free gives 1,000 records for life, no card.
  • Limit to watch: LeadOcean allows 5 requests a second on Free and 200 on Agency. The script calls one person at a time, so it stays under both.

Prerequisites

  • A Nimble account and an API access token. Nimble uses OAuth 2.0 bearer tokens, sent as Authorization: Bearer <token> (Nimble auth docs, October 2026). Export it as $NIMBLE_TOKEN.
  • A LeadOcean key from app.leadocean.io/register. Export it as $LEADOCEAN_API_KEY.
  • Nimble person contacts that carry a LinkedIn URL.
  • Node 18 or later, for the built-in fetch.

Step 1: List the Nimble people you want to fix

Nimble lists contacts at GET https://api.nimble.com/api/v1/contacts/. The list takes record_type (person, company or all), per_page (default 30), page and a fields list (Nimble contacts list docs, September 2026). Sorting only works with per_page at 100 or less.

bash
curl -G "https://api.nimble.com/api/v1/contacts/" \
  -H "Authorization: Bearer $NIMBLE_TOKEN" \
  --data-urlencode "record_type=person" \
  --data-urlencode "per_page=50" \
  --data-urlencode "fields=first name,last name,email,phone"

Each contact comes back with a fields object. Every field is a list of {value, modifier} pairs (Nimble update docs, September 2026).

Step 2: Test the LeadOcean key

Enrich one person by LinkedIn URL. Send exactly one identifier per call. Add reveal_email to get the addresses and numbers.

bash
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-example", "reveal_email": true}'

A 200 returns the person and spends one record. A 404 means we hold nothing for that person and costs nothing. A 401 means the key is wrong.

Step 3: Add the company data

The person response carries the current employer in contact_current_experiences. Take its company_domain and call company enrich for the firmographics.

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

The response holds company_data, company_metrics and company_detected_technologies. Look up each domain once and cache it, because ten Nimble contacts at one company should cost one company record, not ten.

Step 4: Write it back to Nimble

Nimble updates a contact with PUT /api/v1/contact/<id>. The body is {"fields": {...}}. By default a value only replaces the field entry with the same modifier. Add ?replace=1 to replace every entry of that field. Passing null as a value removes it (Nimble update docs, September 2026).

Leave replace off. You then add a work email next to the personal one instead of deleting it.

Here is what each LeadOcean field maps to in Nimble. Pick the Nimble fields you want filled before you run it on the whole account.

LeadOcean fieldKindNimble target
contact_data.contact_emails[0].emailContactemail with modifier work
contact_data.contact_phones[0].phoneContactphone
profile_data.profile_headlineSocialA custom field such as "headline"
contact_data.contact_current_experiences[0]FirmographicJob title and company name
company_dataFirmographicCustom fields for size, industry and location
company_detected_technologiesFirmographicA custom tag or note

Social data here means the LinkedIn headline and profile you already match on, plus current role. Firmographic data means the company facts: size, industry, location and detected technologies. Both come from the same two calls.

Step 5: The full script

Nimble's default linkedin field is a social field that holds the profile URL (Nimble default fields, October 2026). The script reads it.

This script enriches up to LIMIT people, caches companies by domain, and writes the work email, phone and a company line into Nimble. Save it as enrich-nimble.mjs.

javascript
const NIMBLE = "https://api.nimble.com/api/v1";
const LO = "https://api.leadocean.io";
const LIMIT = 50;
// Name of the Nimble field that holds the LinkedIn URL.
const LINKEDIN_FIELD = "linkedin"; // Nimble default social field, value is the profile URL

const nimble = (path, init = {}) =>
  fetch(NIMBLE + path, {
    ...init,
    headers: {
      Authorization: `Bearer ${process.env.NIMBLE_TOKEN}`,
      "content-type": "application/json",
    },
  });

async function lo(path, body) {
  for (;;) {
    const r = await fetch(LO + path, {
      method: "POST",
      headers: { "x-api-key": process.env.LEADOCEAN_API_KEY, "content-type": "application/json" },
      body: JSON.stringify(body),
    });
    if (r.status === 429) {
      await new Promise((s) => setTimeout(s, Number(r.headers.get("retry-after") ?? 1) * 1000));
      continue;
    }
    if (r.status === 404) return null; // nobody matched, no charge
    if (!r.ok) throw new Error(`LeadOcean ${r.status}`);
    return (await r.json()).data;
  }
}

const list = await (await nimble(`/contacts/?record_type=person&per_page=${LIMIT}`)).json();
const companies = new Map();

for (const c of list.resources) {
  const url = c.fields?.[LINKEDIN_FIELD]?.[0]?.value;
  if (!url) continue;

  const p = await lo("/v1/people/enrich", { linkedin_url: url, reveal_email: true });
  if (!p) { console.log(c.id, "skipped"); continue; }

  const email = p.contact_data?.contact_emails?.[0];
  const phone = p.contact_data?.contact_phones?.[0];
  const domain = p.contact_data?.contact_current_experiences?.[0]?.company_domain;

  if (domain && !companies.has(domain)) {
    companies.set(domain, await lo("/v1/companies/enrich", { domain }));
  }
  const co = companies.get(domain)?.company_data;

  const fields = {};
  if (email?.email) fields.email = [{ value: email.email, modifier: "work" }];
  if (phone?.phone) fields.phone = [{ value: phone.phone, modifier: "work" }];
  if (co) fields["description"] = [{ value: JSON.stringify(co).slice(0, 500), modifier: "" }];

  const r = await nimble(`/contact/${c.id}`, { method: "PUT", body: JSON.stringify({ fields }) });
  console.log(c.id, r.status, email?.status ?? "no email");
}

Two cautions. The company line is a raw JSON slice so you can see which keys exist before you map them to custom fields. Nimble lets you create custom fields through its metadata API (Nimble contacts docs, September 2026), so map firmographic keys to real fields once you have chosen them. And contact_emails[0] is the best address, so read its status before anyone sends to it.

Step 6: Size a list of new contacts

Enrichment fixes contacts you already hold. To add new companies, size them first. GET /v1/companies/count and the MCP tool leadocean_count_companies are free and return an exact total with breakdowns. On 21 October 2026 this count ran against the live index:

text
industry: Software Development
hqCountry: US

total: 201,057 companies (exact)
employee_range: 1-10: 148,099 | 11-50: 38,048 | 51-200: 9,261 | 201-500: 2,316
hq_state: California 44,279 | Texas 15,040 | New York 14,458
credits: 0

This counts companies, not mailable people. To load people into Nimble, export a CSV and use Nimble's own import. Use the Exports page in the LeadOcean app: a filter builder, a column picker, a price preview and a download button. Or call POST /v1/exports, which takes up to 50,000 rows a request and 500,000 a day. Bulk CSV export is not on Free.

Step 7: Check before you widen the run

Run 50 contacts, then open them in Nimble. Three checks catch most problems.

First, confirm the email landed on the right contact. A stale LinkedIn URL can point at someone who left. Second, count the skipped rows. A high rate means the URLs in Nimble are old. Third, read email_status on the sample. Only verified and catch_all_valid are safe to send to without a second check.

Then raise LIMIT. Each run is repeatable, but it re-spends a record on people you already filled, so add a custom Nimble field such as "enriched" and skip contacts that have it.

What you get

A run on three placeholder contacts prints one line each. Your IDs differ:

text
4f69fb852ab3740c5e000004 200 verified
5e69fb852ab3f40d5e050017 200 catch_all_valid
6a69fb852ab3f40d5e050099 skipped

The script reads this LeadOcean person response. It is the response shape from the API reference, with placeholder values:

json
{
  "success": true,
  "data": {
    "profile_data": { "profile_full_name": "Jane Doe", "profile_headline": "VP Sales at Acme" },
    "contact_data": {
      "has_email": true,
      "has_phone": true,
      "email_status": "verified",
      "contact_emails": [{ "email": "jane@acme.com", "type": "work", "status": "verified" }],
      "contact_phones": [{ "phone": "+14155550133", "type": "mobile" }],
      "contact_current_experiences": [{ "job_title": "VP Sales", "company_name": "Acme", "company_domain": "acme.com" }]
    }
  }
}

Since 17 October 2026 the same call also returns career history in resume_data: past roles, education and certifications. Past employers carry no company_id yet.

Troubleshooting

ErrorCauseFix
401 from LeadOceanKey missing or wrongCheck $LEADOCEAN_API_KEY and the x-api-key header name.
429 from LeadOceanOver the plan's rate limit, 5 requests a second on Free, 200 on AgencyProcess contacts one at a time. Honour Retry-After.
402 from LeadOceanThe 1,000 free records are spentMove to Solo at $97 a month. See pricing.
404 from LeadOceanNo record for that person or companyExpected. No charge. The script skips it.
Empty results from a LeadOcean searchA filter value is not an exact enum string, such as vp for jobLevelResolve values with the free leadocean_list_enum_values tool or GET /v1/enums.
Validation error from NimbleA parameter value Nimble does not recognise (Nimble list docs, September 2026)Unknown parameters are ignored but unknown values return an error. Check record_type and fields.

Run it on a schedule

A cron line on any machine with Node is enough. Hourly suits contacts that arrive through forms or imports.

bash
0 * * * * NIMBLE_TOKEN=... LEADOCEAN_API_KEY=... node /opt/enrich/enrich-nimble.mjs >> /var/log/enrich.log 2>&1

Keep the keys in a secrets store, not in the crontab. A contact LeadOcean cannot match is retried each run as a free 404, so the cost of a miss is a few milliseconds.

FAQ

Does LeadOcean have a native Nimble integration?

No. It is a REST API with a hosted MCP server. You call it from a script or from an automation tool that can make HTTP requests. The same requests work in Make, Zapier and n8n.

Does this replace Nimble's own social features?

No. Nimble keeps the contact record and its CRM workflow. LeadOcean only supplies data. LeadOcean has no rep workspace, sequencer, dialer, CRM or Chrome extension.

Can I enrich by email instead of LinkedIn URL?

Yes. Send email in place of linkedin_url. Send exactly one identifier per call. There is no name plus domain lookup: search by domain first, then enrich by person_id.

How many records does a 1,000-contact account cost?

At most 2,000 in the worst case, one per person and one per distinct company. Misses are free, and cached companies are looked up once. The Free plan covers 1,000 records for life, then you need a paid plan from $97 a month. See what CRM enrichment adds to your sales data and fixing a messy CRM.

Fill the social and company fields in your Nimble contacts with a free LeadOcean key.

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

Get your free API key →