All articlesAPI engineering

Migrating from Proxycurl to EnvoAPI, endpoint by endpoint

A working migration guide: how the URL-keyed calls map, what the response envelope changes, the adapter that keeps your internal contracts intact, and how to cut over without a live provider to compare against.

Migration diagram: profile, company, and job data moving from a Proxycurl panel marked with a warning to an EnvoAPI panel marked with checks.

LinkedIn sued Proxycurl in January 2025 over scraping and a network of fake accounts. Proxycurl shut down in July 2025, and the case was resolved with a permanent injunction requiring it to stop accessing LinkedIn and delete the data it had collected. Any pipeline still calling those endpoints is already failing. This guide maps the calls one by one, shows what actually changes in the response, and gives a cutover plan that works now that there is no live provider left to compare against.

What actually changes

Most of the migration is mechanical. Proxycurl was keyed on LinkedIn URLs and returned the record at the top level under snake_case field names; EnvoAPI takes the same URL on its detail endpoints, wraps every response in one envelope, and names fields in camelCase. Nothing about your downstream data model has to change if you put an adapter in the right place.

Contract differences
AspectProxycurlEnvoAPI
AuthenticationAuthorization: Bearer <key>X-API-Key: <key>
Primary key for lookupsFull LinkedIn URLFull URL on /detail endpoints, or the public slug on the path
Response shapeRecord at the top levelsuccess, data, pagination, meta envelope
Field namingsnake_casecamelCase
ErrorsProvider-specific bodiesmeta.error with statusCode, type, and detail
Rate limitingCredit model with monthly minimums429 with type: rate_limited, never queued

Endpoint mapping

The /detail endpoints exist precisely for this shape of call: they take the same url parameter your Proxycurl code already passes, so the majority of call sites change only a base URL, a header, and a path.

Proxycurl calls and their EnvoAPI equivalents
Proxycurl callEnvoAPI endpointNote
Person profile by URLGET /api/v2/profiles/detail?url=Same input, same idea
Person profile by slugGET /api/v2/profiles/{public_identifier}Slug only — do not pass a full URL here
Company profile by URLGET /api/v2/companies/detail?url=Company overview, industry, headcount
Company lookup by domainGET /api/v2/companies/by-domain?domain=Paginated with offset and limit
Employee listingGET /api/v2/companies/{universal_name}/employeesSlug from the company record
Job detailGET /api/v2/jobs/detail?url=Posting metadata and company linkage
Person searchGET /api/v2/search/peopleFilters come from /api/v2/search/filters
Job searchGET /api/v2/jobs/searchFilters come from /api/v2/jobs/search/filters

The full surface is 47 documented endpoints across profiles, companies, search, jobs, and posts. If a Proxycurl call has no single equivalent, it usually composes from two — a company lookup followed by an employee listing, for example.

Changing the request

Authentication moves from a bearer token to the X-API-Key header. Keep the key in the environment, on the server, and out of anything that reaches a browser bundle.

The same lookup, before and afterpython
import osimport requests# Before — Proxycurl, keyed on the profile URLrequests.get(    "https://nubela.co/proxycurl/api/v2/linkedin",    headers={"Authorization": f"Bearer {os.environ['PROXYCURL_KEY']}"},    params={"url": profile_url},    timeout=20,)# After — EnvoAPI, same input, different header and pathrequests.get(    "https://api.envoapi.com/api/v2/profiles/detail",    headers={"X-API-Key": os.environ["ENVO_API_KEY"]},    params={"url": profile_url},    timeout=20,)

Unwrapping the envelope and keeping your contracts

This is where migrations actually go wrong — not on paths, but on the shape underneath them. Every EnvoAPI response carries success, data, pagination, and meta, so the record your code wants is one level down, and failures arrive as meta.error rather than as an exception from the HTTP client. One adapter function at the edge of your system keeps that detail out of every service that consumes it.

The profile fields most migrations touch
Proxycurl fieldEnvoAPI field
first_namefirstName
last_namelastName
full_namefullName
headlineheadline
public_identifierpublicIdentifier
follower_countfollowerCount
experiences[].companycurrentPositions[].companyName
experiences[].titlecurrentPositions[].title
Adapter — envelope out, your own shape inpython
class EnrichmentError(Exception):    passdef to_internal_profile(payload: dict) -> dict:    """Unwrap one EnvoAPI response into the shape our services expect."""    if not payload.get("success"):        error = (payload.get("meta") or {}).get("error", {})        raise EnrichmentError(error.get("type", "unknown"), error.get("detail"))    data = payload["data"]    current = (data.get("currentPositions") or [None])[0] or {}    return {        "first_name": data.get("firstName"),        "last_name": data.get("lastName"),        "headline": data.get("headline"),        "public_identifier": data.get("publicIdentifier"),        "current_company": current.get("companyName"),        "current_title": current.get("title"),    }

A missing field is a state, not a failure. Public data is uneven: treat a null headline or an empty position list as a value your workflow handles, the way it already handles an unresolved record.

Errors and rate limits

Rate limits return 429 with type: rate_limited in meta.error — requests are rejected rather than queued, so the client owns the backoff. Bounded exponential backoff over a few attempts is enough; retry loops without a ceiling turn one slow minute into an outage. The rate limits and errors pages document the response bodies and every error type.

Rolling out without a live provider to compare against

Standard migration advice says run both providers in parallel and diff the results. That is not available here: Proxycurl has been shut down since July 2025, so there is nothing live to compare against. Use what you already stored instead.

  • Audit the call sites first — endpoint, monthly volume, and the fields your business logic actually reads. Fields nobody reads do not need mapping.
  • Take a sample of records your pipeline enriched through Proxycurl and re-enrich them through EnvoAPI, then diff coverage field by field. Your own historical output is the baseline the dead provider can no longer give you.
  • Test the whole thing on the free plan first — it includes 100 credits, which is enough to check coverage on a real sample before any commitment.
  • Put the new client behind a feature flag and move traffic in stages, highest-value flows first, so a coverage surprise affects one workflow rather than all of them.
  • Watch error rates, latency, and enrichment coverage for a few days per stage, then remove the Proxycurl code paths once the last flag is on.

Prioritise by blast radius: real-time enrichment on a user-facing screen before nightly exports, and anything feeding a customer-visible number before internal dashboards.

What to read while you migrate

  • Authentication — where the key goes and how to keep it server-side.
  • Rate limits — the 429 body and a bounded backoff example.
  • Errors — every error type and what to do about it.
  • Pagination — cursors and offsets for list endpoints such as employee listings and search.
  • Contact the team if a Proxycurl call in your inventory has no obvious equivalent — the composition is usually two endpoints, and it is worth confirming before you build around a gap.

FAQs about the migration

Is EnvoAPI a drop-in replacement for Proxycurl?

Close, but not byte-for-byte. The URL-keyed detail endpoints take the same input your existing code passes, so the request change is small. The response differs in two ways that matter: it arrives inside a success/data/pagination/meta envelope, and fields are camelCase rather than snake_case. An adapter absorbs both in one place, and you can drop it later if you decide to adopt the native schema throughout.

How long does the migration take?

It scales with the number of call sites and downstream consumers, not with the API. The adapter and the request changes are usually a short job; the long pole is verifying coverage on your own records and staging the rollout behind flags. A single service with a handful of endpoints is quick. A dozen services that each parse Proxycurl-shaped records directly will spend most of the time on the parsing, which is the argument for adding the adapter first.

Can I run Proxycurl and EnvoAPI side by side to compare?

No — Proxycurl stopped serving traffic in July 2025, so there is no live provider to diff against. Compare against the records you already enriched and stored instead: re-enrich a representative sample and check coverage per field. If you are moving from a provider that is still operating, a parallel run is still the better test.

What if a Proxycurl endpoint has no exact EnvoAPI equivalent?

Compose it. A company lookup followed by an employee listing covers most people-at-company workflows, and job detail plus job search covers most feed-shaped ones. Where a composition changes the shape, absorb the difference in the adapter so your internal contract is unchanged. If a gap blocks a critical flow, send the details through the contact form rather than designing around it blind.

Start building

Put this into practice with one API key.

Connect profile, company, job, and search data to your product, CRM, or data warehouse through one documented API with clean JSON responses.

100 free credits · No credit card required · One header to set up