Skip to content
Agentmart
Submit a tool

linkedin

search people, and search inside named members' connections, read profiles, read a job posting, check keywords, verify connect eligibility, find role/company.

Skill by overseers-desk

IndexedMIT★ 1
Write a review

Install

Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.

npx agentmarket install skill/overseers-desk-linkedin

Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.

Get the weekly diff

The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.

We use your email only for this newsletter. Privacy

About

search people, and search inside named members' connections, read profiles, read a job posting, check keywords, verify connect eligibility, find role/company. Read a member's shared contact info (email, phone, websites). Send connection invites or direct messages to connections. Edit your own profile headline and About. Enumerate the messaging inbox and a thread's messages. Report which account the session is signed in as.

Use cases: documents, writing, productivity

No reviews yet. Installed it? Reviews come from verified installers only. How reviews work

Permissions

What this skill can do and what it asks for, from its SKILL.md. Checked Oct 25, 2026.

Tools
No tools found
Secrets
None asked for
Install scripts
None found

Network hosts it talks to 1

  • codeload.github.com · found in install command

Files it touches

  • ~/.claude/skills/linkedin

What's inside

Skill name
linkedin
Description
search people, and search inside named members' connections, read profiles, read a job posting, check keywords, verify connect eligibility, find role/company. Read a member's shared contact info (email, phone, websites). Send connection invites or direct messages to connections. Edit your own profile headline and About. Enumerate the messaging inbox and a thread's messages. Report which account the session is signed in as.
License
MIT
Instructions
343 lines in SKILL.md · view on GitHub

Agent Skills spec issues

  • name: "linkedin" does not match folder "linkedin.com"

Files (22)

  • BUGS.md
  • EDIT-VALIDATION.md
  • SKILL.md
  • auth-contact-info.tcl
  • auth-keyword-search.tcl
  • auth-li-connections.tcl
  • auth-li-inbox.tcl
  • auth-li-profile.tcl
  • auth-li-thread.tcl
  • auth-parse-profile.tcl
  • auth-parse-search.tcl
  • auth-send-invite.tcl
  • auth-send-message.tcl
  • auth-set-profile-field.tcl
  • connect-automation-notes.md
  • example-cul-quota-wall.html
  • li-canonical.tcl
  • identity.tcl · lib/
  • parse-profile.tcl · lib/
  • login.tcl
  • pub-parse-job.tcl
  • whoami.tcl

SKILL.md (first lines)

## Execution model

This workflow produces large DOM outputs (1-20MB per page). Spawn a **Sonnet subagent** to execute it so the main conversation context is not consumed. Tell the subagent to use the scripts in `${CLAUDE_PLUGIN_ROOT}/skills/linkedin.com/` — do not paste scripts inline.

Each script runs through `browser-serialiser`, which owns the browser and exposes a policed command surface (the skill drives `nav`/`dump`/`type`/`click` verbs, never a raw socket). The skill is named by a reference relative to `skills/`, without the `.tcl` suffix: `linkedin.com/auth-parse-profile`, `linkedin.com/auth-send-invite`, and so on. See `serialised-browsing` for the surface contract.

## Prerequisites

A logged-in LinkedIn session in the user-data-dir that `browser-serialiser` targets. This skill constructs LinkedIn URLs, navigates to them, dumps the rendered DOM, and parses the result.

Optional config, `[linkedin.com] public_identifier`: the vanity slug of the member expected signed in. `browser-serialiser` names it in the `session missing` line it prints on exit 77, so a person reading a failed run knows which login to restore.

If the dumped DOM title contains "Sign In", "Log In", "Iniciar sesión", or "Registrarse", the session is not active. LinkedIn expires the session periodically while keeping a remember-me cookie. First run `linkedin.com/login` (see "Establish a session" below) to re-mint a session via the fastrack flow without a password. If that reports `logged_out` (no remember-me), or if the title persists after a successful login, then investigate the plumbing: the user-data-dir may be wrong, or another chromium instance may hold the same user-data-dir.

## Establish a session

LinkedIn expires the active session periodically while keeping a remember-me cookie. Re-mint a session without a password via the fastrack flow:

```bash
browser-serialiser linkedin.com/login          # log in via fastrack if logged out
browser-serialiser linkedin.com/login --check  # report state only, never click
```

`login`, `auth-send-invite`, and `auth-send-message` drive the browser through the serialiser, which owns the browser lifecycle: one browser at a time (flock), a deadman timeout, and a teardown that reaches the browser even after snap detaches it into its own systemd scope.

The JSON `status` is one of:
- `already_logged_in` / `logged_in` — session active
- `logged_out_remember_me` — remember-me cookie present; the default run clicks the fastrack "continue" control to log in
- `logged_out` — no remember-me; the user must log in once in the GUI browser
- `login_failed` — fastrack clicked but no session minted

Run this when a fetch returns a sign-in title, before assuming the plumbing is broken. The same `[browser]` user-data-dir and UA caveats as the send scripts apply (close the GUI browser so the headless instance can write the session cookie).

## 1. Search people

Use **people search**, not "all" search. One reference builds the search URL, reads the rendered results, and parses them:

```bash
browser-serialiser linkedin.com/auth-parse-search "SEARCH TERMS"
browser-serialiser linkedin.com/auth-parse-search '{"keywords":"loan","connectionOf":["ACoAA..."],"network":["F"]}'
browser-serialiser linkedin.com/auth-parse-search '{"keywords":"loan","connectionOf":["ACoAA...","ACoAB...","ACoAC..."]}'
```

A bare string is a keyword search. A JSON object combines a keyword with LinkedIn's filters.

The third form is the one to reach for when the question is who, among the people these members know, matches a keyword. Name as many members as you like in `connectionOf`. One call covers all their networks, deduplicates the people who appear in more than one, and tags each result with the members it was found through. It costs one search per member named; see "What a call costs" below.

Emits the canonical envelope. `result` is:

```json
{"query": {...as passed...}, "state": "ok"|"metered", "total": null,
 "cost": {"queries": 1, "pages": 1}, "count": 3,
 "results": [{"slug", "profile_id", "profile_url", "name", "headline", "location",
              "degree", "shared_connections": [{"slug","name","profile_url"}],
              "via": ["ACoAA..."]}]}
```

`via` names which of the people in `connectionOf` this person was found through, so a merged result set stays attributable. `profile_id` is the `ACoAA...` id LinkedIn keys the card on, which is what `connectionOf` takes — so a result can be fed straight back in as the next search's target without a profile fetch. `shared_connections` are the members LinkedIn names on that card as connections you have in common; they are not search results, and stay inside the card they belong to rather than becoming rows of their own.

`total` is LinkedIn's own stated result count, and is null on a current page because the people-search UI states none. `hasMore` is true when paging stopped on `maxPages` rather than on a page that brought nothing new, and `cursor` is then the page to resume at.

### Filter keys

Keys are LinkedIn's own, from the people-search filter bar. A key outside this list is refused rather than sent, because LinkedIn discards a key it does not know and then answers for a filter that never applied.

Free text (sent as given): `keywords`, `firstName`, `lastName`, `title`, `company`, `schoolFreetext`, `origin`.

Filters (sent as a list, one value or several): `network`, `geoUrn`, `schoolFilter`, `connectionOf`, `followerOf`, `currentCompany`, `pastCompany`, `industry`, `serviceCategory`, `profileLanguage`, `eventAttending`, `activelyHiringForJobTitles`, `companyHQBingGeo`, `companySizeV2`, `seniorityV2`, `openToVolunteer`, `functionV2`.

Exercised against a live account: `keywords`, `connectionOf`, `network` (2026-08-14), and `geoUrn`. The rest are names LinkedIn's own filter bar sends and have not been run from here — treat a result filtered on one of them as unconfirmed until it is. Narrow by role with `title`; `titleFreeText` is not a LinkedIn key and was silently discarded whenever it was used.

`connectionOf` takes `ACoAA...` ids, one or several, from `auth-parse-profile`'s `urn` field or from a previous result's `profile_id` (a full URN works; the prefix is stripped). A vanity slug is refused, because LinkedIn answers one with an empty result set rather than an error. `network` is `["F"]` for first-degree, `["F","S"]` to include second. What LinkedIn returns for a third party's network is gated: first-degree means the connections you share with them, always available; second-degree is populated only when that person is your own first-degree and has not hidden their list.

### What a call costs

LinkedIn meters people search per account per calendar month, does not report how much remains, and does not error when the meter runs out — it returns a handful of results with an ordinary page title. `state: "metered"` is that condition, detected from LinkedIn's own paywall marker rather than banner text, so it holds in any interface language. Treat a metered result set as a fragment, never as "few matches".

`cost` reports what the call spent, queries and page reads counted apart.

Naming several people in `connectionOf` costs one search each. LinkedIn reads only the **first** id in that filter and discards the rest, so several ids in one query answer for one person while looking like they answered for all: measured 2026-08-14, two first-degree members with disjoint mutual sets of 4 and 5 returned, named together, exactly the first one's 4. Reading them one query each and merging is therefore the only way to cover several networks. Batching people into one call still beats separate calls, since the keyword, the merge and the attribution are shared, and it is not free.

Pagination is `"page"` and `"maxPages"`, default one page, ten results a page. LinkedIn stops at 100 pages and serves page one again beyond that, so a request crossing the ceiling is refused rather than sent.

### Search variants for hard-to-find people (vary the quoted terms)

Try in order if no results:

1. `Name City` — `Sidney%20Lin%20Singapore`
2. `Name Company` — `Sidney%20Lin%20A%20Firm%20Foundation`
3. `Name Role City` — `Sidney%20Lin%20property%20Singapore`
4. Alternative romanisations — Lin (Mandarin), Lim (Hokkien), Lam (Cantonese) are the same surname
5. `"Company" City` — search by company to find employees, then match
6. `Role Organisation City` — `Chairman%20Distinction%20ASME%20Singapore`

A search returning zero results does not mean the person has no LinkedIn. Try all variants before declaring "not found".

## 2. Parse a profile

```bash
browser-serialiser linkedin.com/auth-parse-profile USERNAME            # complete (default)
browser-serialiser linkedin.com/auth-parse-profile USERNAME --quick     # topcard only, one navigation
```

`USERNAME` is the vanity slug or a full profile URL. By default this fetches the
topcard plus the Experience and Skills details pages and the About, emitting a
structured YAML record: name, vanity slug, the profile URN
(`urn:li:fsd_profile:ACoAA...`, the owner's, found as the dominant id in the
page's data payload), headline, location, `current_company`, `experience` (a list
of entries with `title`/`company`/`start`/`end`/`current`), `skills`, `about`,
evidence blocks, and a **`coverage:`** block. The record is the envelope's `result`, so a caller writing it to `<slug>.yaml` reads that key rather than redirecting stdout.

`coverage` is the honesty contract: each section is `fetched`, `not_found`
(the page was read and the section was absent — e.g. a profile with no skills
emits `skills: []` with `skills: fetched`), or `not_fetched` (never read).
`current_company` comes from the ongoing position in Experience, not guessed from
the headline; it is `null` only when Experience was read and no role is ongoing,
and `not_fetched` under `--quick`. So a caller can always tell a real empty from
an unread one and never reads partial data as a complete profile.

From overseers-desk/magazines, MIT.

More from overseers-desk/magazines

Signals

Stars
1
Forks
0
Open issues
36
Last commit (this folder)
Aug 23, 2026 (2 months ago)

From GitHub (fetched Oct 24, 2026).

Details

License
MIT
First seen
Oct 24, 2026
Last checked
Oct 24, 2026

More from overseers-desk

Cite this page

linkedin skill. Agent Mart. https://agentmart.dev/skills/overseers-desk-linkedin (last updated 2026-10-24)