# LinkedIn University Alumni Career Mapper (`fanndev/linkedin-alumni-career-mapper`) Actor

Map where a university's graduates ended up: employer, role, location and graduation year, aggregated into top employers and destinations. Requires your own li\_at session cookie - LinkedIn blocks school pages outright for logged-out visitors.

- **URL**: https://apify.com/fanndev/linkedin-alumni-career-mapper.md
- **Developed by:** [Faisal Ahdan naufal](https://apify.com/fanndev) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $8.00 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## LinkedIn University Alumni Career Mapper

Where do a university's graduates actually end up? This actor reads a school's alumni and maps them — employer, role, location, graduation year — then aggregates that into the picture a tracer study is trying to produce: top employers, top destinations, top roles.

It is the kind of talent-mobility data that is close to impossible to assemble by hand and that universities routinely pay survey firms for.

### Read this before you buy

**This actor requires your own `li_at` session cookie, and LinkedIn is stricter about school pages than about anything else.**

`/school/` answers **HTTP 999** to logged-out visitors on every path tried — including the `/company/` alias that redirects there — and unlike LinkedIn's ordinary rate gate, **it does not clear with delays**. Verified 2026-09-20 across repeated attempts with multi-second pauses, in the same session where company and profile pages were answering 200 normally.

So unlike this portfolio's company and profile actors, there is **no public school record to fall back on**. Without a cookie there is nothing to read at all. The run still finishes successfully and writes a `cookie_required` notice per school explaining exactly this, rather than failing or handing you a silent empty dataset.

### Treat every result as a sample, not a census

LinkedIn's people search stops serving results past roughly **1,000**, and a large university has hundreds of thousands of alumni. No scraper gets a complete alumni list — this one does not pretend to.

What that means in practice:

- `alumniAnalysed` on every `SUMMARY` row tells you the sample size the rankings are built on. Read the rankings as "among the alumni LinkedIn surfaced", never as "among all alumni".
- **Use `filterKeywords`.** Narrowing server-side — `"software engineer"`, `"Jakarta"`, `"product manager"` — is both far cheaper and far more meaningful than sampling a thousand arbitrary graduates. For a large school it is the only way to get a slice you can reason about.
- Results also depend on what your account is allowed to see. An account with more network reach surfaces more.

### Input

```json
{
  "schoolUrls": ["stanford-university", "https://www.linkedin.com/school/universitas-indonesia/"],
  "filterKeywords": "software engineer",
  "sessionCookie": "AQEDAT...",
  "maxAlumniPerSchool": 200
}
```

A bare slug is treated as a `/school/` page, not a `/company/` one.

### Output

One `ALUMNUS` row per graduate, plus a `SUMMARY` row per school:

```json
{
  "recordType": "SUMMARY",
  "schoolName": "Stanford University",
  "alumniAnalysed": 200,
  "distinctEmployers": 84,
  "withEmployerPct": 78.5,
  "topEmployers": [
    { "employer": "Google", "alumni": 21 },
    { "employer": "Meta", "alumni": 14 }
  ],
  "topLocations": [{ "location": "San Francisco Bay Area", "alumni": 96 }],
  "graduationYearRange": [1994, 2024]
}
```

**`withEmployerPct` is the field that tells you how much to trust the rest.** `currentTitle` and `currentCompany` are split out of the person's headline on "at" and its common localisations — which works well for `"Senior Engineer at Acme"` and not at all for a headline that is a slogan. When `withEmployerPct` is low, the employer ranking is built on a thin slice and should be read that way. The raw `headline` is kept on every row so you can always check the split yourself.

`graduationYear` is likewise best-effort: LinkedIn does not return a structured graduation year in search results, so it is the earliest plausible year found in the text attached to the result, and it is often null.

These caveats are in the output schema too, not just this README — the numbers carry their own limitations.

### Getting your cookie

Sign in to LinkedIn in your browser → DevTools → Application → Cookies → `www.linkedin.com` → copy the **value** of `li_at` → paste into `sessionCookie`.

This actor never asks for your password and never signs in on your behalf.

**The honest risk.** Scraping while signed in breaches LinkedIn's User Agreement and accounts do get restricted for it. Mitigations, all of which this actor does or exposes:

- **Residential proxy in your own country** — the default here. LinkedIn weighs the exit IP against where your account normally signs in; a datacenter IP is the mismatch that flags a session.
- **Low caps** — `maxAlumni` defaults to 1,000, `maxAlumniPerSchool` to 200.
- **The 3–8 second randomised delay**, which with a cookie in play is what keeps the run looking like a person rather than a script.

Use an account you would not mind losing.

### Who this is for

- **Universities** — tracer studies and employability reporting without commissioning a survey.
- **Headhunters** — which firms hire from which programmes, and where those people sit now.
- **Market researchers** — talent flows between companies, regions and sectors.

### Before you use this on people

Graduates are real people, and in the EU, UK and similar jurisdictions they carry data-protection rights even though their profiles are public. Aggregate reporting (top employers, top locations) is a much easier case to defend than contacting individuals. If you do reach out, say where you got the data, honour opt-outs, and keep it out of consumer marketing lists. That is your obligation as the data controller, not the actor's.

### Related actors

- **LinkedIn Company Employee Role Filter** — the same people search, faceted to an employer instead of a school.
- **LinkedIn Smart Job Description & Recruiter Extractor** — what employers are actually hiring for, fully public.

# Actor input Schema

## `schoolUrls` (type: `array`):

Universities to map. A linkedin.com/school/... URL or a bare school slug both work; a bare slug is treated as a school, not a company.

## `startUrls` (type: `array`):

The same school links in the request-list format, for callers that already keep one.

## `sessionCookie` (type: `string`):

Mandatory, and LinkedIn is stricter about school pages than about anything else in this portfolio: /school/ answers HTTP 999 to logged-out visitors on every path, including the /company/ alias that redirects there, and unlike the ordinary rate gate it does not clear with delays. There is no public school record to fall back on. To get the cookie: sign in to LinkedIn in your browser, open DevTools > Application > Cookies > www.linkedin.com, copy the VALUE of li\_at. This actor never asks for your password and never signs in on your behalf. Scraping while signed in breaches LinkedIn's User Agreement and accounts do get restricted - use one you are willing to risk. Without a cookie the run still finishes successfully and explains itself in the dataset.

## `filterKeywords` (type: `string`):

Passed to LinkedIn's own search box alongside the school filter - 'software engineer', 'product manager', 'Jakarta'. Narrowing server-side is much cheaper than reading every alumnus and filtering afterwards, and for large universities it is the only way to get a useful slice.

## `maxAlumniPerSchool` (type: `integer`):

Cap per school. LinkedIn's people search stops serving results past roughly 1,000 regardless, and large universities have far more alumni than that - so treat any result as a sample, not a census.

## `maxAlumni` (type: `integer`):

Overall ceiling across every school.

## `emitSummary` (type: `boolean`):

Append a SUMMARY row per school with top employers, top locations, top roles and the graduation-year range - the aggregate a tracer study actually wants out of the individual rows.

## `minDelaySeconds` (type: `integer`):

LinkedIn answers request bursts with HTTP 999. With a session cookie in play the randomised gap does double duty: it keeps the run under the rate gate and keeps it looking like a person rather than a script.

## `maxDelaySeconds` (type: `integer`):

The ceiling of the randomised gap. Raise both bounds if the log shows rate-gate warnings.

## `exportFormats` (type: `array`):

Besides the dataset, write ready-made files into this run's key-value store.

## `proxyConfiguration` (type: `object`):

This actor always runs with your session cookie, so use RESIDENTIAL in your own country. LinkedIn scores the exit IP against where your account normally signs in, and an Apify datacenter IP is precisely the mismatch that gets a session flagged.

## Actor input object example

```json
{
  "schoolUrls": [
    "stanford-university"
  ],
  "maxAlumniPerSchool": 200,
  "maxAlumni": 1000,
  "emitSummary": true,
  "minDelaySeconds": 3,
  "maxDelaySeconds": 8,
  "exportFormats": [],
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

## `results` (type: `string`):

One ALUMNUS row per graduate, a SUMMARY row per school with top employers and destinations, plus NOTICE and ERROR rows.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "schoolUrls": [
        "stanford-university"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("fanndev/linkedin-alumni-career-mapper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "schoolUrls": ["stanford-university"] }

# Run the Actor and wait for it to finish
run = client.actor("fanndev/linkedin-alumni-career-mapper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "schoolUrls": [
    "stanford-university"
  ]
}' |
apify call fanndev/linkedin-alumni-career-mapper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,fanndev/linkedin-alumni-career-mapper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/Xd8oteUyK7WofGr4d/builds/y0TA2rbXyXBsd4djc/openapi.json
