§ blog · guide

Proxycurl Field Mapping Cheatsheet for 2026

Proxycurl's old field schema still lives in thousands of integrations. Here is the field-by-field mapping to LinkFetch and other 2026 alternatives.

Proxycurl Field Mapping Cheatsheet for 2026

Proxycurl shut down in July 2025 after LinkedIn's federal lawsuit landed. Nine months later, the old schema is still wired into production codebases everywhere. This is the field-by-field translation so you can swap providers without rewriting your data layer. LinkFetch, a compliance-first LinkedIn data API, gets the most coverage here, but the patterns apply to every serious alternative.

Why field mapping is the hidden cost of a Proxycurl migration

Most "Proxycurl alternatives" posts focus on the wrong thing. They rank vendors on coverage and pricing, then hand-wave the integration step with "just swap the endpoint". That is the part that actually breaks. LinkFetch's /v1/profiles returns camelCase keys, nested arrays for employment, and ISO-8601 dates. Proxycurl returned snake_case, flat arrays with denormalised company stubs, and partial dates with null days. A direct find-and-replace will compile and silently corrupt roughly 18% of records on the first run.

The cost is not the API contract. It is the assumptions baked into your ETL: the current_company field your CRM writes to, the experiences[*]. company.name your enrichment scoring reads, the timezone-naive joined_ on date your "tenure" calculation depends on. Every one of those is a rename in your code, not in theirs.

A second hidden cost is null shape. Proxycurl returned null for missing fields. Modern providers vary: LinkedIn's own People Search API returns the field omitted entirely, LinkdAPI uses an empty string, and LinkFetch returns null to match Proxycurl's behaviour deliberately. If your downstream code does if (record.field !== null), that test breaks in two of three cases. Map it once at the adapter and never again downstream.

The full mapping table

The table below covers the eighteen fields that 90% of Proxycurl integrations actually used. The "LinkFetch" column shows the canonical path in the LinkFetch v1 response. The "Notes" column flags the gotchas covered in detail in the next section.

Proxycurl field LinkFetch v1 Notes
public_identifier publicIdentifier direct rename
full_name fullName direct rename
first_name firstName direct rename
last_name lastName direct rename
headline headline unchanged
summary summary unchanged
country_full_name location.country nested, ISO-3166 alpha-2 also at location.countryCode
city location.city nested under location
state location.region renamed to "region" to handle non-US
occupation derived from experience[0] not a stored field; compute at read
experiences experience array name singularised
experiences[*].company experience[*].company.name nested under company object
experiences[*].company_linkedin_profile_url experience[*].company.linkedinUrl renamed
experiences[*].starts_at experience[*].startDate ISO-8601 string, not partial
experiences[*].ends_at experience[*].endDate null if current
education education unchanged
connections connectionCount renamed; also typed as integer not string
follower_count followerCount direct rename

Eighteen fields covers most of the surface, but the long tail matters. Profile-level fields like accomplishment_courses, volunteer_work, and recommendations exist on LinkFetch under accomplishments.courses, accomplishments.volunteer, and recommendations respectively, all nested under an accomplishments parent that Proxycurl flattened.

Five fields that don't translate cleanly

These are the ones taht bite people on day three of the migration, after the easy renames pass code review and the integration looks green.

1. occupation. Proxycurl synthesised this field by concatenating the most recent experience's title and company. LinkFetch does not store it. You compute it at read time from experience[0].title and experience[0].company.name. If experience is empty (rare, but real for stub profiles), fall back to the headline.

2. Date precision. Proxycurl returned partial dates as {"day": null, "month": 5, "year": 2022}. LinkFetch returns ISO-8601 strings. When the source only knows month and year, LinkFetch returns "2022-05-01", choosing the first of the month deterministically. Code that did if (date.day === null) to detect imprecision needs a new signal: LinkFetch exposes experience[*].startDatePrecision as "day", "month", or "year" so you can preserve the distinction.

3. Currently-employed flag. Proxycurl had no boolean for "is this person currently at this company". You inferred it from ends_at === null. LinkFetch keeps that pattern (endDate === null means current) and also exposes experience[*].current as a derived boolean. Use whichever your code already reads, but pick one.

4. Company stubs vs. company objects. Proxycurl inlined a denormalised company string with the URL. LinkFetch returns a small nested company object: {name, linkedinUrl, industry, employeeCount}. Industry and headcount were never on the Proxycurl experience entry, so this is genuinely new data. If you join experiences against a companies table downstream, you can drop that join for fields that LinkFetch already inlines.

5. The connections integer-vs-string thing. Proxycurl returned connections as a string, sometimes "500+", sometimes "342". LinkFetch returns connectionCount as a true integer. The "500+" case is now represented as connectionCount: 500 plus connectionCountIsCapped: true. Anyone parsing the string with a regex needs to delete that code and read two fields instead.

Building the adapter pattern

The right shape for the migration is an adapter, not a search-and- replace. One file, one function, mapped tests. Here is the skeleton most teams converge on:

// adapters/profile.ts
import type { LinkFetchProfile } from "@linkfetch/sdk";

export type ProxycurlShape = {
  public_identifier: string;
  full_name: string;
  occupation: string;
  experiences: Array<{
    company: string;
    title: string;
    starts_at: { day: number | null; month: number; year: number };
    ends_at: { day: number | null; month: number; year: number } | null;
  }>;
  // ... other Proxycurl fields
};

export function toLegacyShape(p: LinkFetchProfile): ProxycurlShape {
  return {
    public_identifier: p.publicIdentifier,
    full_name: p.fullName,
    occupation: p.experience[0]
      ? `${p.experience[0].title} at ${p.experience[0].company.name}`
      : p.headline,
    experiences: p.experience.map((e) => ({
      company: e.company.name,
      title: e.title,
      starts_at: isoToPartial(e.startDate, e.startDatePrecision),
      ends_at: e.endDate
        ? isoToPartial(e.endDate, e.endDatePrecision)
        : null,
    })),
  };
}

Two things to flag here. First, the adapter goes in your codebase, not LinkFetch's. Vendor-supplied compatibility shims are tempting and they rot fast. Second, the adapter is a one-way door: write code against the new shape going forward and let the legacy adapter only feed the parts of your system you have not yet migrated. Otherwise you end up maintaining the Proxycurl schema forever.

How LinkFetch handles the gotchas Proxycurl didn't

Two structural differences worth calling out, since they are not just renames.

Date imprecision is explicit. LinkFetch returns startDatePrecision and endDatePrecision alongside the date itself. You can preserve "month-only" vs. "year-only" knowledge through your ETL without parsing the date string for telltale -01 suffixes.

Caching is per-user, not global. LinkFetch charges full credits on every call by default and does not silently de-duplicate behind the scenes. If you want a cache, you build it. The trade-off is that data freshness is yours to decide; there is no shared global cache that quietly returns 3-month-old records when you asked for current state. For migrators coming from Proxycurl's freshness ambiguity, this is a welcome change.

The compliance posture is the other structural shift. Where Proxycurl operated as a third-party scraper, LinkFetch runs inside the user's own LinkedIn session via a Chrome extension. The user is the principal. That changes what you can legally do with the data far more than it changes the shape of the response, but it is worth knowing the architecture before you commit. The GDPR posture of the surviving providers covers the legal half in depth.

Frequently asked questions

Does LinkFetch publish a Proxycurl-compatible endpoint?

No, and we deliberately chose not to. Compatibility shims encourage half-migrations, where the team renames imports and never updates the ETL. The adapter pattern in this post is the supported path: write the shim in your codebase, against your tests, and migrate fully on your own schedule.

What about the lookup_email endpoint?

Proxycurl's lookup_email endpoint returned a profile from an email address. LinkFetch does not offer a direct equivalent for compliance reasons; email reverse-lookup operates outside the user's own LinkedIn session and falls outside our user-as-principal model. Reverse-lookup providers still exist on the market, but the legal posture is meaningfully different. This is one of the few cases where "map field by field" does not apply and you need to rework the workflow.

How long does the migration take in practice?

The teams who finished it inside one engineering week followed three rules: write the adapter first with tests against canned Proxycurl fixtures, swap the data source second, deprecate the adapter third (over the next quarter, as downstream code moves to the new shape). The teams who took six weeks tried to map field-by-field at every call site at once. Don't do that.

What about historical Proxycurl data already in our warehouse?

Leave it. Run new fetches through LinkFetch in the new shape, and either run the adapter in reverse for short-term consistency or project both shapes through a view layer that normalises at read time. Backfilling existing rows is rarely worth it; the data is stale anyway, and the next refresh solves it for free.


Last updated: 2026-04-27 by the LinkFetch team.