All platforms

LinkedIn Jobs API

Query LinkedIn job postings through one API — and why LinkedIn is a search, not a board, and what that changes about the data you get.

?source=linkedinsource reference

LinkedIn is not an ATS. Every other source on this site is an employer's own job board — the Greenhouse or Ashby instance a company runs for itself. LinkedIn is a job board: a posting here was aggregated by LinkedIn, and the company didn't publish it to LinkedIn's API the way it publishes to its own careers page. That difference isn't pedantry — it changes how the data is collected, how fresh it is, and whether you can trust the apply link.

It's the second-largest source in this dataset at 87,462 postings. Read the live figure from /v1/meta.

Try it#

BASH
curl -G https://api.hyperjobs.io/v1/jobs \
  -H "Authorization: Bearer $HYPERJOBS_KEY" \
  --data-urlencode "source=linkedin" \
  --data-urlencode "time_frame=24h" \
  --data-urlencode "limit=2"
Illustrative — the shape, not a live response
{
  "data": [
    {
      "id": "linkedin:4127883941",
      "title": "Senior Product Manager",
      "company": {
        "slug": "orbital-payments",
        "name": "Orbital Payments",
        "domain": "orbitalpayments.com",
        "website": "https://orbitalpayments.com",
        "logo": "https://media.licdn.com/dms/image/…",
        "industry": "Financial Services",
        "employee_count": 900
      },
      "location": {
        "remote": true,
        "countries": ["United States"],
        "cities": ["San Francisco"],
        "regions": null,
        "raw": ["San Francisco, CA"]
      },
      "employment_type": ["FULL_TIME"],
      "work_arrangement": "Remote",
      "seniority": "Mid-Senior level",
      "salary": {
        "min": 150000,
        "max": 350000,
        "currency": "USD",
        "unit": "YEAR",
        "annual_min": 150000,
        "annual_max": 350000,
        "summary": "Base pay range: $150,000.00/yr - $350,000.00/yr"
      },
      "skills": ["product strategy", "payments"],
      "taxonomies": ["Product"],
      "apply_url": "https://www.linkedin.com/jobs/view/4127883941",
      "url": "https://www.linkedin.com/jobs/view/4127883941",
      "source": "linkedin",
      "posted_at": "2026-07-14T08:30:00.000Z",
      "expires_at": null,
      "updated_at": "2026-07-14T09:05:22.000Z"
    }
  ],
  "total": 2,
  "limit": 2,
  "offset": 0
}

How LinkedIn actually works#

There's no board to fetch. LinkedIn's public guest endpoints are a search:

BASH
# Search — returns HTML cards, job ids in data-entity-urn
curl "https://www.linkedin.com/jobs-guest/jobs/api/seeMoreJobPostings/search?keywords=&geoId=103644278&f_TPR=r86400&start=0"
 
# Detail — HTML, the full description is in .show-more-less-html__markup
curl "https://www.linkedin.com/jobs-guest/jobs/api/jobPosting/<id>"

Both are server-rendered HTML, unauthenticated, and need no browser. The job id lives in data-entity-urn="urn:li:jobPosting:<id>". The search cards carry free per-card metadata — title, company, location, posted date — so a metadata-only pass costs nothing beyond the search itself.

The two hard limits#

Everything about collecting LinkedIn follows from these:

LimitValue
Results per search~1,000start caps around 975
Requests per IP before a 429~20–25 rapid ones

The first one is the interesting one. You cannot enumerate LinkedIn. Any query returning more than about a thousand jobs is truncated, and nothing tells you it was truncated — you just get a thousand.

So coverage means slicing the query space until each slice fits under the cap, then unioning the slices with global id dedup. The facets are the knives: geoId, f_TPR (time posted), f_E (experience), f_JT (job type), f_WT (workplace type), f_C (company).

f_TPR is the one that makes it tractable. Run f_TPR=r86400 — posted in the last 24 hours — and each day's new jobs per keyword × geo is comfortably under 1,000. You get complete fresh coverage, cheaply, indefinitely. That's why LinkedIn here is refreshed as an hourly delta of the last hour's postings rather than by diffing a board: there is no board to diff.

What the detail page gives you#

The guest fragment carries a structured "job criteria" list — seniority level, employment type, job function, industries — so you get employment type without inference. LinkedIn's own compensation block is on roughly 15% of postings, mostly US pay-transparency states:

HTML
<section class="compensation">
  <h3 class="compensation__heading">Base pay range</h3>
  <div class="salary compensation__salary">$150,000.00/yr - $350,000.00/yr</div>

That's ground truth — cleaner than mining the prose, and present on some postings whose description omits pay entirely.

What bites you#

`apply_url` is not the employer's form on this source

Everywhere else here, apply_url is the employer's real application form, because the posting came from the employer's own board. A linkedin posting's apply_url points wherever LinkedIn points.

Some LinkedIn postings are "Easy Apply" — you apply on LinkedIn without leaving. Others redirect offsite. When a posting matters enough to verify, check source before assuming the URL is the real ATS. Sources has the full attribution story.

The detail page gives a relative date, not a timestamp. 3 days ago, 2 weeks ago. There's a datetime attribute on the search card, which is why the card metadata is worth keeping — the detail alone forces you to reconstruct an approximate date from prose, and "3 days ago" has a day of slack in it either way.

The default logo is a placeholder. Logos on static.licdn.com are LinkedIn's generic grey building, not the company's mark. Store it and every logo-less company gets a fake logo instead of falling back to its domain favicon. Real logos are on media.licdn.com.

Not Applicable is a real seniority value. LinkedIn emits it as a placeholder. It means nothing and shouldn't be stored as a level.

A thousand results looks exactly like all the results. Worth repeating, because it's the failure that produces a confident, wrong dataset. If your slice returns ~980+ ids, there are more jobs than the query can return, and the slice needs splitting.

Or use ours#

LinkedIn is the source where "just scrape it" is furthest from the truth. There's no endpoint that returns a company's jobs. There's a search with a hard cap, a per-IP throttle that trips in about twenty requests, and a query space you have to slice by geography, recency, seniority and job type to get underneath the cap — then union with global dedup and re-slice anything that comes back at the ceiling.

You get that already run: an hourly delta, structured pay where LinkedIn published it, employment type and seniority from the criteria block, placeholder logos and placeholder seniorities dropped, and the company join.

And you get it deduplicated against the employer's own board. This is the one that matters most on this source: a role on both a company's Greenhouse board and LinkedIn is collapsed to one row. Which means ?source=linkedin&limit=1 doesn't count "jobs on LinkedIn" — it counts jobs where LinkedIn won dedup. Any per-source breakdown you publish is a breakdown of surviving rows, and it's worth saying so out loud.

Quickstart · Pricing

Every source above normalises onto the same schema, with the hiring company already joined onto each posting.

Get an API key