LinkedIn API v1 to v2 migration

Move LinkedIn integrations from the legacy v1 routes to typed v2 routes.

LinkedIn API v1 to v2 migration

The documented LinkedIn surface is v2: typed camelCase fields, explicit lookup outcomes, and one result page per request. Existing /v1/linkedin callers keep working. The 30-day HTTP retirement window has not started. For new work, use the v2 routes below.

Start with the LinkedIn API reference for identifiers, credits, and the current route list.

Route changes

v1 routev2 routeMigration behavior
GET /v1/linkedin/profilesGET /v2/linkedin/profiles (handle or entityId) or GET /v2/linkedin/profiles/batchThe v2 person record is projected into the legacy response during the compatibility window.
GET /v1/linkedin/organizationsGET /v2/linkedin/organizations (id), GET /v2/linkedin/organizations/resolve (slug), or GET /v2/linkedin/organizations/batchResolve when needed, enrich, then project into the legacy organization shape.
GET /v1/linkedin/jobsGET /v2/linkedin/jobs/{jobId} or GET /v2/linkedin/jobs/batchThe typed v2 job is projected into the legacy response.
GET /v1/linkedin/people/searchGET /v2/linkedin/people/searchLive discovery records are projected. The v1 response does not preserve v2 pagination availability or facts.
GET /v1/linkedin/jobs/searchGET /v2/linkedin/jobs/searchUse v2 for new work. Filters differ — there is no silent remap of v1 text-location or radius. v1 remains callable until the HTTP retirement window completes.
GET /v1/linkedin/profiles/postsGET /v2/linkedin/people/{entityId}/postsUse v2 for new work. Date, authorship, and limit semantics differ from v1. v1 remains callable until the HTTP retirement window completes.

New integrations should call v2. The v1 replacements stay on the wire until the retirement window starts and completes. No sunset date is implied by local implementation or passing tests.

Field and input changes

Both public API versions use camelCase. V2 introduces richer typed records and keeps stored snapshot IDs, LinkedIn numeric IDs, person entity IDs, handles, and slugs separate. Identifiers are strings; do not convert large activity IDs to JavaScript numbers. Callers use the documented v2 query names.

v1 profile responsev2 single profile response
Record inside data.results[i].profile for REST batchesRecord in data.profile for single lookups
profile.bioprofile.summary
profile.avatarUrlprofile.profilePictureUrl, plus all supplied profile.profilePictures
profile.experienceprofile.positions, retaining nested fields and partial dates
Required legacy arrays may be empty when unavailableMissing collection is null; observed empty collection is []

Legacy rows include typed compatibility.availability and compatibility.omittedEntries when a projection is available. Use these to distinguish an unavailable section from a verified empty one, and to identify entries that cannot fit an old schema. For example, a publication without a title is retained in v2 and counted as a projection omission in v1. Do not treat a legacy empty array as proof that LinkedIn returned no records.

Partial dates preserve known components. A zero date component becomes null; it is not presented as a real year, month, or day. Public responses include lookupStatus where the route supports lookup outcomes, so a completed not_found or empty result is distinct from a technical failure.

Requests, pages, and batches

Every request returns one page. When data.page.nextCursor is supplied, pass it as cursor to the same endpoint with the same entity and filters. The start and page response fields describe the current page, not the next one. Cursors are bound to the operation, entity, and filters. Unknown hasMore and totals remain null; do not infer completion from array length or invent an offset when continuation has not been established.

People search defaults to 20 records and accept at most 50 per page. Live people discovery defaults to 20 records; organization and job discovery default to 25. All accept at most 50. V2 rejects unsupported query parameters, including legacy job-search filters whose meaning cannot be preserved.

URL batch routes accept 1–50 repeated url parameters. They preserve input order and duplicates, and do not accept enrichment flags. A batch result records each completed input independently, so one unavailable URL does not erase successful rows for the other inputs.

The response limit is 4,000,000 UTF-8 bytes. An oversized response returns HTTP 413 response_too_large without a charge. Use a smaller batch to reduce the response size. Technical failures are not charged. Completed empty and not_found outcomes remain billable at the route price.

Unknown query keys, including former include* flags on base profile and organization lookups, are rejected with HTTP 400 and no charge. Related data lives on dedicated routes: employment history, recommendations, similar people, interests, profile posts, organization headcount, similar organizations, affiliated organizations, organization posts, and organization jobs.

Pricing and freshness

Use the published route price on each docs page and meta.creditsCharged as the accounting record.

  • Person and job URL batches cost 3 credits per completed URL.
  • Organization URL batches cost up to 9 credits per completed URL. A resolver miss costs 3 credits; a technical failure of the whole wrapper costs 0.
  • A single profile or person lookup costs 3 credits. An organization costs 6 by ID or up to 9 by slug (3 on resolver miss). Dedicated related-data routes cost 3 credits each.

A cache hit still costs the full published price. The public response does not identify cache provenance; sourceFamily is not a cache indicator.

Example

From SDK 0.30, client.linkedin.* calls the v2 routes. The old URL-based methods remain on client.linkedin.v1. SDK calls return a Result, so check ok before reading value:

const result = await client.linkedin.v1.getProfile({ url });
if (result.ok) {
  console.log(result.value.data.profile?.firstName);
} else {
  console.error(result.error.message);
}

The default method calls the single profile route directly:

const result = await client.linkedin.getProfile({
  handle: "ryanroslansky",
});
if (result.ok) {
  const { data, meta } = result.value;
  if (data.lookupStatus === "found" && data.profile) {
    console.log(data.profile.entityId, data.profile.firstName);
  }
  console.log(meta.creditsCharged);
} else {
  console.error(result.error.message);
}

Treat a not_found result as a completed outcome and inspect meta.creditsCharged. Retry technical failures according to the HTTP error and retry guidance rather than converting them into empty data.

Use client.linkedin.getProfile({ entityId }) when an entity ID is already available. client.linkedin.v2 is an alias for the same v2 methods. Other platform resources are unchanged.

Retirement

The six v1 migration routes return 410 endpoint_retired without a charge after the announced 30-day window. Responses during the window carry deprecation and sunset guidance. Remaining /v1/linkedin company, post, transcript, and Ad Library routes are not part of this retirement.

On this page