# LinkedIn Job Postings Scraper - Salary, Company, Easy Apply (`snow_leo_data/linkedin-jobs-scraper`) Actor

4646 unique jobs from one keyword, 4.65 times LinkedIn's 1000-job search ceiling. Scrape LinkedIn job postings with the full job description, 47 fields per posting, no login. Employment type, seniority and workplace filters are applied here, because LinkedIn ignores 8 of its own URL parameters.

- **URL**: https://apify.com/snow\_leo\_data/linkedin-jobs-scraper.md
- **Developed by:** [Snow Leo Data](https://apify.com/snow_leo_data) (community)
- **Categories:** Jobs, Lead generation, MCP servers
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$1.49 / 1,000 jobs

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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 Jobs Scraper

Scrape LinkedIn job postings without a login, a cookie, a proxy or a browser.
Title, company, location, pay range, employment type, seniority, job function,
industries, applicant count and the full job description - **47 fields** per
posting, straight from LinkedIn's public guest pages.

Two things make this Actor different from the others in the niche, and both are
measurements you can repeat, not claims:

1. **Its filters actually filter.** LinkedIn's guest search accepts `f_JT`
   (employment type), `f_WT` (workplace), `f_E` (experience), `f_SB` (salary),
   `f_F`, `f_I` and `sortBy` - and acts on none of them. A scraper that passes
   those straight through hands you a filter that quietly does nothing. This
   one applies them itself, on the job page where the values really live.
2. **It goes past LinkedIn's 1000-job ceiling.** One query can never return
   more than 1000 jobs - `start=1000` is HTTP 400. Walking a ladder of
   freshness windows turned 1000 jobs into **4646 unique jobs** from the same
   single query.

#### What you get

Every row carries, when LinkedIn publishes it:

| Group | Fields |
| --- | --- |
| Identity | `job_id`, `job_url`, `apply_url`, `found_via`, `rank`, `scraped_at` |
| Role | `title`, `employment_type`, `seniority_level`, `job_function`, `industries` |
| Employer | `company`, `company_url`, `company_slug`, `company_logo` |
| Place | `location`, `city`, `region`, `country`, `workplace_type`, `workplace_signal` |
| Time | `posted_date`, `posted_ago`, `is_new_posting`, `hiring_badge` |
| Pay | `salary_min`, `salary_max`, `salary_currency`, `salary_period`, `salary_text` |
| Interest | `applicants`, `applicants_precision`, `applicants_text` |
| Apply | `apply_type`, `easy_apply` |
| Job poster | `poster_name`, `poster_url`, `poster_headline` |
| Text | `description`, `description_html`, `description_markdown`, `description_length` |
| Contacts | `contact_emails`, `contact_phones`, `contact_links` |
| Monitoring | `change_type`, `changed_fields` |

How often each one is really filled, counted over 30 live job pages from five
countries: title, company, company page, location, posting date, the four job
criteria and the description - **30 of 30**. Applicant count - **14 of 30**.
A published pay range - **2 of 30**. Apply route - 30 of 30, of which 27
external and 3 Easy Apply. A named job poster with a link to their profile -
**4 of 24**, and included in the row price, where the nearest competitor
charges $0.02 extra per job for it.

A second, larger census over the **100 rows** of one live cloud run, all of
them United States postings: applicant count filled on **77**, a published pay
range on **16**, a named job poster on **18**. The two samples disagree
because they are different slices of LinkedIn - which is the point of showing
both rather than one flattering number. What holds in both: pay is the field
employers publish least often, so do not build a pipeline that assumes it. Nothing is invented to fill a gap: a field LinkedIn
does not publish is `null`, and a numeric field is never an empty string.

#### Getting past the 1000-job ceiling

`start=1000` answers HTTP 400. A full walk of `software engineer` in the
`United States`, from start 0 to start 990, returned 1000 unique job ids and
that was the end of that query.

`f_TPR=r<seconds>` is one of the parameters LinkedIn does honour, and it takes
any number of seconds - not just the four the LinkedIn interface offers. Each
window gets its own top-1000, and the windows are not nested slices of one
result. Measured on that same query:

| Freshness window | Unique jobs | New ones | Running total |
| --- | --- | --- | --- |
| no window | 1000 | 1000 | 1000 |
| past hour | 913 | 910 | 1910 |
| past 4 hours | 950 | 707 | 2617 |
| past 12 hours | 997 | 638 | 3255 |
| past 24 hours | 948 | 337 | 3592 |
| past 3 days | 1000 | 487 | 4079 |
| past week | 1000 | 389 | 4468 |
| past month | 993 | 178 | 4646 |

That is **4.65 times** the ceiling of one query, out of one keyword in one
country, for 838 requests. Add a second keyword or a second location and each of them brings its
own ladder. The ladder is on by default and never reaches outside the freshness
you asked for: choose "Past 24 hours" and the widest rung is 24 hours.

#### The filters LinkedIn ignores, and what this Actor does about them

Measured three independent ways on 2026-09-17: comparing the returned job ids
against the same query without the parameter, combining the parameter with one
that does work, and reading the job pages of what came back.

| Parameter | What it claims | What it does |
| --- | --- | --- |
| `keywords`, `location`, `geoId` | search | works |
| `f_TPR` | posted within | works - the id set moves with the window |
| `f_C` | one company | works - 0 of 30 jobs overlap the unfiltered search |
| `f_AL` | Easy Apply only | works - 0 of 30 overlap |
| `f_EA` | under 10 applicants | works - 1 of 10 overlap |
| `distance` | radius in miles | works - 5 miles around Chicago returns Chicago, 100 miles reaches Milwaukee |
| `f_JT` | employment type | **ignored** - 30 of 30 identical to no filter |
| `f_WT` | workplace type | **ignored** - 30 of 30 identical |
| `f_E` | experience level | **ignored** - 30 of 30 identical |
| `f_SB` | salary band | **ignored** - 30 of 30 identical |
| `f_F` | job function | **ignored** - 10 of 10 identical |
| `f_I` | industry | **ignored** - 10 of 10 identical |
| `f_BE` | benefits | **ignored** - 30 of 30 identical |
| `sortBy` | relevance or date | **ignored** - 30 of 30 identical |

The ignored ones were retried as `"I"`, as `INTERNSHIP`, as `1,2`, under
`geoId` instead of `location`, against the HTML search page instead of the API,
and together with the working `f_TPR`. The id set never moved. The job pages
settle it: a search with `f_JT=I` for internships returned six jobs whose pages
all say "Employment type: Full-time" - the same six the unfiltered search
returned.

So this Actor asks LinkedIn only for what LinkedIn honours, and applies
employment type, seniority, workplace type, apply route and salary itself. When
one of those filters is on, the job page is fetched automatically even if you
turned descriptions off - otherwise the filter would read an empty field and
drop every row.

#### The location trap

LinkedIn does not refuse a place it does not know. It silently answers with
jobs from somewhere else. Measured: `location=Zzqqxxnotaplace` returns HTTP 200
and the same ten United States jobs as `location=United States` - zero of them
in the place that was asked for.

"Check that the location is the one you asked for" is on by default. It drops
rows that do not answer your location, puts the share that matched in the run
report, and if almost nothing matched it names the reason instead of handing
you another country's jobs. The comparison is loose on purpose: "Berlin,
Germany" accepts "Berlin, Berlin, Germany", and "United States" accepts
"Chantilly, VA".

#### Monitor mode: pay only for what is new

Turn on **Monitor mode** and the Actor remembers every job it has delivered.
The next run returns only postings that are new or have changed, each labelled
`change_type` NEW / UPDATED / UNCHANGED, and `changed_fields` says what moved
and what it was before. Watched values: title, company, location, employment
type, seniority, workplace type, pay range, apply route, applicant count,
description length and posting date. The scrape timestamp is deliberately not
watched - otherwise every row would look changed on every run.

A monitor run with nothing to report writes zero rows, and that is correct
behaviour, not a failure: you are not charged for jobs you already have.

#### An empty result always says why

A green run with no rows is the worst thing a scraper can do to you. This one
always names the reason in the log and in the `empty_reason` field of the
REPORT record. Reasons that are decided by your input alone are checked before
a single byte is downloaded, so you are not charged time for a result that was
never possible:

`no-target-given`, `no-search-terms-and-no-locations`, `all-job-ids-invalid`,
`employment-type-filter-matches-nothing`, `seniority-filter-matches-nothing`,
`workplace-filter-matches-nothing`, `date-not-understood`,
`date-window-is-empty`, `freshness-window-excludes-the-date-window`,
`easy-apply-only-with-external-apply-filter`, `salary-range-is-empty`,
`charge-limit-reached`.

Reasons that only become visible after the crawl: `filtered-out`,
`nothing-changed`, `source-returned-nothing`, `location-not-recognised`,
`job-not-found`, `source-rate-limited`, `all-queries-unreachable`.

The REPORT record also carries the per-filter drop counts, the queries that
were run, how many cards were seen, how many job pages were opened, how many
duplicates were skipped and the HTTP counters.

#### Modes

- **Search by keyword and location** - the usual way. Every keyword and every
  location is a separate query with its own ceiling.
- **Job URLs or IDs** - open exactly the postings you list. Accepts a full
  `/jobs/view/` address or the bare job id, and can take them from another
  Actor's dataset.
- **One company** - jobs of a single employer. LinkedIn's guest search filters
  by numeric company id only; a company slug in that parameter is ignored by
  the source, so this Actor refuses it loudly instead of quietly returning the
  whole search.

#### Honest limitations

- **The external apply link is not available to a guest.** When a job applies
  on the employer's own site, LinkedIn puts that address behind the login wall.
  The row says `apply_type: EXTERNAL` and `apply_url` is the LinkedIn job page.
  Only a logged-in scraper can get the employer URL, and this Actor does not
  log in to anything.
- **Pay is rarely published.** 2 job pages in 30 carried a range. A salary
  filter therefore leaves jobs with no published pay in the result by default -
  absence of data is not a mismatch. Turn on "Require a published salary" to
  drop them, knowing that drops about 28 of every 30 postings.
- **Workplace type is inferred, and every row says so.** LinkedIn gives a guest
  no workplace field and ignores `f_WT`. The answer is read from the location,
  then the title, then the description, and `workplace_signal` names which one
  it came from - so you can keep only the rows you trust.
- **`sortBy` does nothing.** Not this Actor's choice: the source ignores it.
  Order inside a query is LinkedIn's relevance order; `rank` records it.
- **There is no automatic split by city.** The reference Actor in this niche
  offers one as its way past the ceiling; here the freshness ladder does that
  job and was measured at 4.65x, and several locations can be listed by hand,
  each with its own ceiling.
- **Company headcount, followers and firmographics are not included.** Those
  live on the company page, not the job page, and would be an extra request and
  an extra charge per company.
- **One query is capped at 1000 jobs by LinkedIn, not by this Actor.** The
  freshness ladder multiplies that, but there is no way to enumerate every job
  in a country.

#### Paste a LinkedIn search URL

The URL in your clipboard works as input. Its query string is unpacked into
the plain fields - keyword, location, geo id, radius, freshness window, Easy
Apply, under 10 applicants - rather than forwarded as-is. Forwarding it would
also forward the parameters LinkedIn ignores, and you would believe your
employment-type filter had been applied. Those parameters are listed in the
run report under `search_url_parameters_ignored_by_linkedin`, and the matching
fields of this Actor apply them for real.

#### Input in short

Search terms or LinkedIn search URLs, locations or geo ids, radius in miles,
freshness window, Easy Apply only, under 10 applicants only, the
ceiling ladder on or off, maximum results, a per-query quota, job ids to skip,
whether to open the job page,
description format (text, HTML, markdown, all three or none), contact
extraction, and the filters: employment type, seniority level, workplace type,
apply route, title contains / excludes, description contains, company contains,
location contains, minimum and maximum salary, require a published salary,
posted after / before, location verification, monitor mode, compact rows, drop
empty fields, parallel job pages.

`maxItems` limits what is **delivered**, not what is crawled. Because the
filters above run after download, stopping the crawl at the collected count
would hand you nothing whenever a filter is narrow.

#### FAQ

**Do I need a LinkedIn account, cookies or a proxy?**
No. The Actor uses only public guest endpoints that answer without any
authentication. It never logs in and never touches an account.

**Why does it open a second page per job?**
The search card carries 12 fields. The employment type, seniority, job
function, industries, applicant count, pay range and the description live on
the job page only. Turn "Open the job page" off for a fast, cheap list of
cards; leave it on for the full row.

**Will the same job be charged twice?**
No. Jobs are deduplicated by LinkedIn's own job id across every query and every
freshness window in the run, and the report counts how many duplicates were
skipped. Titles are cleaned of `&nbsp;` before comparison, because a title with
an invisible leading space does not match a clean one.

**How many results can I get?**
One query tops out at 1000. The freshness ladder took that to 4646 unique jobs
from one keyword in one country. More keywords and more locations multiply it
further.

**Why is `applicants` sometimes 25 with `applicants_precision: at-most`?**
Because LinkedIn wrote "Be among the first 25 applicants", which means fewer
than 25 people applied - a ceiling, not a count. "Over 200 applicants" gives
`at-least`. The row tells you which it is instead of pretending to a number.

**What happens if the run is interrupted?**
Rows already written stay written, and memory is saved in a `finally` block.
Nothing is marked delivered before it has actually been delivered, so the next
run continues rather than starting over or skipping what never arrived.

**Can I feed job ids from another Actor?**
Yes - give the dataset id and the field name in the input, and this Actor will
open those postings.

**Is this legal?**
It reads pages LinkedIn serves to anyone with no account and no login, exactly
as a browser would. You are responsible for how you use the data, including
under GDPR and LinkedIn's own terms.

# Actor input Schema

## `mode` (type: `string`):

Search - walk LinkedIn job search by keyword and location. Job URLs - open exactly the postings you list. Company - jobs of one employer by numeric LinkedIn company id.

## `searchTerms` (type: `array`):

Job titles or keywords. Each term is a separate search with its own 1000-row ceiling, so three terms reach three times as far as one.

## `locations` (type: `array`):

Locations spelled as LinkedIn spells them: 'United States', 'Berlin, Germany', 'Greater London Area'. A place LinkedIn does not know is NOT refused - it silently answers with jobs from somewhere else, so keep the location check on.

## `distance` (type: `integer`):

Radius around the location. LinkedIn honours this one: measured on nurse jobs in Chicago, 5 miles returned Chicago only, 25 miles reached Oak Brook and Deerfield, 100 miles reached Milwaukee. Leave empty for LinkedIn's default.

## `geoIds` (type: `array`):

LinkedIn numeric geo ids, for example 103644278 for the United States. Used instead of the location text when you already know the id.

## `searchUrls` (type: `array`):

Paste LinkedIn job-search URLs. The query string is unpacked into the fields below - keyword, location, geo id, radius, freshness, Easy Apply. Parameters LinkedIn accepts but ignores (f\_JT, f\_WT, f\_E, f\_SB, sortBy) are named in the run report instead of being forwarded as a filter that does nothing.

## `jobUrls` (type: `array`):

Used in 'Job URLs or IDs' mode. Accepts a full /jobs/view/ address or the bare job id.

## `skipJobIds` (type: `array`):

Job ids you already have. They are never fetched and never charged for.

## `companyUrls` (type: `array`):

Used in 'One company' mode. LinkedIn's guest search filters by NUMERIC company id only - a company slug is ignored by the source, so this Actor rejects it loudly instead of returning the whole search.

## `freshness` (type: `string`):

The only date filter LinkedIn's guest search really honours. With 'Go past the 1000-row ceiling' on, this becomes the widest rung of the freshness ladder.

## `beyondCeiling` (type: `boolean`):

One LinkedIn query can never return more than 1000 jobs - start=1000 is HTTP 400. This walks a ladder of freshness windows, each with its own thousand. Measured on one query: 1000 rows without it, 1910 with the first extra rung alone.

## `maxItems` (type: `integer`):

How many rows you want. It limits what is DELIVERED, not what is crawled - filters this Actor applies after download would otherwise empty the result. Kept at 25 by default so a first run finishes in about a minute; raise it for a full pull.

## `maxItemsPerQuery` (type: `integer`):

A quota per search term and location, so one broad keyword cannot eat the whole order and turn a five-keyword run into a one-keyword scrape. 0 means no per-query quota.

## `easyApplyOnly` (type: `boolean`):

Asks LinkedIn itself for on-site applications (f\_AL). This one the source honours: overlap with the unfiltered search was 0 of 30.

## `under10Applicants` (type: `boolean`):

Asks LinkedIn for postings with fewer than ten applicants (f\_EA). Honoured by the source: overlap with the unfiltered search was 1 of 10.

## `includeDescription` (type: `boolean`):

Fetch the full posting: description, employment type, seniority, job function, industries, pay range and applicant count. One extra request per job. Turned on automatically when a filter needs a field that only the job page carries.

## `descriptionFormat` (type: `string`):

Plain text is the default. HTML keeps the employer's markup, markdown keeps headings, bold and bullets. 'All three' returns every form; 'none' drops the description field entirely.

## `extractContacts` (type: `boolean`):

Emails, phone numbers and links the employer wrote into the posting text.

## `employmentTypes` (type: `array`):

Full-time, Part-time, Contract, Temporary, Internship, Volunteer, Other. LinkedIn's guest search IGNORES its own f\_JT parameter, so this Actor applies the filter on the job page instead.

## `seniorityLevels` (type: `array`):

Internship, Entry level, Associate, Mid-Senior level, Director, Executive, Not Applicable. Also ignored by the source (f\_E) and applied here.

## `workplaceTypes` (type: `array`):

Remote, Hybrid, On-site. LinkedIn does not give a guest a workplace field at all and ignores f\_WT, so this is read from the location and the title, and every row says in workplace\_signal where the answer came from.

## `applyType` (type: `string`):

Easy Apply means the application form is on LinkedIn. External means a form on the employer's own site - and LinkedIn does not show a guest that address, so apply\_url stays the LinkedIn job page.

## `titleIncludes` (type: `array`):

Keep only jobs whose title contains one of these. A job with no title is kept - absence of data is not a mismatch.

## `titleExcludes` (type: `array`):

Drop jobs whose title contains any of these, for example 'senior' or 'intern'.

## `descriptionIncludes` (type: `array`):

Needs the job page, so it turns 'Open the job page' on by itself.

## `companyIncludes` (type: `array`):

Keep only jobs whose employer name contains one of these.

## `locationContains` (type: `string`):

Keep only jobs whose location text contains this, for example 'CA' or 'Greater London'.

## `salaryMin` (type: `integer`):

Only 2 job pages in 30 publish a pay range, so by default a job with no published pay PASSES this filter - absence of data is not a mismatch. Use 'Require a published salary' to drop them.

## `salaryMax` (type: `integer`):

Drop jobs whose pay range starts above this. Jobs with no published pay are kept.

## `requireSalary` (type: `boolean`):

Drops every job without a pay range. Measured: that is about 28 of every 30 postings.

## `requirePoster` (type: `boolean`):

Keep only postings that name the recruiter who opened them, with a link to their LinkedIn profile. Measured: 4 job pages in 24 carry one. Included in the row price - the nearest competitor charges $0.02 extra per job for this.

## `postedAfter` (type: `string`):

Keep only jobs posted on or after this date (YYYY-MM-DD). The check runs on the posting date LinkedIn prints on the card.

## `postedBefore` (type: `string`):

Keep only jobs posted on or before this date (YYYY-MM-DD).

## `verifyLocation` (type: `boolean`):

LinkedIn answers an unknown place with jobs from elsewhere and no warning. This drops those rows and names the reason instead of handing you another country.

## `onlyNew` (type: `boolean`):

Remembers what was delivered in earlier runs and returns only postings that are new or have changed. Every row carries change\_type NEW / UPDATED / UNCHANGED and changed\_fields.

## `emitUnchanged` (type: `boolean`):

In monitor mode, also return jobs that have not changed since the last run. Off by default, so you pay only for what is new.

## `compact` (type: `boolean`):

17 key fields instead of the full set - for agents and spreadsheets.

## `dropEmptyFields` (type: `boolean`):

Remove null and empty fields from every row - smaller payloads for agents.

## `concurrency` (type: `integer`):

How many job pages to open at once. LinkedIn answers HTTP 429 after roughly twenty fast requests, so the client keeps an even pace no matter what is set here.

## `inputDatasetId` (type: `string`):

Dataset id of another Actor run to take job ids from.

## `jobIdField` (type: `string`):

Name of the field holding the job id inside that dataset.

## Actor input object example

```json
{
  "mode": "search",
  "searchTerms": [
    "python developer"
  ],
  "locations": [
    "United States"
  ],
  "geoIds": [],
  "searchUrls": [],
  "jobUrls": [],
  "skipJobIds": [],
  "companyUrls": [],
  "freshness": "any",
  "beyondCeiling": true,
  "maxItems": 25,
  "maxItemsPerQuery": 0,
  "easyApplyOnly": false,
  "under10Applicants": false,
  "includeDescription": true,
  "descriptionFormat": "text",
  "extractContacts": false,
  "employmentTypes": [],
  "seniorityLevels": [],
  "workplaceTypes": [],
  "applyType": "any",
  "titleIncludes": [],
  "titleExcludes": [],
  "descriptionIncludes": [],
  "companyIncludes": [],
  "locationContains": "",
  "requireSalary": false,
  "requirePoster": false,
  "postedAfter": "",
  "postedBefore": "",
  "verifyLocation": true,
  "onlyNew": false,
  "emitUnchanged": false,
  "compact": false,
  "dropEmptyFields": false,
  "concurrency": 4,
  "inputDatasetId": "",
  "jobIdField": "job_id"
}
```

# Actor output Schema

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

All collected 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 = {
    "searchTerms": [
        "python developer"
    ],
    "locations": [
        "United States"
    ],
    "maxItems": 25
};

// Run the Actor and wait for it to finish
const run = await client.actor("snow_leo_data/linkedin-jobs-scraper").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 = {
    "searchTerms": ["python developer"],
    "locations": ["United States"],
    "maxItems": 25,
}

# Run the Actor and wait for it to finish
run = client.actor("snow_leo_data/linkedin-jobs-scraper").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 '{
  "searchTerms": [
    "python developer"
  ],
  "locations": [
    "United States"
  ],
  "maxItems": 25
}' |
apify call snow_leo_data/linkedin-jobs-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,snow_leo_data/linkedin-jobs-scraper"
        }
    }
}
```

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/6m6TgFFjoPWLgcrUq/builds/TmBTnZvyd48CUdvlS/openapi.json
