// Migration guide

Migrate from Proxycurl to EnrichGrid

Proxycurl shut down in July 2025. This guide maps its endpoints and fields to EnrichGrid, with a Python adapter that returns the Proxycurl-style keys your code already reads, a Node.js example, and the differences you need to handle.

Endpoint mapPerson, company, school and search.
AdapterKeep your Proxycurl field names.
Python & Node.jsCopy-paste code.
Honest gapsWhat doesn't carry over.

What changes when you switch

  • Requests are POST with a JSON body {"identifier": "…"} (a LinkedIn URL, slug or ID) instead of GET with query parameters.
  • Auth is the header x-api-key: YOUR_KEY instead of Authorization: Bearer.
  • The record is wrapped: {"found": true, "profile": {…}} (companies use "company", schools "school"). A profile that doesn't exist returns 200 with "found": false and costs nothing.
  • Slow profiles return 202 with "status": "pending" and a poll URL. Poll until the status is done or failed; polling is free.
  • Dates are ISO strings ("2015-05-01") instead of {day, month, year} objects, and the country is an ISO code ("US").
  • Billing: 1 credit per found profile, company or school, and 1 credit per page of up to 10 search results. Not-found lookups and empty pages are free. Records are kept for up to 30 days, so a repeat lookup is instant (still 1 credit); "live": true collects fresh at the same price.

Endpoint mapping

What you usedProxycurlEnrichGrid
Person profileGET /proxycurl/api/v2/linkedin?url=…POST /v1/enrich/person
Company profileGET /proxycurl/api/linkedin/company?url=…POST /v1/enrich/company
School profileGET /proxycurl/api/linkedin/school?url=…POST /v1/enrich/school
Person searchGET /proxycurl/api/v2/search/person?…POST /v1/search
Slow results–GET /v1/lookups/{id} (free)
AuthAuthorization: Bearer …x-api-key: …

Person field mapping

ProxycurlEnrichGrid (profile.…)
public_identifierslug
first_name, last_name, full_namefirst_name, last_name (join them for the full name)
headline, summaryheadline, summary
occupationcurrent_title + current_company_name
country, citylocation_country (ISO code), location (free text)
profile_pic_urlphoto_url
connections, follower_countnum_connections, follower_count
experiences[]sections.experiences[]: title, company_name, started_on, ended_on, is_current, plus entreprise with the employer's firmographics
education[]sections.educations[]: school_name, degree_name, field_of_study, started_on, ended_on
skills[]sections.skills[].name (with endorsement_count)
certifications[]certifications[]
(not in Proxycurl)seniority, job_function, current_companies[]

Company field mapping

ProxycurlEnrichGrid (company.…)
name, description, taglinename, description, tagline
websitewebsite_url
industryindustry
company_size [min, max]staff_count_range (e.g. "201-500")
company_size_on_linkedinstaff_count
hq {city, state, country}hq_city, hq_region, country_code
founded_year, follower_countfounded_year, follower_count
specialitiesspecialties
locations[]locations[]

Python: keep reading Proxycurl-style keys

If your code reads Proxycurl fields in many places, convert the EnrichGrid response once, at the edge, and leave the rest of your code alone.

Ready-made adapter: download enrichgrid_proxycurl.py (one file, MIT, needs only requests) or install it from GitHub with pip install "git+https://github.com/enrichgrid/proxycurl-adapter". It handles polling and retries, and client.person(url) / client.company(url) return Proxycurl-style dicts, or None when nothing is found (no charge). It's tested on synthetic fixtures and on the sample responses in our API docs. The same mapping, inline:

import os, time, requests

API = "https://enrichgrid.com/v1"
H = {"x-api-key": os.environ["ENRICHGRID_KEY"]}

def enrich(kind, identifier, max_wait=300):
    """kind: "person", "company" or "school". Returns the finished lookup."""
    r = requests.post(f"{API}/enrich/{kind}", headers=H, json={"identifier": identifier}, timeout=120)
    r.raise_for_status()  # 401 bad key, 402 no credits, 429 slow down (see Retry-After)
    data = r.json()
    poll, deadline = data.get("poll"), time.time() + max_wait
    while data.get("status") == "pending" and time.time() < deadline:  # 202: slow profile, polling is free
        time.sleep(3)
        data = requests.get("https://enrichgrid.com" + poll, headers=H, timeout=30).json()
    return data

def _date(iso):
    # "2015-05-01" -> {"day": 1, "month": 5, "year": 2015}
    # When only the year is known the date is YYYY-01-01; started_on_display shows the real precision.
    if not iso:
        return None
    year, month, day = (int(x) for x in iso.split("-"))
    return {"day": day, "month": month, "year": year}

def to_proxycurl_person(p):
    """EnrichGrid profile -> the Proxycurl person fields most integrations read."""
    sections = p.get("sections") or {}
    return {
        "public_identifier": p.get("slug"),
        "first_name": p.get("first_name"),
        "last_name": p.get("last_name"),
        "full_name": " ".join(x for x in (p.get("first_name"), p.get("last_name")) if x),
        "headline": p.get("headline"),
        "summary": p.get("summary"),
        "occupation": " at ".join(x for x in (p.get("current_title"), p.get("current_company_name")) if x),
        "country": p.get("location_country"),  # ISO code, e.g. "US"
        "city": p.get("location"),             # free text, e.g. "San Francisco Bay Area"
        "profile_pic_url": p.get("photo_url"),
        "connections": p.get("num_connections"),
        "follower_count": p.get("follower_count"),
        "experiences": [{
            "title": e.get("title"),
            "company": e.get("company_name"),
            "company_linkedin_profile_url": (
                f"https://www.linkedin.com/company/{e['entreprise']['slug']}/"
                if (e.get("entreprise") or {}).get("slug") else None),
            "description": e.get("description"),
            "location": e.get("location"),
            "starts_at": _date(e.get("started_on")),
            "ends_at": _date(e.get("ended_on")),
        } for e in sections.get("experiences") or []],
        "education": [{
            "school": ed.get("school_name"),
            "degree_name": ed.get("degree_name"),
            "field_of_study": ed.get("field_of_study"),
            "starts_at": _date(ed.get("started_on")),
            "ends_at": _date(ed.get("ended_on")),
        } for ed in sections.get("educations") or []],
        "skills": [s.get("name") for s in sections.get("skills") or []],
        "certifications": p.get("certifications") or [],
    }

def to_proxycurl_company(c):
    """EnrichGrid company -> the Proxycurl company fields most integrations read."""
    low, _, high = (c.get("staff_count_range") or "").replace("+", "-").partition("-")
    return {
        "name": c.get("name"),
        "description": c.get("description"),
        "website": c.get("website_url"),
        "industry": c.get("industry"),
        "company_size": [int(low) if low.isdigit() else None, int(high) if high.isdigit() else None],
        "company_size_on_linkedin": c.get("staff_count"),
        "founded_year": c.get("founded_year"),
        "specialities": c.get("specialties") or [],
        "follower_count": c.get("follower_count"),
        "tagline": c.get("tagline"),
        "hq": {"city": c.get("hq_city"), "state": c.get("hq_region"), "country": c.get("country_code")},
    }

data = enrich("person", "https://www.linkedin.com/in/brendaneich")
if data.get("found"):
    person = to_proxycurl_person(data["profile"])
    print(person["full_name"], "-", person["occupation"])

Node.js

// Node.js 18+ (built-in fetch), saved as an ES module, e.g. enrich.mjs
const API = "https://enrichgrid.com/v1";
const H = { "x-api-key": process.env.ENRICHGRID_KEY, "content-type": "application/json" };
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

async function enrich(kind, identifier, maxWaitMs = 300000) {
  const res = await fetch(`${API}/enrich/${kind}`, { method: "POST", headers: H, body: JSON.stringify({ identifier }) });
  if (!res.ok) throw new Error(`EnrichGrid ${res.status}: ${await res.text()}`);
  let data = await res.json();
  const poll = data.poll;
  const deadline = Date.now() + maxWaitMs;
  while (data.status === "pending" && Date.now() < deadline) {  // 202: slow profile, polling is free
    await sleep(3000);
    data = await (await fetch("https://enrichgrid.com" + poll, { headers: H })).json();
  }
  return data;
}

const data = await enrich("person", "https://www.linkedin.com/in/brendaneich");
if (data.found) console.log(data.profile.first_name, data.profile.last_name, "-", data.profile.current_title);

People search

Proxycurl's person search took query parameters. EnrichGrid takes a filters object and returns up to 10 profile slugs per page, which you then enrich:

requests.post(f"{API}/search", headers=H, json={"filters": {
    "country": ["US"], "title": {"any": ["vp sales"]}, "company": ["microsoft"]}})

Filters: country, city, title, company (current), past_company, company_size, company_industry_id, industry_id, school, career_years, tenure_years. Page with cursor / next_cursor. 1 credit per page with results; empty pages are free. Details in the API reference.

What doesn't carry over

  • Contact data: no personal or work emails, phone numbers, or reverse email lookup.
  • Employee lists: there is no separate endpoint; use people search with a company filter (10 people per page).
  • Job postings: not offered.
  • Response shape: not Proxycurl-compatible. Use the adapter above or map the fields directly.

Test it on your own list

Create a free account (50 credits, no card), run the last 50–100 LinkedIn URLs from your logs through the adapter and compare the fields you use. Not-found lookups are free, so stale URLs cost nothing. For a whole file, upload it to Bulk enrich.

// FAQ

Questions, answered

Is EnrichGrid Proxycurl-compatible?

No. Endpoints, auth and the response shape differ. The inputs are the same (a LinkedIn URL, slug or ID), so most integrations need only the small adapter above or a direct field mapping.

Do I pay for profiles that aren't found?

No. Not-found lookups and empty search pages are free.

How fresh is the data?

Collected live on the first request and kept for up to 30 days. Send "live": true to collect fresh at the same price.

Do you return emails or phone numbers?

No. EnrichGrid returns professional profile and company data only.

Start enriching in the next 60 seconds

50 free credits. No credit card. Pay only for what we find.

Create a free account →
Questions? Ask us 👋