# Changelog of LinkedIn Profile Finder & Scraper, No Cookies — LinkedIn Emails (`afanasenko/linkedin-profile-api-scraper`) Actor

- **URL**: https://apify.com/afanasenko/linkedin-profile-api-scraper/changelog.md
- **Full Actor documentation**: https://apify.com/afanasenko/linkedin-profile-api-scraper.md

## Changelog

All notable changes to the LinkedIn Profile Scraper are documented here.
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

### \[0.0.49] - 2026-10-11

#### Changed

- The upgrade hint now shows Apify's current starting price for paid plans: $19/month with $19 in platform credits (it said $29).

### \[0.0.48] - 2026-10-08

#### Fixed

- **A request that briefly got no answer is no longer given up on too early.** When a request got no answer at all — the connection dropped, or nothing came back in time — it was asked again twice; but if the same request had already waited out a busy moment or a temporary error, it was given up on the spot, and what it was reading was left unchecked this time. It is now asked again twice in every case, a few seconds apart, never past the run's own timeout.

### \[0.0.47] - 2026-10-08

#### Fixed

- **A temporary error on our data source no longer ends a lookup after a few seconds.** A profile,
  company or search that hits one now keeps trying for about a minute, at spread-out moments, before
  it is reported as not checked — and a lookup that had already waited out a busy moment gets that
  full minute too. Anything that still cannot be checked is left out of the charge, as before.
- If the errors continue, the run stops asking after two such lookups in a row instead of spending a
  minute on every remaining one, and says so once. Whatever was not looked up is not charged.
- A temporary error page is now reported as a lookup that could not be checked this time, never as
  a failed run.

### \[0.0.46] - 2026-10-05

#### Changed

- **New title: «LinkedIn Profile Finder & Scraper, No Cookies — LinkedIn Emails».** The README heading matches it. Input fields, output columns, defaults and pricing are unchanged from 0.0.45.

### \[0.0.45] - 2026-09-30

#### Fixed

- Empty filtered runs now name the filters that excluded records, with counts and specific fixes.
  The email filter explains that it keeps available emails rather than finding missing ones.
- The completed status page shows this explanation when every result was filtered out.
- Filtered counts now reach analytics correctly, including runs stopped early. The total analyzed
  no longer counts filtered records twice.

### \[0.0.44] - 2026-09-24

#### Fixed

- **A request that runs into a busy moment and then a dropped connection no longer gets a fresh
  round of waiting.** Each request keeps one waiting budget from its first attempt to its last, so a
  stuck one is reported as not checked on time instead of after a second full round of waiting.

### \[0.0.43] - 2026-09-24

#### Changed

- **Docs:** the related-actors table links the new LinkedIn People Search — find employees and
  decision makers by job title, company, country and job function.

### \[0.0.42] - 2026-09-23

#### Fixed

- **When several of your runs start at the same moment, each profile or company now waits its turn
  instead of being skipped.** Large batches finish complete, just slower while runs overlap.
- **A lead or company search whose results could not be read is reported as temporarily
  unavailable**, not as timed out.

#### Changed

- **Docs:** the README's performance note no longer gives a fixed time for large batches. It now
  says they can take a while, that the run keeps going until every profile in your list has been
  checked, and that runs started at the same time share capacity.

### \[0.0.41] - 2026-09-23

#### Fixed

- **A lookup that could not be done is no longer reported as returning no data.** When a profile or
  company lookup was turned away on every attempt, the run used to finish as if the link had nothing
  behind it. It now says the link could not be looked up this time, that nothing was charged for
  it, and to re-run it in a few minutes. When only some of your links could not be looked up, you
  get the rest plus a line saying how many to re-run.
- **A search that could not be run no longer reports that nothing matched.** A company search that
  was turned away used to end as a failure saying no companies matched, and a decision-maker search
  whose companies could not be looked up said nothing matched. Both now say the search could not be
  run this time and that nothing was charged.
- **A lookup that finds nothing now explains why.** The run lists the usual causes — a page that was
  removed, renamed or is not public, a link that is not a LinkedIn profile or company URL, a website
  domain that LinkedIn does not link to a company page — and names the field to fix. Before, such a
  run could show only the paid-plan welcome message.
- **If a run has to stop early because of a limit on our side, it now stops cleanly and keeps what
  it fetched.** You are charged only for the rows already in your dataset, and the message says
  your input was fine and that the same input will work when you run it again later.
- On the free plan, the note saying the first results are in your dataset no longer appears when
  the dataset is empty; the run says what happened instead.
- The "Welcome to the paid plan" message now appears only on your first paid run instead of on
  every paid run, and the check-in after your third paid run can now appear. The count starts with
  this version, so if you already ran this actor on a paid plan you see the welcome once more.

#### Changed

- `RUN_SUMMARY.stopReason` now says how a run that returned nothing ended — `no_results`,
  `all_filtered` or `Targets temporarily unavailable` — where it used to say the run completed.

### \[0.0.40] - 2026-09-10

#### Changed

- Clarified wording in the README, this changelog, the input form, the output column descriptions
  and the live status page description. Nothing changes in how the actor runs, what it returns or
  what it costs.

#### Fixed

- When a run fails for a reason on our side, the error message now stays short and plain.

### \[0.0.39] - 2026-09-01

#### Fixed

- **A run that ran alongside your other runs could come back empty-handed, and now waits instead.**
  When several of your runs are working at the same time, requests start being turned
  away. The run used to try twice more, five and ten seconds apart, and then give up — so on
  22 August five runs started within a minute of each other and every one of them returned nothing.
  A run now waits longer and up to five times, and each run picks its own waiting time rather than
  all of them counting to five together and arriving back at the same instant. If the response says
  how long to wait, that is what the run waits.
- The extra waiting is capped: at most 30 seconds at a time and 75 seconds in total for any one
  piece of data, after which the run moves on rather than holding your run open. **A busy run may
  therefore finish a little slower than before — it is waiting where it used to fail.**

### \[0.0.38] - 2026-08-26

#### Changed

- **Your free monthly profiles are now held at the start of a run and returned at the end.** A run
  sets aside only what it could actually use — three profile URLs hold three, not fifty — and
  everything it does not collect goes straight back to your month when it finishes. What a completed
  run costs you is unchanged; what changed is that a run which is stopped part-way no longer leaves
  the count untouched.
- When fewer profiles are left in your month than a run asked for, the message at the end already
  quoted the smaller number. It now stays correct for a run the platform restarts part-way, which
  could previously report the full 50.
- The live status page picks the first real target from your list instead of showing an empty box
  when the list starts with a blank line.

#### Fixed

- Builds 0.0.36 and 0.0.37 do not exist as releases: both failed inside a dependency's own install
  step, before any of this Actor's code was reached. Nothing about them ever ran.

### \[0.0.35] - 2026-08-18

#### Changed

- **The free plan now includes 100 profiles a month**, counted across every run you start and
  resetting on the 1st. The existing limit of 50 rows per single run is unchanged. Most free runs
  are well under this — the typical free month collects around 24 profiles — but a run started
  once the month's 100 are used stops straight away, before looking anything up, and is not
  charged. Paid plans have no monthly limit.
- When fewer than 50 profiles are left in your free month, a run now returns what is left rather
  than stopping at nothing, and the message at the end says how many that was. Previously the
  message always said 50 even when fewer rows had been delivered.
- The completed screen and the README now state both free-plan limits together.

### \[0.0.34] - 2026-08-17

Maintenance build — no user-facing changes. Input schema, output dataset
columns, KVS records, console output, error messages, defaults, and pricing
are all unchanged from 0.0.33. Saved tasks continue to work identically.

### \[0.0.33] - 2026-08-12

Maintenance build — no user-facing changes. Input schema, output dataset
columns, KVS records, console output, error messages, defaults, and pricing
are all unchanged from 0.0.32. Saved tasks continue to work identically.

### \[0.0.32] - 2026-08-12

Maintenance build — no user-facing changes. Input schema, output dataset
columns, KVS records, console output, error messages, defaults, and pricing
are all unchanged from 0.0.31. Saved tasks continue to work identically.

### \[0.0.31] - 2026-08-10

#### Fixed

- **A failed run no longer prints raw technical failure text.** That text went into the run log and
  into the run's storage records, and on a connection problem it carried a server address. None of it
  was ever useful to you. Failures now read as a short, plain message; the reasons you can act on are
  unchanged.

### \[0.0.30] - 2026-08-10

#### Changed

- The free-plan limit ids listed in the run's `FREE_LIMITS_APPLIED` storage record now use one
  shared vocabulary across all our actors, so the same limit reads the same wherever you meet it.
  The human-readable message on each entry is unchanged.

### \[0.0.29] - 2026-07-30

Maintenance build — no change to input, output or pricing.

### \[0.0.28] - 2026-07-29

#### Changed

- **The Storage tab now names the records the run writes** — the run summary, the plain-language
  explanation of what happened, and the free-plan caps applied — instead of showing bare keys.
- **The Store listing now says what this actor returns** — name, headline, experience, education,
  followers, company size and industry — and the price line shows the per-profile price instead of
  a generic label.

### \[0.0.27] - 2026-07-28

#### Fixed

- **The status page is saved at all now.** Its saved copy was being rejected on every single run,
  so there was nothing to fall back on once the run's container went away.
- **The status page now keeps up while a run is going.** Its saved copy was only written twice —
  once at the start and once at the end — so anyone opening it mid-run saw the run as it looked at
  second zero. It now refreshes every ten seconds.
- **The "Live status" link now works after a run has finished.** It pointed at the run's own
  container, which the platform shuts down as soon as the run ends, so opening it later showed
  nothing. It now opens the saved copy of the page, which stays available for as long as the run's
  storage does.
- **Removed a storage record from the actor's schema that this actor never writes.** `SKIPPED_ITEMS`
  was declared but never produced, so tools reading the schema expected a record that was never
  there.

### \[0.0.26] - 2026-07-06

#### Changed

- Every "Upgrade" link in run messages now says what's behind it: paid plans start at $29/mo with $29 in platform credits, and the link opens your plan page in Apify Console.

### \[0.0.25] - 2026-07-06

#### Fixed

- The "Upgrade" link in run summaries now opens your Apify plan page directly (the old link form could land on the wrong billing tab).

### \[0.0.24] - 2026-07-01

#### Added

- **"Example tasks" section in the README** — three ready-to-run templates (scrape profiles by URL, scrape company data, find leads by company & job title) you can open and run in one click.
- **Clearer in-form guidance for the search modes** — the Job title (Mode 3) and Company keywords (Mode 4) fields now carry a short tip on how broad to go, so a search returns results on the first try instead of coming back empty.
- **New FAQ entries** — whether you need your own LinkedIn account, cookies, or login (you don't), and how to bulk-enrich a large list of profile URLs.

#### Changed

- **Store title and description** now spell out bulk lookups, company data, and that no login or cookies are required.
- **Docs correction** — the Tips section previously said Mode 3 search waits up to 60s; it now reflects the current behaviour (slow searches are waited out for several minutes, and a search with no matches ends in seconds).

No change to pricing, modes, run behaviour, or output schema.

### \[0.0.23] - 2026-07-01

#### Added

- **Free runs now tell you exactly how much more was available.** When a run on the free plan matches more than the 50-item free ceiling, it shows a clear message: how many results were matched in total, that the first 50 are already in your dataset, and how many more are waiting — plus a one-click way to get the full set by re-running the same input on a paid plan. Previously the extra results were left out silently.

### \[0.0.22] - 2026-05-25

#### Fixed

- **A search with no matches now ends in seconds with the no-results message** — when Search Leads or Search Companies returned zero results, the run used to keep waiting on the search and, after several minutes, finish with a misleading "search did not finish in time — please re-run shortly". It now recognises a completed empty search right away and shows the clear no-results guidance (with the best ways to broaden your search). Empty results are still not charged.

### \[0.0.20] - 2026-05-21

#### Added

- **Helpful message when a search returns no results** — if Search Leads or Search Companies finds nothing, the run now explains the likely cause and the best fixes (broaden or remove the job title, simplify keywords, drop the industry filter, try larger companies) instead of finishing silently with an empty dataset. Empty results are not charged.

### \[0.0.19] - 2026-05-21

#### Fixed

- **Country filter dropped every result** — selecting a country (e.g. "United States") in `countryFilter` previously matched nothing and returned an empty dataset, because the filter compared the full country name against LinkedIn's two-letter country codes. The filter now matches the country code correctly across all four modes.
- **Keyword company / lead search returned empty when the search was slow** — the company (`searchCompany`) and lead (`searchLead`) searches run asynchronously and can take longer than usual. The Actor now waits substantially longer (with progress logged) and, if the search still hasn't finished, ends with a clear "please re-run shortly" message instead of silently reporting zero results. Empty results from a slow search are not charged.

### \[0.0.18] - 2026-05-18

#### Added

- **"Filters dropped every result" notice** — when every fetched row gets removed by your advanced-filter settings, the run now writes a `USER_MESSAGE` explaining what happened plus a one-line hint on which knob to relax first (lower `maxFollowers`, drop `countryFilter` / `industryFilter`, or set `mustHaveEmail` / `mustHavePhone` to false). Most useful in Mode 3 (`searchLead`), where it's easy to combine `maxFollowers` + `countryFilter` against senior decision-makers and end up with 0 saved rows.
- **Paid-plan welcome message** — first paid run now lists the three things the free plan blocked: running all 4 operation modes without per-mode item caps, applying the full advanced-filter suite, and scheduling daily Apify Task runs with your own input. Replaces the prior placeholder line.

#### Changed

- **Cleaner stop-reason classification** — clean runs that finish without any matching results are now labeled as completed runs (with zero results) rather than as "unknown" in the run summary and status banner, making it easier to tell "ran fine, nothing matched your input" apart from real errors.

### \[0.0.16] - 2026-05-11

#### Changed

- **README** — Dropped the standalone `Choose your mode` (84 lines, 4 sub-sections with per-mode JSON examples) and `How to use it` (6 lines) H2 sections. The 4 operation modes (Lookup Profile by URL / Lookup Company / Search Leads / Search Companies) are now summarized in a compact 4-row table at the top of `What it does`. Trims 84 lines and 4 sub-sections; same input, output, and pricing.

### \[0.0.13] - 2026-05-09

#### Changed

- **Customer-facing run logs** — fetch operations now log `Fetching profile data for <slug>…`, `Fetching company data for <key>…`, `Searching decision-makers at <company>…`, `Searching companies for "<keywords>"…`. Per-request HTTP details (status, elapsed, response size) demoted from `info` to `debug` level so the run log stays focused on the work being done.
- **Cleaner Input UI** — removed the "Try it for free" demo-fallback advertising from input schema descriptions (top-level + 4 per-mode `sectionDescription` fields). Demo run still triggers silently on empty input + shows the loud `⚠️ TEST RUN` banner in logs.
- **Empty image cells** — when a profile has no avatar or a company has no logo, the cell now renders empty in the Apify Console output preview. Previously the column showed a "broken image" proxied placeholder. Internally `profile_image_url` / `company_logo_url` / `logo_url` now emit `null` (not the `"N/A"` sentinel) and `dataset_schema` declares them as `["string", "null"]`.

#### Added

- **Country & industry filter dropdowns** — `countryFilter` is now a searchable single-select dropdown backed by the canonical ISO-3166 country name list (198 entries). `industryFilter` is a single-select dropdown and Mode 4 `searchCompanyIndustries` is a **multi-select** dropdown — both backed by the 434-entry canonical LinkedIn Industry Codes V2 taxonomy. Free-text input is no longer required for either filter.
- **Live status page redesign** — aligned with the rest of our scraper family: Inter font, 5-second auto-refresh, badge-dot pulse animation on the running state, distinct completed-paid / completed-free banners with review and upgrade CTAs. The `/api/status` JSON gained 4 progress counters (`skippedPrivate`, `skippedErrors`, `mediaFetchFailed`, `goodQuality`) and a `cost.budget` field. Two new endpoints — `POST /api/client-info` (capture viewer device/timezone/locale) and `GET /track` (analytics click redirect) — round out the contract.

#### Backwards compatibility

- Legacy free-text `countryFilter` values from existing tasks: tasks load with the dropdown defaulting to `(no filter)` if the saved value isn't one of the new enum entries — re-pick the country to lock in the dropdown choice. The post-fetch substring filter still works for any value that does match.
- Legacy free-text `industryFilter` values: tasks load with the dropdown defaulting to `(no filter)` if the saved value isn't one of the canonical taxonomy names — re-pick the industry to lock it in. Post-fetch substring filter still honors any free-text value left from old tasks.
- `searchCompanyIndustries` is now restricted to canonical taxonomy values via `items.enum`. Existing tasks with non-canonical free-text values may need to be re-saved with the dropdown picker.
- All v0.0.12 dataset shape guarantees (entity-aware fields, no aliases) are preserved.

### \[0.0.12] - 2026-05-08

#### Changed

- **Cleaner output rows** — each row now carries only the field set that matches its `entity_type`. Profile rows expose ~44 profile fields; company rows expose ~26 company fields. Previously every row was padded to the union of both schemas with `"N/A"` filler in the irrelevant half.
- **No more duplicated columns** — duplicate alias columns (`industry` ↔ `industries`, `company_domain` ↔ `domain`, `company_logo_url` ↔ `logo_url`, `company_employee_count` ↔ `employee_count`, `company_employee_range` ↔ `employee_range`, `company_year_founded` ↔ `year_founded`, `company_website` ↔ `website`, `company_linkedin_url` ↔ `linkedin_url`) are collapsed to a single canonical column.

#### Backwards compatibility

- Profile-only fields (`first_name`, `headline`, `educations`, `experiences`, `school`, `is_premium`, etc.) are no longer present on company rows. If your downstream consumer iterated those keys expecting `"N/A"`, switch on `row.entity_type === 'company'` first. Same applies in the other direction (`affiliated_companies`, `funding_info`, `specialties`, `tagline` were never meaningful on profile rows).

### \[0.0.11] - 2026-05-08

#### Changed

- **Cleaner Input UI** — example URLs removed from field defaults; Apify Console now shows fields empty by default with example URLs displayed as ghost-text placeholders only.
- **Cost-control caps** moved inside each mode's section — no separate "Cost control" subsection. Apify Console Input tab now renders 5 fold-out sections (4 🎯 mode sections + 1 🔬 Advanced filtering), down from 9.
- **Mode section emoji unified** — every mode sectionCaption now uses 🎯 (was 🎯/🏢/🔍/🏭).

#### Added

- **Test-run on empty input** — clicking *Save & Start* without filling any field now triggers a built-in demo run (capped at 10 items) instead of failing with `Input Error`. A loud `⚠️ TEST RUN` banner is logged so you know it's not running on your data.
- **`USER_MESSAGE` demo hint** — when a test run fires, the run's KVS gets a `demo_run_v1` user-message explaining the cap and how to switch to your own data.

#### Backwards compatibility

- Existing tasks with explicit input continue to work unchanged. Tasks that relied on the old "satyanadella" / "microsoft" prefill values being auto-filled by the schema will now see the demo seed injected by the actor code instead — same effective behavior.

### \[0.0.10] - 2026-05-08

#### Added

- **Mode 3: Search Leads** — find decision-makers at given target companies, with optional job-title filter (e.g. "engineering manager" at Microsoft + Anthropic).
- **Mode 4: Search Companies** — keyword search across the LinkedIn company graph, optionally narrowed to specific industries.
- **Advanced filtering** — 7 cross-mode filters (`minFollowers`, `maxFollowers`, `mustHaveEmail`, `mustHavePhone`, `openToWorkOnly`, `countryFilter`, `industryFilter`) applied post-fetch.
- **Live status web server** — auto-refreshing HTML status page during runs, plus `/api/status` JSON endpoint and `/api/health` for programmatic monitoring.
- **Per-mode cost control** — separate `maxItemsModeN` field per mode (Mode 1: `maxItemsMode1`, etc.) replaces the old single global cap.
- **Output table thumbnails** — Profile photo + Company logo columns now render as image thumbnails in the Apify Console output preview (`profile_image_url`, `company_logo_url`, `logo_url` formatted as images).

#### Changed

- **Title:** "LinkedIn Profile API Scraper" → "LinkedIn Profile Scraper" (cleaner Store-page name).
- **README** restructured to 18-section layout — opens with value prop instead of pricing; per-mode cost control inline; Related Actors table added; Filter recipes section added.
- **Default input prefills** kept (so test runs work) but `default: []` set explicitly so production runs respect input.
- **Schemas added:** `.actor/web_server_openapi.json` (live API spec) + inline `storages.keyValueStore.collections` (RUN_SUMMARY, USER_MESSAGE, FREE_LIMITS_APPLIED, SKIPPED_ITEMS, status.html).

#### Backwards compatibility

- Legacy `maxItems` field still honored as fallback when no mode-specific cap is set — existing tasks keep working.
- Dataset schema is additive — no column removals or type narrowings; existing downstream consumers continue to receive the same row shape.

### \[0.0.1] - 2026-05-07

#### Added

- Initial private build.
- Two operation modes:
  - `lookupByUrl` — enrich LinkedIn profile URLs (44 fields per row: about, headline, experience, education, current employer, follower count, verification flags).
  - `companyLookup` — enrich LinkedIn company URLs OR domains (26 fields per row: industry, employee count, headquarters, specialties, funding, affiliated companies). URL vs domain auto-detected per line.
- Single primary paid event `PROFILE_ANALYZED` at $0.030 — billed once per dataset row.
- Free-plan cap of 50 items per run; paid-plan unlimited subject to `maxItems` ceiling.
- Test-run cap of 10 items when running with default seed input (Satya Nadella / Microsoft) — keeps trial spend under $0.30.
- Stable union output shape with `entity_type` discriminator (`profile` | `company`); missing values returned as `"N/A"` for downstream Sheet / CSV consumers.
- Resume-safe with automatic retries when a request is rate-limited or fails temporarily (exponential backoff, max 3 attempts).
- Stops the run early after 3 requests in a row are refused, instead of spending your budget on a run that cannot succeed.
- KVS records `RUN_SUMMARY`, `USER_MESSAGE`, `FREE_LIMITS_APPLIED` for live status and post-run dashboards.
- Analytics webhook integration with per-actor named KVS for first-run timestamps and session IDs.
