People change jobs, and a CRM full of stale titles quietly ruins outreach and account reports. If you already have the LinkedIn profile links of your contacts (most CRMs store them), you can refresh titles and companies on a schedule from the public profile pages, without logging in to LinkedIn and without handing over your session cookie.

This post covers what a public profile actually shows, what goes wrong, and a working script. Disclosure: I make the tool used in the script (a pay-per-result Apify actor from SpellzCode). The approach and the caveats apply whatever tool you use.

Why not use your own LinkedIn session?

Many scrapers ask for your li_at cookie. That works until LinkedIn notices automated traffic on your account, and then it is your personal account that gets restricted. Reading only logged-out public pages keeps your account out of it entirely. The trade-off is less data and some refusals, covered below.

What a public profile shows

Logged out, a profile page usually shows:

  • name and headline (the job titles)
  • location and follower count
  • the "About" text
  • some experience and education entries, often only the current one or a few

It does not show emails or phone numbers, and many people limit their public profile, so fields can be empty. For "is this person still at Acme, and what is their title now?" it is enough most of the time.

What goes wrong

  1. Refusals. LinkedIn refuses a share of logged-out requests. In our own test 4 of 11 pages were refused. Treat a refusal as "try again later", not as "profile gone".
  2. Partly hidden text. LinkedIn sometimes blurs parts of the public page. A good parser marks those rows instead of returning garbage.
  3. Vanity URL changes. People rename their profile URL. A 404 is a signal to look the contact up again.

So design the refresh as an incremental job: update rows that came back ok, queue refused rows for the next run, and flag not_found rows for a human.

The script

This reads a CSV with a linkedin column, fetches the public profiles, and writes a CSV of changes to review before importing into your CRM.

// npm i apify-client csv-parse csv-stringify import fs from 'node:fs'; import { parse } from 'csv-parse/sync'; import { stringify } from 'csv-stringify/sync'; import { ApifyClient } from 'apify-client'; const contacts = parse(fs.readFileSync('contacts.csv'), { columns: true }); const links = contacts.map((c) => c.linkedin).filter(Boolean); const client = new ApifyClient({ token: process.env.APIFY_TOKEN }); const run = await client.actor('spellzcode/linkedin-profile-data').call({ profiles: links }); const { items } = await client.dataset(run.defaultDatasetId).listItems(); const byUrl = new Map(items.map((p) => [p.query, p])); const changes = []; const retry = []; for (const c of contacts) { const p = byUrl.get(c.linkedin); if (!p) continue; if (p.status === 'refused') { retry.push(c.linkedin); continue; } if (p.status !== 'ok') { changes.push({ ...c, note: p.status }); continue; } const current = p.experience?.find((e) => e.to === null); const newTitle = p.jobTitles?.[0] ?? ''; const newCompany = current?.name ?? ''; if (newTitle !== c.title || (newCompany && newCompany !== c.company)) { changes.push({ ...c, newTitle, newCompany, note: p.partlyHidden ? 'partly hidden' : '' }); } } fs.writeFileSync('changes.csv', stringify(changes, { header: true })); fs.writeFileSync('retry.txt', retry.join('\n')); console.log(`${changes.length} changes, ${retry.length} to retry next run`); 

Run it weekly (cron, GitHub Actions, or Apify's own scheduler) and feed retry.txt back in next time.

No-code version

If you do not want to run code: open the actor's page on Apify, paste your profile links into the Profiles field, run it, and export the results as CSV or Excel. Apify also connects to Make, Zapier and n8n if you want the refresh to write straight into a sheet or CRM.

Cost

The actor charges per profile returned: $3 per 1,000 on Apify's free plan, less on paid plans. Refused and unavailable pages cost nothing. Apify's free plan includes $5 of monthly credit, which covers roughly 1,600 profiles a month.

The legal part

Job titles of named people are personal data. Under GDPR and similar laws you need a lawful basis (keeping data about existing business contacts accurate is a common one), you should keep only what you need, and you must honour objections and deletion requests. Do not use this for spam or for decisions like hiring without meeting the rules that apply. You are also responsible for respecting LinkedIn's terms.

Links