# LinkedIn Jobs Scraper (`cyprusapi/linkedin-jobs`) Actor

Anonymous LinkedIn job search (no cookies, no login): typed salary, applicants, seniority, ISO posting dates, provenance block on every item — plus a job-alert mode with cross-run dedupe: scheduled runs push only jobs never seen before. Errors are never charged.

- **URL**: https://apify.com/cyprusapi/linkedin-jobs.md
- **Developed by:** [Torchtechnology LTD](https://apify.com/cyprusapi) (community)
- **Categories:** Jobs, Lead generation, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.50 / 1,000 job scrapeds

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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

### What does LinkedIn Jobs Scraper do?

LinkedIn Jobs Scraper extracts **typed, provenance-stamped job postings** from [LinkedIn Jobs](https://www.linkedin.com/jobs/) — **anonymously, with no cookies and no LinkedIn account** — using LinkedIn's public guest search. Search by **keywords × locations** with a date-posted filter, and get clean JSON: title, company, location, **ISO posting date**, applicants count, **typed salary** (min/max/currency/period, where the company publishes one), seniority, employment type, and — opt-in — the full **description text**. Every item carries an **audit-proof provenance block** (source URL, HTTP status, SHA-256 content hash) and a **per-field coverage status** that never invents a value.

Its standout feature is **alert mode**: give your search a `watchlistId`, schedule the actor daily, and each run pushes **only jobs never seen before** — a job-alert delta feed as a data product, with true **cross-run deduplication** that persists between runs.

The actor runs on the Apify platform, so you get **API access, scheduling, integrations, proxy rotation, and run monitoring** out of the box — no infrastructure to manage. Pricing is **pay per job** with **no platform-usage passthrough**, and **errors are never charged**. Try it on the Apify free tier in a couple of clicks.

### What makes this LinkedIn Jobs Scraper different?

Most LinkedIn job scrapers are commodity keyword scrapers that re-deliver the same postings on every run and leave you to dedupe them yourself. This one was built for pipelines you can defend — typed values, honest nulls, and a persistent alert history.

| Capability | LinkedIn Jobs Scraper (this actor) | [curious\_coder/linkedin-jobs-scraper](https://apify.com/curious_coder/linkedin-jobs-scraper) (12.7K users/30d, $2/1K, ★4.36) | [cheap\_scraper/linkedin-job-scraper](https://apify.com/cheap_scraper/linkedin-job-scraper) (9.5K users/30d, $0.70/1K, ★3.90) | [valig/linkedin-jobs-scraper](https://apify.com/valig/linkedin-jobs-scraper) (4.9K users/30d, $0.40/1K) | [fantastic-jobs/advanced-linkedin-job-search-api](https://apify.com/fantastic-jobs/advanced-linkedin-job-search-api) (3.1K users/30d, $5/1K, ★3.45) |
|---|---|---|---|---|---|
| **Alert mode**: only *new* jobs per run, history persists across runs | ✅ built in (`job-new` event) | ❌ | ❌ | ❌ | ❌ |
| **Cross-run dedupe** (watchlist store, job reported exactly once, ever) | ✅ | ❌ | ⚠️ within-run only ("remove duplicates") | ❌ | ❌ |
| **Typed salary** (min/max/currency/period as numbers, not strings) | ✅ `{"min":103000,"max":131000,"currency":"USD","period":"year"}` | ❌ string array | ❌ | ❌ | ⚠️ partial |
| **Applicants count as integer** | ✅ | ❌ string | ⚠️ | ❌ | ⚠️ |
| **Provenance block** (URL, HTTP status, SHA-256 hash per item) | ✅ on every item | ❌ | ❌ | ❌ | ❌ |
| **Per-field coverage status** (`obtained`/`absent`/`unknown`) | ✅ | ❌ silent nulls | ❌ | ❌ | ❌ |
| **Honest about LinkedIn's ignored filters** (workplaceType/jobType/experienceLevel) | ✅ documented + parsed fields for client-side filtering | ⚠️ keyword-conversion hack | ⚠️ | ❌ | ❌ |
| **Errors never charged**, pushed as transparency items | ✅ | ⚠️ | ⚠️ | ⚠️ | ⚠️ |
| **Pricing** | $1.50/1K jobs, $2/1K new-job alerts, **no usage passthrough** | $2/1K | $0.70/1K | $0.40/1K | $5/1K |

User counts, ratings, and prices from the Apify Store API, August 2026.

#### Job alerts as a data product (alert mode)

This is the feature none of the established competitors have. With `alertMode: true` and a `watchlistId` of your choice, the actor keeps a persistent history (`job-watchlist-<watchlistId>` key-value store) of every job it has ever reported for that watchlist. Each run then:

1. collects the current search results,
2. diffs them against the watchlist history,
3. pushes **only the jobs never seen before** (charged as `job-new`),
4. records everything it saw, so a posting is **never delivered twice** — not tomorrow, not next month.

Combine it with an **Apify Schedule** and you have a job-alert pipeline with zero glue code:

1. Run the actor once with `alertMode: true` and a `watchlistId` (e.g. `"backend-berlin"`). The first run reports everything currently matching — that is your baseline.
2. In Apify Console, open **Schedules → Create new**, pick this actor, keep the same input (same `watchlistId`), and set a cron like `0 7 * * *` (daily at 07:00).
3. From the second run on, your dataset contains **only new postings** — pipe them to Slack, email, a webhook, or your ATS via Apify integrations.

A good pairing is `datePosted: "pastWeek"` with a daily schedule: LinkedIn's date filter plus the watchlist diff keeps alerts fresh and duplicate-free.

### How much does it cost to scrape LinkedIn jobs?

The actor uses **pay-per-event** pricing — you pay only for delivered results, and there is **no platform-usage passthrough**: unlike some competing LinkedIn scrapers, we do not bill you for compute units or proxy traffic on top. The event prices below are the whole cost (exact prices are always shown on the actor's **Pricing** tab in Apify Console):

- **`job`** — **$0.0015 per job** ($1.50 per 1,000), charged once per successfully delivered job item. Detail hydration (`includeDescription`) is included — no add-on charge.
- **`job-new`** — **$0.002 per new job** ($2.00 per 1,000), charged in alert mode only, and only for jobs the watchlist has never reported.
- **`apify-actor-start`** — a one-time **$0.01 per run**.

**Errors are never charged.** Invalid inputs, blocked requests, and transport failures produce transparency items in the dataset at zero cost. Jobs that disappear (404) mid-run are reported, not billed.

#### Cost by volume (job event only, + $0.01 per run)

| Jobs per run | Standard mode | Alert mode (all new) |
|---|---|---|
| 100 jobs | **$0.16** | $0.21 |
| 1,000 jobs | **$1.51** | $2.01 |
| 10,000 jobs | **$15.01** | $20.01 |

The Apify **free tier** is enough to try the actor on a handful of searches before you scale up. Alert mode with a daily schedule typically costs a few cents per month per watchlist — you only ever pay for genuinely new postings.

### Who is this LinkedIn Jobs Scraper for?

| Audience | What they use it for |
|---|---|
| **Job seekers & career platforms** | **Scheduled job alerts** via alert mode — new matching postings as a clean delta feed, no duplicates, ever. |
| **Recruiters & staffing agencies** | Market mapping: who is hiring what, where, at which seniority — with typed salary benchmarks where published. |
| **HR tech & ATS builders** | A stable, typed ingestion feed (ISO dates, integer counts, structured salary) with provenance per item. |
| **Labor-market researchers** | Time-series of postings per keyword/location; the watchlist store doubles as a seen-set for longitudinal studies. |
| **Sales & lead-gen teams** | Hiring signals — companies posting relevant roles right now (`datePosted: past24h`, daily schedule). |

### How to scrape LinkedIn jobs with LinkedIn Jobs Scraper

1. Open the actor in [Apify Store](https://apify.com/store) and click **Try for free**.
2. On the **Input** tab, set **Keywords** (e.g. `["software engineer", "product manager"]`) and/or **Locations** (e.g. `["Berlin", "United States"]`).
3. Optionally set **Date posted** (`past24h` / `pastWeek` / `pastMonth`) and enable **Include description & details** for full text, salary, applicants, and seniority per job.
4. For a recurring alert, enable **Alert mode**, set a **Watchlist ID**, and attach an Apify **Schedule** (see above).
5. Click **Start**, then download your results from the **Output** or **Storage** tab — or pull them via the Apify API.

#### Using the Apify API (JavaScript)

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

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });

const run = await client.actor('CyprusAPI/linkedin-jobs').call({
    keywords: ['software engineer'],
    locations: ['Berlin'],
    datePosted: 'pastWeek',
    includeDescription: true,
    maxJobs: 50,
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

#### Using the Apify API (Python)

```python
from apify_client import ApifyClient

client = ApifyClient('<YOUR_APIFY_TOKEN>')

run = client.actor('CyprusAPI/linkedin-jobs').call(run_input={
    'keywords': ['software engineer'],
    'locations': ['Berlin'],
    'alertMode': True,
    'watchlistId': 'backend-berlin',
})

for item in client.dataset(run['defaultDatasetId']).iterate_items():
    print(item)
```

### Input

The actor accepts the following parameters (see the **Input** tab for the full form). Provide `keywords` and/or `locations` — every keyword is searched in every location.

| Field | Type | Description |
|---|---|---|
| `keywords` | array | Search keywords (job titles, skills, companies). Default `["software engineer"]`. |
| `locations` | array | Free-text locations as on linkedin.com/jobs (e.g. `"Berlin"`, `"United States"`). Defaults to the `country` name when empty. |
| `country` | string | ISO alpha-2 code (`US`, `DE`, …). Default location + geo-pinned proxy exit. Default `"US"`. |
| `datePosted` | string | `any` (default), `past24h`, `pastWeek`, `pastMonth`. The one filter LinkedIn's guest search verifiably honors. |
| `workplaceType` | string | `on-site` / `remote` / `hybrid`. **Currently ignored by LinkedIn** — see Known limitations. |
| `jobType` | string | `full-time` / `part-time` / `contract` / … **Currently ignored by LinkedIn** — use `includeDescription` and filter on the parsed `employmentType`. |
| `experienceLevel` | string | `entry` / `associate` / `mid-senior` / … **Currently ignored by LinkedIn** — filter on the parsed `seniority`. |
| `maxJobs` | integer | Global cap per run, deduplicated by job ID. Default `10`, max `1000`. |
| `includeDescription` | boolean | Fetch the detail page per job: `descriptionText`, `applicantsCount`, `salary`, `seniority`, `employmentType`, `jobFunction`, `industries`. Default `false`. |
| `alertMode` | boolean | Push only jobs never reported before for `watchlistId` (charged as `job-new`). Default `false`. |
| `watchlistId` | string | Persistent history identifier for alert mode. Default `"default"`. |
| `proxyConfiguration` | object | Optional — bundled geo proxies are included; enable only to force your own or Apify proxies. |
| `proxiesByCountry` | object | Optional geo-pinned proxies per ISO country code, tried first. |

### Output

One dataset item per job. List fields (title, company, location, posting date) come from the search cards; `includeDescription` adds detail-page fields. Every item carries `coverage` (per-field-group status) and `capture` (provenance). You can download the dataset in various formats such as JSON, HTML, CSV, or Excel.

```json
{
  "jobId": "4450182400",
  "title": "Global Product Manager, Hardware",
  "company": "Cricut",
  "companyUrl": "https://www.linkedin.com/company/cricut",
  "location": "South Jordan, UT",
  "workplaceType": null,
  "postedAt": "2026-08-12",
  "postedAtText": "1 week ago",
  "applicantsCount": null,
  "applicantsText": "Be among the first 25 applicants",
  "salary": { "min": 103000, "max": 131000, "currency": "USD", "period": "year", "raw": "$103,000.00/yr - $131,000.00/yr" },
  "seniority": "Mid-Senior level",
  "employmentType": "Full-time",
  "jobFunction": "Product Management",
  "industries": "Manufacturing",
  "benefits": ["Be an early applicant"],
  "descriptionText": "This range is provided by Cricut…",
  "jobUrl": "https://www.linkedin.com/jobs/view/4450182400",
  "promoted": null,
  "searchKeywords": "product manager",
  "searchLocation": "United States",
  "coverage": { "core": "obtained", "salary": "obtained", "applicants": "absent", "description": "obtained", "criteria": "obtained", "workplaceType": "unknown", "promoted": "unknown" },
  "capture": {
    "captured_at_utc": "2026-08-20T15:40:00+00:00",
    "url": "https://www.linkedin.com/jobs-guest/jobs/api/seeMoreJobPostings/search?keywords=product+manager&location=United+States&start=0",
    "http_status": 200,
    "content_hash": "sha256:9f2c…",
    "detail": { "url": "https://www.linkedin.com/jobs-guest/jobs/api/jobPosting/4450182400", "http_status": 200, "content_hash": "sha256:71ab…" }
  },
  "scrapedAt": "2026-08-20T15:40:01+00:00"
}
```

Fields follow a **three-state discipline**: a value means *observed*, `null` means *verifiably absent*, and `"unknown"` in the `coverage` block means *not observable anonymously*. `"Be among the first 25 applicants"`-style labels are kept in `applicantsText` but do **not** become a fake `applicantsCount`. Failed searches appear as transparency items with `error` and `errorDescription` and are never charged.

### Data table

| Field | Description |
|---|---|
| `jobId`, `jobUrl` | LinkedIn job ID and canonical, non-localized job URL (stable for dedupe) |
| `title`, `company`, `companyUrl`, `companyLogo` | Posting and company identity |
| `location` | Location as displayed on the posting |
| `postedAt`, `postedAtText` | ISO date from LinkedIn's `datetime` attribute, plus the raw relative text ("2 weeks ago") |
| `applicantsCount`, `applicantsText` | Integer applicant count where LinkedIn shows one ("Over 200 applicants" → `200`); raw label always kept |
| `salary` | Typed `{min, max, currency, period, raw}` from the company-provided pay range — `null` when the posting has none (most don't) |
| `seniority`, `employmentType`, `jobFunction`, `industries` | LinkedIn's job-criteria block (with `includeDescription`) |
| `benefits` | Card badges such as "Actively Hiring" / "Be an early applicant" |
| `descriptionText` | Full plain-text job description (with `includeDescription`) |
| `workplaceType`, `promoted` | **Not observable anonymously** — always `null`, marked `unknown` in coverage (see Known limitations) |
| `isNew`, `watchlistId` | Present in alert mode; every pushed item is new by construction |
| `searchKeywords`, `searchLocation` | The search combo that surfaced this job |
| `coverage` | Per-field-group status: `obtained` / `absent` / `unknown` |
| `capture` | Provenance: timestamp, source URL(s), HTTP status, SHA-256 content hash |

### Known limitations

We'd rather you know these up front:

- **LinkedIn currently ignores the `workplaceType`, `jobType`, and `experienceLevel` search filters.** Verified live on 2026-08-20: the guest search returns byte-identical result sets with and without `f_WT` / `f_JT` / `f_E`. We still forward them (LinkedIn may re-enable them) and the actor warns in the run log when you set them — but they do **not** filter results. The reliable path: enable `includeDescription` and filter client-side on the parsed `employmentType` and `seniority` fields. Only `datePosted` verifiably filters results today.
- **No structured workplace type or promoted/sponsored flag exists in LinkedIn's anonymous markup.** Rather than guessing from description text, both fields are `null` with coverage `unknown`. Any competitor showing these fields anonymously is inferring them.
- **Salary is rare and company-provided.** Only postings with a company-published pay range carry `salary`; in our live verification roughly a minority of US postings and almost no EU postings had one. `null` means *the posting shows no salary*, not "unknown salary".
- **`postedAt` is date-granularity.** LinkedIn's guest cards expose an ISO date, not a timestamp; `postedAtText` keeps the raw relative label.
- **Result cap ~1,000 per search.** LinkedIn's guest search stops paginating after about 100 pages (10 cards each); very broad queries ("manager", "United States") hit that wall. Narrow with keywords, locations, and `datePosted` — or use alert mode, where the daily delta is what matters.
- **No login-only data.** Job-poster profiles, "Easy Apply" internals, and similar login-gated datapoints are out of scope by design — this actor never uses cookies or accounts.

### Tips and advanced options

- **Start narrow, then fan out.** Prefer several specific keyword×location combos over one broad query — you get better relevance and stay far from LinkedIn's ~1,000-result ceiling.
- **Use `datePosted: "past24h"` with a daily schedule** for the tightest alert feed; use `pastWeek` if you want overlap safety (the watchlist dedupes it away anyway).
- **Alert mode baseline:** the first run of a new `watchlistId` reports everything currently matching. Keep `maxJobs` modest for that first run, then let the schedule deliver deltas.
- **Multiple independent alerts** = multiple schedules with different `watchlistId`s — each keeps its own history in its own store (`job-watchlist-<id>`).
- **Proxy fallback chain.** Searches are attempted in tiers: your own `proxiesByCountry` pool first, then the bundled geo pool (we cover it), then Apify datacenter, then Apify residential. Retries only happen on bot-detection/transport failures; blocked combos surface as uncharged transparency items.
- **Automate it.** Schedules, webhooks, and integrations (Slack, Google Sheets, Make/Zapier) are all available on the Apify platform — alert mode plus a schedule is the intended sweet spot.

### FAQ

#### Do I need a LinkedIn account or cookies?

No. The actor uses only LinkedIn's **public guest endpoints** — no login, no cookies, no account, ever. That also means there is no account-ban risk on your side, unlike cookie-based scrapers.

#### Will LinkedIn block the scraper?

LinkedIn rate-limits anonymous traffic, which is why each request goes through a tiered proxy fallback chain with a browser TLS fingerprint, and retries happen only on genuine bot-detection. Blocked searches surface as transparency items with `error` / `errorDescription` and are **never charged**.

#### How does alert mode differ from just scheduling any job scraper?

A plain scheduled scraper re-delivers the same postings every day and leaves deduplication to you. Alert mode keeps a **persistent cross-run history** per `watchlistId`: a job is delivered exactly once, on the first run that sees it. Your dataset becomes a true delta feed — and you only pay the `job-new` event for genuinely new postings.

#### Why are `workplaceType` and `promoted` always `null`?

Because LinkedIn's anonymous pages verifiably do not contain them (checked 2026-08-20). We report honest `null` + coverage `unknown` instead of inventing values — that is the design, not a bug.

#### Can I bring my own proxies?

Yes. Add endpoints via `proxyConfiguration.proxyUrls`, or pin **geo-located proxies per country** with `proxiesByCountry` (e.g. German exits for `locations: ["Berlin"]`) — your pool is always tried first.

#### Is scraping LinkedIn jobs legal?

Web scraping is legal when you extract **publicly available data** that is not behind a login. This actor scrapes only anonymous, public job-search pages. Job postings are company-published data, but postings can reference individuals (e.g. in description text) — personal data is protected by regulations such as GDPR in the EU, and you should have a legitimate purpose and legal basis for processing it. If you are unsure, consult a lawyer, and review LinkedIn's Terms of Service before scraping. Apify is not liable for how you use the extracted data.

#### Something isn't working — where do I report it?

Open the **Issues** tab on the actor's page in Apify Console and describe the problem, ideally with your input JSON and the run ID. Error items in the dataset (`error` / `errorDescription`) already tell you what went wrong for individual searches.

#### Can you build a custom LinkedIn data solution?

Yes — if you need different fields, company or profile data, or a fully managed pipeline, reach out via the Issues tab and ask about a custom solution.

# Actor input Schema

## `keywords` (type: `array`):

Search keywords (job titles, skills, companies). Each keyword is searched in each location. Provide keywords and/or locations.

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

Free-text locations as you would type them on linkedin.com/jobs (e.g. "Berlin", "United States", "Remote"). Defaults to the country below when empty.

## `country` (type: `string`):

ISO alpha-2 country code (e.g. US, DE, GB). Sets the default location when `locations` is empty and picks the geo-pinned proxy exit.

## `datePosted` (type: `string`):

Only jobs posted within this window. Verified to work with LinkedIn's current guest search.

## `workplaceType` (type: `string`):

On-site / remote / hybrid. NOTE: LinkedIn's guest search currently ignores this filter (verified 2026-08-20) — it is forwarded but results are not filtered. See README.

## `jobType` (type: `string`):

Employment type filter. NOTE: LinkedIn's guest search currently ignores this filter (verified 2026-08-20) — it is forwarded but results are not filtered. Enable `includeDescription` and use the parsed employmentType field for reliable filtering.

## `experienceLevel` (type: `string`):

Seniority filter. NOTE: LinkedIn's guest search currently ignores this filter (verified 2026-08-20) — it is forwarded but results are not filtered. Enable `includeDescription` and use the parsed seniority field for reliable filtering.

## `maxJobs` (type: `integer`):

Global cap on jobs per run, across all keyword×location combos (deduplicated by job ID).

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

Fetch the job detail page per job: full description text, applicants count, company-provided salary range, seniority, employment type, job function, industries. One extra request per job.

## `alertMode` (type: `boolean`):

Job alerts as a data product: the actor remembers all reported jobs in a persistent watchlist store ('job-watchlist-<watchlistId>') and pushes ONLY jobs never seen before (charged as 'job-new'). Schedule the actor (e.g. daily) to get a delta feed of new postings.

## `watchlistId` (type: `string`):

Identifies the persistent history store used in alert mode. Use the same ID across scheduled runs of the same alert; different IDs keep independent histories.

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

Optional — the actor ships with bundled geo proxies (no setup needed, we cover the cost). Enable this only to force your own or Apify proxies instead.

## `proxiesByCountry` (type: `object`):

Optional geo-pinned proxies per ISO country code ({"US": \["http://…"], "DE": \["http://…"]}). Tried FIRST, ahead of the bundled pool and the Apify proxy tiers.

## Actor input object example

```json
{
  "keywords": [
    "software engineer"
  ],
  "locations": [
    "United States"
  ],
  "country": "US",
  "datePosted": "any",
  "workplaceType": "any",
  "jobType": "any",
  "experienceLevel": "any",
  "maxJobs": 10,
  "includeDescription": false,
  "alertMode": false,
  "watchlistId": "default",
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

No description

# 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 = {
    "keywords": [
        "software engineer"
    ],
    "locations": [
        "United States"
    ],
    "country": "US",
    "datePosted": "any",
    "watchlistId": "default"
};

// Run the Actor and wait for it to finish
const run = await client.actor("cyprusapi/linkedin-jobs").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 = {
    "keywords": ["software engineer"],
    "locations": ["United States"],
    "country": "US",
    "datePosted": "any",
    "watchlistId": "default",
}

# Run the Actor and wait for it to finish
run = client.actor("cyprusapi/linkedin-jobs").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 '{
  "keywords": [
    "software engineer"
  ],
  "locations": [
    "United States"
  ],
  "country": "US",
  "datePosted": "any",
  "watchlistId": "default"
}' |
apify call cyprusapi/linkedin-jobs --silent --output-dataset

```

## MCP server setup

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

```

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/psHUhCsOfFGX3b52i/builds/OOruQgFrwzXbdZP0V/openapi.json
