{
  "data": {
    "id": "0IKu7BXXnrONDnWWc",
    "actId": "v3jtgxsJLvXk7gs7U",
    "userId": "ntKWHRDUK3b8EebtU",
    "startedAt": "2026-09-09T16:41:54.766Z",
    "finishedAt": "2026-09-09T16:41:58.739Z",
    "status": "SUCCEEDED",
    "meta": {
      "origin": "CLI"
    },
    "stats": {
      "durationMillis": 3866,
      "runTimeSecs": 3.866,
      "computeUnits": 0.004295555555555556,
      "imageSizeBytes": 306656768
    },
    "options": {
      "useCache": true,
      "betaPackages": false,
      "memoryMbytes": 4096,
      "diskMbytes": 8192
    },
    "exitCode": 0,
    "inputSchema": "{\n  \"title\": \"Reliable LinkedIn Profiles Input\",\n  \"type\": \"object\",\n  \"schemaVersion\": 1,\n  \"properties\": {\n    \"profiles\": {\n      \"title\": \"LinkedIn profiles\",\n      \"type\": \"array\",\n      \"editor\": \"stringList\",\n      \"prefill\": [\n        \"https://www.linkedin.com/in/williamhgates\",\n        \"satyanadella\"\n      ],\n      \"description\": \"LinkedIn profile URLs or public identifiers (the part after /in/).\"\n    },\n    \"include_experience\": {\n      \"title\": \"Include work experience\",\n      \"type\": \"boolean\",\n      \"default\": true,\n      \"description\": \"Fetch the full experience history (titles, companies, dates).\"\n    },\n    \"include_education\": {\n      \"title\": \"Include education\",\n      \"type\": \"boolean\",\n      \"default\": true,\n      \"description\": \"Fetch the full education history (schools, degrees, dates).\"\n    },\n    \"max_proxy_sessions\": {\n      \"title\": \"Max proxy sessions per profile\",\n      \"type\": \"integer\",\n      \"default\": 3,\n      \"minimum\": 1,\n      \"maximum\": 6,\n      \"description\": \"If LinkedIn blocks one proxy identity, retry with a fresh one, up to this many times, before giving up on a profile (and not charging for it).\"\n    },\n    \"proxyConfiguration\": {\n      \"title\": \"Proxy configuration\",\n      \"type\": \"object\",\n      \"editor\": \"proxy\",\n      \"default\": {\n        \"useApifyProxy\": true,\n        \"apifyProxyGroups\": [\n          \"RESIDENTIAL\"\n        ]\n      },\n      \"description\": \"Residential proxy strongly recommended - LinkedIn aggressively blocks datacenter IPs (confirmed live: an unproxied request gets blocked within a handful of calls), which is the #1 cause of empty/failed runs on other scrapers.\"\n    },\n    \"max_results\": {\n      \"title\": \"Max results\",\n      \"type\": \"integer\",\n      \"default\": 15,\n      \"minimum\": 1,\n      \"maximum\": 5000,\n      \"description\": \"Maximum rows returned per run. The free preview is capped at 15 — raise this once you need the full feed.\"\n    }\n  },\n  \"required\": [\n    \"profiles\"\n  ]\n}",
    "readme": "# Reliable LinkedIn Profiles\n\nScrape a batch of LinkedIn profiles, and a chunk of the results come back as private or blank. That's normal at scale — accounts get deleted, restricted, or your request gets blocked mid-batch. What's not normal is paying for every one of those empty rows anyway, buried in a CSV of a few thousand.\n\nThis one is built around a single rule: **a profile is only billed if it actually returned real data.**\n\n## No cookies, no login — how\n\nLinkedIn serves a genuinely public, server-rendered version of a profile (and separate `/details/experience/` and `/details/education/` pages) to logged-out visitors, for anyone who hasn't restricted their public visibility. That's the actual mechanism behind every \"no cookies required\" LinkedIn scraper — this one included. No account, no session token, nothing that risks a ban on your own LinkedIn login.\n\n## What \"reliable\" means here, concretely\n\nConfirmed live while building this: a plain request with no proxy gets blocked by LinkedIn within a handful of calls — not a 403, a non-standard `999` status specific to their bot defense. Sometimes the block even comes back as an HTTP 200 with a fake-looking page instead of a clean error, which is exactly the kind of thing that turns into a silently empty \"successful\" result on a less careful scraper.\n\n- **Residential proxy by default** — the single biggest lever against getting blocked.\n- **Session rotation, not blind retries** — if one identity gets blocked, a fresh proxy session (new IP, new cookies) is used instead of hammering the same one.\n- **A confirmed private/deleted profile is not retried** — no proxy session will make a real profile out of one that isn't public, so it's skipped immediately instead of burning sessions.\n- **No charge on failure** — a profile that fails every session, or turns out private/nonexistent, is logged clearly in the run and simply never pushed to the dataset.\n\n## Input\n\n- `profiles` (required): LinkedIn profile URLs or bare public identifiers (`williamhgates` or the full `https://www.linkedin.com/in/williamhgates` both work)\n- `include_experience` (optional, default true): include work history (parsed from the public profile page)\n- `include_education` (optional, default true): include education (parsed from the public profile page)\n- `max_proxy_sessions` (optional, default 3, max 6): how many fresh proxy identities to try per profile before giving up\n- `proxyConfiguration` (optional, default: Apify residential proxy)\n\n## Output (one row per profile that returned real data)\n\n```json\n{\n  \"public_identifier\": \"williamhgates\",\n  \"name\": \"Bill Gates\",\n  \"headline\": \"Chair, Gates Foundation and Founder, Breakthrough Energy\",\n  \"location\": \"Seattle, Washington, United States\",\n  \"about\": \"...\",\n  \"experience_count\": 2,\n  \"education_count\": 2,\n  \"experience\": [{\"title\": \"Co-chair\", \"company\": \"Gates Foundation\", \"dates\": \"2000 - Present\"}],\n  \"education\": [{\"school\": \"Harvard University\", \"degree\": null, \"dates\": \"1973 - 1975\"}]\n}\n```\n\n## Known limitation — read before you buy\n\nOnly what's on the public (logged-out) version of a profile is available — that's less than what a logged-in connection sees, but it's also exactly what every \"no cookies\" scraper in this category is actually built on, regardless of what the marketing copy implies. Profiles with restricted public visibility will come back private/skipped, not billed.\n\n## Pricing\n\nPay per result — no charge for profiles that come back private, deleted, or blocked.\n",
    "changelog": "# Changelog\n\n## 0.1.x — Cold review (09/09/2026)\n\n**Fixed**\n\n- **A 200-with-block page was treated as \"profile private\" instead of\n  rotating the session.** LinkedIn sometimes answers a bot-flagged request\n  with HTTP 200 carrying an authwall/login/stripped page. That is the current\n  IDENTITY being blocked, not the profile being private — it must trigger a\n  fresh proxy session (the whole point of this actor). Reading it as\n  \"not found\" skipped session rotation after the first identity and returned\n  a fraction of a batch. A page is now accepted only if it carries the real\n  profile markup; an authwall/login/checkpoint redirect or a markup-less page\n  rotates to a new session.\n- **The `/details/experience/` and `/details/education/` pages are dead for\n  logged-out visitors** (checked live: the first redirects back to the profile,\n  the second answers 999). Work experience and education are now parsed from\n  the public profile page itself — two fewer proxied requests per profile, and\n  the sections are actually populated again.\n- **A proxy that could not even reach linkedin.com crashed the run** (the\n  warm-up request was unguarded). It now rotates to the next session.\n- **Profiles beyond `max_results` were still fetched** through the paid\n  residential proxy and then dropped. The run now stops fetching once the cap\n  is reached, and logs which profiles it did not reach. Light pacing added\n  between profiles.\n\n",
    "actorDefinition": {
      "actorSpecification": 1,
      "name": "reliable-linkedin-profiles",
      "title": "LinkedIn Profile Scraper — Never Pay for Empty Profiles",
      "description": "Bulk LinkedIn profile scraper - no cookies or login required. Pulls name, headline, location, about, full work experience and education. Built around one rule: a profile is only billed if it actually returned real data. Private, deleted, or blocked profiles are never billed.",
      "version": "0.1",
      "buildTag": "latest",
      "dockerfile": "../Dockerfile",
      "input": {
        "title": "Reliable LinkedIn Profiles Input",
        "type": "object",
        "schemaVersion": 1,
        "properties": {
          "profiles": {
            "title": "LinkedIn profiles",
            "type": "array",
            "editor": "stringList",
            "prefill": [
              "https://www.linkedin.com/in/williamhgates",
              "satyanadella"
            ],
            "description": "LinkedIn profile URLs or public identifiers (the part after /in/)."
          },
          "include_experience": {
            "title": "Include work experience",
            "type": "boolean",
            "default": true,
            "description": "Fetch the full experience history (titles, companies, dates)."
          },
          "include_education": {
            "title": "Include education",
            "type": "boolean",
            "default": true,
            "description": "Fetch the full education history (schools, degrees, dates)."
          },
          "max_proxy_sessions": {
            "title": "Max proxy sessions per profile",
            "type": "integer",
            "default": 3,
            "minimum": 1,
            "maximum": 6,
            "description": "If LinkedIn blocks one proxy identity, retry with a fresh one, up to this many times, before giving up on a profile (and not charging for it)."
          },
          "proxyConfiguration": {
            "title": "Proxy configuration",
            "type": "object",
            "editor": "proxy",
            "default": {
              "useApifyProxy": true,
              "apifyProxyGroups": [
                "RESIDENTIAL"
              ]
            },
            "description": "Residential proxy strongly recommended - LinkedIn aggressively blocks datacenter IPs (confirmed live: an unproxied request gets blocked within a handful of calls), which is the #1 cause of empty/failed runs on other scrapers."
          },
          "max_results": {
            "title": "Max results",
            "type": "integer",
            "default": 15,
            "minimum": 1,
            "maximum": 5000,
            "description": "Maximum rows returned per run. The free preview is capped at 15 — raise this once you need the full feed."
          }
        },
        "required": [
          "profiles"
        ]
      },
      "output": {
        "actorOutputSchemaVersion": 1,
        "title": "Output schema of Reliable LinkedIn Profiles",
        "description": "One row per profile that actually returned real data - profiles that are private, deleted, or blocked on every retry are never pushed here and never billed.",
        "properties": {
          "results": {
            "type": "string",
            "title": "Profile results",
            "template": "{{links.apiDefaultDatasetUrl}}/items"
          }
        }
      },
      "storages": {
        "dataset": {
          "actorSpecification": 1,
          "fields": {
            "$schema": "http://json-schema.org/draft-07/schema#",
            "type": "object",
            "properties": {
              "public_identifier": {
                "type": "string"
              },
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "headline": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "location": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "about": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "experience_count": {
                "type": "integer"
              },
              "education_count": {
                "type": "integer"
              },
              "experience": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "title": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "company": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "dates": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  }
                }
              },
              "education": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "school": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "degree": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "dates": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  }
                }
              }
            },
            "required": [
              "public_identifier",
              "name"
            ]
          },
          "views": {
            "overview": {
              "title": "Profiles overview",
              "transformation": {
                "fields": [
                  "name",
                  "headline",
                  "location",
                  "experience_count",
                  "education_count",
                  "public_identifier"
                ]
              },
              "display": {
                "component": "table",
                "properties": {
                  "name": {
                    "label": "Name"
                  },
                  "headline": {
                    "label": "Headline"
                  },
                  "location": {
                    "label": "Location"
                  },
                  "experience_count": {
                    "label": "Jobs found"
                  },
                  "education_count": {
                    "label": "Schools found"
                  },
                  "public_identifier": {
                    "label": "LinkedIn ID"
                  }
                }
              }
            }
          }
        }
      },
      "categories": [
        "LEAD_GENERATION",
        "AUTOMATION"
      ],
      "author": {
        "email": "0xgollum@gmail.com",
        "name": "Crabe"
      },
      "readme": "# Reliable LinkedIn Profiles\n\nScrape a batch of LinkedIn profiles, and a chunk of the results come back as private or blank. That's normal at scale — accounts get deleted, restricted, or your request gets blocked mid-batch. What's not normal is paying for every one of those empty rows anyway, buried in a CSV of a few thousand.\n\nThis one is built around a single rule: **a profile is only billed if it actually returned real data.**\n\n## No cookies, no login — how\n\nLinkedIn serves a genuinely public, server-rendered version of a profile (and separate `/details/experience/` and `/details/education/` pages) to logged-out visitors, for anyone who hasn't restricted their public visibility. That's the actual mechanism behind every \"no cookies required\" LinkedIn scraper — this one included. No account, no session token, nothing that risks a ban on your own LinkedIn login.\n\n## What \"reliable\" means here, concretely\n\nConfirmed live while building this: a plain request with no proxy gets blocked by LinkedIn within a handful of calls — not a 403, a non-standard `999` status specific to their bot defense. Sometimes the block even comes back as an HTTP 200 with a fake-looking page instead of a clean error, which is exactly the kind of thing that turns into a silently empty \"successful\" result on a less careful scraper.\n\n- **Residential proxy by default** — the single biggest lever against getting blocked.\n- **Session rotation, not blind retries** — if one identity gets blocked, a fresh proxy session (new IP, new cookies) is used instead of hammering the same one.\n- **A confirmed private/deleted profile is not retried** — no proxy session will make a real profile out of one that isn't public, so it's skipped immediately instead of burning sessions.\n- **No charge on failure** — a profile that fails every session, or turns out private/nonexistent, is logged clearly in the run and simply never pushed to the dataset.\n\n## Input\n\n- `profiles` (required): LinkedIn profile URLs or bare public identifiers (`williamhgates` or the full `https://www.linkedin.com/in/williamhgates` both work)\n- `include_experience` (optional, default true): include work history (parsed from the public profile page)\n- `include_education` (optional, default true): include education (parsed from the public profile page)\n- `max_proxy_sessions` (optional, default 3, max 6): how many fresh proxy identities to try per profile before giving up\n- `proxyConfiguration` (optional, default: Apify residential proxy)\n\n## Output (one row per profile that returned real data)\n\n```json\n{\n  \"public_identifier\": \"williamhgates\",\n  \"name\": \"Bill Gates\",\n  \"headline\": \"Chair, Gates Foundation and Founder, Breakthrough Energy\",\n  \"location\": \"Seattle, Washington, United States\",\n  \"about\": \"...\",\n  \"experience_count\": 2,\n  \"education_count\": 2,\n  \"experience\": [{\"title\": \"Co-chair\", \"company\": \"Gates Foundation\", \"dates\": \"2000 - Present\"}],\n  \"education\": [{\"school\": \"Harvard University\", \"degree\": null, \"dates\": \"1973 - 1975\"}]\n}\n```\n\n## Known limitation — read before you buy\n\nOnly what's on the public (logged-out) version of a profile is available — that's less than what a logged-in connection sees, but it's also exactly what every \"no cookies\" scraper in this category is actually built on, regardless of what the marketing copy implies. Profiles with restricted public visibility will come back private/skipped, not billed.\n\n## Pricing\n\nPay per result — no charge for profiles that come back private, deleted, or blocked.\n",
      "changelog": "# Changelog\n\n## 0.1.x — Cold review (09/09/2026)\n\n**Fixed**\n\n- **A 200-with-block page was treated as \"profile private\" instead of\n  rotating the session.** LinkedIn sometimes answers a bot-flagged request\n  with HTTP 200 carrying an authwall/login/stripped page. That is the current\n  IDENTITY being blocked, not the profile being private — it must trigger a\n  fresh proxy session (the whole point of this actor). Reading it as\n  \"not found\" skipped session rotation after the first identity and returned\n  a fraction of a batch. A page is now accepted only if it carries the real\n  profile markup; an authwall/login/checkpoint redirect or a markup-less page\n  rotates to a new session.\n- **The `/details/experience/` and `/details/education/` pages are dead for\n  logged-out visitors** (checked live: the first redirects back to the profile,\n  the second answers 999). Work experience and education are now parsed from\n  the public profile page itself — two fewer proxied requests per profile, and\n  the sections are actually populated again.\n- **A proxy that could not even reach linkedin.com crashed the run** (the\n  warm-up request was unguarded). It now rotates to the next session.\n- **Profiles beyond `max_results` were still fetched** through the paid\n  residential proxy and then dropped. The run now stops fetching once the cap\n  is reached, and logs which profiles it did not reach. Light pacing added\n  between profiles.\n\n"
    },
    "buildNumber": "0.1.9",
    "actVersion": {
      "sourceType": "SOURCE_FILES",
      "buildTag": "latest",
      "versionNumber": "0.1"
    },
    "usage": {
      "ACTOR_COMPUTE_UNITS": 0.004295555555555556
    }
  }
}