# Jobs Search API  LinkedIn, Dice & Remote Jobs (`glasswing/jobs-search-api`) Actor

Search jobs on LinkedIn, Dice and six remote job boards (Remote OK, We Work Remotely, Himalayas, Jobicy, Working Nomads, Arbeitnow) in one call. One deduplicated row per job: title, company, location, link, date, description snippet, salary when published. CSV, JSON, Excel.

- **URL**: https://apify.com/glasswing/jobs-search-api.md
- **Developed by:** [Raffy](https://apify.com/glasswing) (community)
- **Categories:** Jobs, Lead generation, Developer tools
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.50 / 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.
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

### What does Jobs Search API do?

Jobs Search API searches **LinkedIn, Dice and six remote job boards in one call** and returns **one clean, deduplicated row per job**. Give it job keywords and, if you like, a city or country. It reads each route's public job listings, merges the same job when it appears on several routes, and delivers title, company, location, link, posting date, a description snippet and the salary when one is published, as JSON, CSV or Excel. It works as a **job search API**: one input, one normalised output, no browser, no logins and no stitching together of separate scrapers.

Typical use cases:

- **Job aggregators and job alerts:** feed a niche job board, a newsletter or a Slack channel with fresh listings for any keyword and place.
- **Recruiting and talent-market research:** see who is hiring for a role in a city, how often, and where the salary is published.
- **AI agents and automations:** one tool call answers "find me jobs for X in Y" with structured rows an agent can read.

It does **not** log in, solve challenges or collect private user data. It reads only job listings that are publicly available.

### Why use Jobs Search API?

- **One call, many routes.** LinkedIn, Dice (US tech and professional jobs), Remote OK, We Work Remotely, Himalayas, Jobicy, Working Nomads and Arbeitnow (Remotive on request). Pick routes with `sources` or leave it empty for all of them.
- **No double billing.** The same job found on several routes (same company, title and place) is merged into one row; `sources` and `sourceUrls` list where it was found. Duplicates are never billed.
- **Balanced results.** The routes take turns, newest first, so a small `maxItems` still shows every route.
- **Privacy-minded.** Descriptions are cut to a snippet and e-mail addresses, phone numbers and personal names are removed. No recruiter or hiring-contact fields exist.
- **Honest results.** Every row carries a `status` (`ok`, `not_found`, `error`). A route that is down gives one free `error` row and the other routes continue. You are only billed for `ok` rows.
- **Runs anywhere Apify runs.** Schedule it, call it from the API or an MCP client, or send results to Google Sheets, Slack, webhooks, Zapier or Make.

### What data can Jobs Search API extract?

| Field | Type | Description |
|---|---|---|
| `title` | string | Job title |
| `companyName` | string | Employer name |
| `location` | string | Location as the route publishes it, e.g. `Austin, TX` or `Worldwide` |
| `jobUrl` | string | Link to the listing on the route it was found on (same as `url`) |
| `postedAt` | string | ISO 8601 posting time, when the route publishes it |
| `descriptionSnippet` | string | First ~500 characters of the description, plain text, contact details removed |
| `remote` | boolean | True when the route marks the job as remote |
| `jobType` | string | Employment type as published, e.g. `Full-time`, `Contract` |
| `salaryMin`, `salaryMax`, `salaryCurrency`, `salaryPeriod` | number / string | Salary range, only when published (`year`, `month`, `week`, `day` or `hour`); never estimated |
| `salaryText` | string | Salary text as published |
| `source`, `sourceName` | string | Route the row comes from, e.g. `linkedin` / `LinkedIn` |
| `sources`, `sourceUrls` | array | Every route and listing the job was found on |
| `applyUrl` | string | Direct apply link, when the route publishes one |
| `jobId` | string | `<route>:<id>`, stable across runs |
| `url` | string | Source URL of the row (the listing) |
| `status` | string | `ok`, `not_found` or `error` (see below) |
| `error` | string | Reason when `status` is not `ok` |
| `scrapedAt` | string | ISO 8601 time of extraction |

Optional fields are omitted when the route does not publish them. LinkedIn does not show salaries on most listings, and Dice often says "Depends on Experience", which is passed through as `salaryText` (no numeric salary fields then).

LinkedIn and Dice return generic or off-topic jobs when a search has no real match, so a LinkedIn or Dice job is kept only if its title, snippet or tags contain every significant word of one of your keywords (plurals count). A search where nothing passes returns one free `not_found` row.

#### Result status (tri-state output)

| `status` | Meaning | Billed? |
|---|---|---|
| `ok` | A unique job was found and extracted. | Yes |
| `not_found` | The routes answered, but no job matched the search. | No |
| `error` | A route could not be read after retries (network error, block, or layout change). `error` names the route; the other routes were still searched. | No |

### How to search jobs with Jobs Search API

1. Open the Actor in Apify Console and click **Try for free**.
2. Type one or more **Keywords** (one per line, up to 5), and optionally a **Location** and **Country**.
3. Set **Maximum results** (start with the default 20 to see the output).
4. Click **Start**. The default input takes under a minute.
5. Open the **Output** tab or **Export** the dataset as JSON, CSV, Excel, XML or HTML.

To automate it, use the **API** tab (Node.js, Python, curl examples) or add a **Schedule**.

### How much does it cost to search jobs?

This Actor uses **pay-per-event** pricing. A result is one unique job with `status: ok`; the price per 1,000 depends on your Apify plan:

| Plan | Result price per 1,000 jobs |
|---|---|
| Free | $5.75 |
| Starter (Bronze) | $5.00 |
| Scale (Silver) | $4.25 |
| Business (Gold) | $3.50 |

Actor start: $0.00005 per run. Example: 1,000 jobs on the Starter plan cost about $5.00. Rows with `status` `not_found` or `error`, and duplicates merged across routes, are never billed. You can cap spending per run with **Maximum results** and with the run's **Max total charge** option.

### Input

See the **Input** tab for the full schema. The main options:

| Field | Type | Default | Description |
|---|---|---|---|
| `keywords` | array of strings | `["python developer"]` | Job titles or skills, one per line, up to 5. Each is searched on its own and the results are merged |
| `location` | string | - | City or region, e.g. `Berlin`, `Austin TX`. Used by LinkedIn and Dice |
| `country` | string | - | Country name or code, e.g. `US`, `Germany`. Dice lists US jobs only and is skipped for other countries. The remote boards keep jobs open to this country plus jobs open worldwide |
| `sources` | array | all routes | Any of `linkedin`, `dice`, `remoteok`, `weworkremotely`, `himalayas`, `jobicy`, `workingnomads`, `arbeitnow`, `remotive` |
| `postedWithinDays` | integer | - | Keep only jobs posted in the last N days; jobs without a date are kept |
| `maxItems` | integer | `20` | Stop after this many unique jobs |
| `startUrls` | array | - | Optional LinkedIn or Dice search page URLs; only their search words and location are used |
| `proxyConfiguration` | object | Apify Proxy (datacenter) | Used for LinkedIn only; included in the price |

Example input:

```json
{
    "keywords": ["python developer"],
    "country": "US",
    "maxItems": 20
}
```

### Output

You can download the dataset in various formats such as JSON, HTML, CSV or Excel. Example rows (shortened):

```json
[
    {
        "url": "https://www.linkedin.com/jobs/view/4450823282/",
        "status": "ok",
        "jobId": "linkedin:4450823282",
        "jobUrl": "https://www.linkedin.com/jobs/view/4450823282/",
        "source": "linkedin",
        "sourceName": "LinkedIn",
        "sources": [
            "linkedin"
        ],
        "scrapedAt": "2026-11-02T05:54:08.000Z",
        "title": "Senior Software Engineer",
        "companyName": "MTN Global",
        "location": "Fort Lauderdale, FL",
        "postedAt": "2026-10-31T00:00:00.000Z",
        "descriptionSnippet": "Job Description Position Summary We are building a modern cloud platform that powers mission-critical satellite communications and...",
        "jobType": "Full-time"
    },
    {
        "url": "https://www.dice.com/job-detail/0e8bbceb-38df-4b7a-9542-f9c1c8e3c950",
        "status": "ok",
        "jobId": "dice:0d6de8ac891b566bd140a898f4b87189",
        "jobUrl": "https://www.dice.com/job-detail/0e8bbceb-38df-4b7a-9542-f9c1c8e3c950",
        "source": "dice",
        "sourceName": "Dice",
        "sources": [
            "dice"
        ],
        "scrapedAt": "2026-11-02T05:54:01.000Z",
        "title": "Python Developer",
        "companyName": "Aasritha infotech Inc",
        "location": "Washington, District of Columbia, USA",
        "postedAt": "2026-10-31T22:18:24.000Z",
        "descriptionSnippet": "Job Title: Python Developer Location: Woodlawn, MD Duration: Long Term Contract Technical Skills: Years/Level of Experience Java...",
        "jobType": "Contract",
        "salaryText": "Depends on Experience"
    },
    {
        "url": "https://himalayas.app/companies/canonical/jobs/web-frontend-engineer-js-css-react-flutter",
        "status": "ok",
        "jobId": "himalayas:canonical/web-frontend-engineer-js-css-react-flutter",
        "jobUrl": "https://himalayas.app/companies/canonical/jobs/web-frontend-engineer-js-css-react-flutter",
        "source": "himalayas",
        "sourceName": "Himalayas",
        "sources": [
            "himalayas"
        ],
        "scrapedAt": "2026-11-02T06:13:38.092Z",
        "title": "Web Frontend Engineer - JS, CSS, React, Flutter",
        "companyName": "Canonical",
        "location": "Worldwide",
        "postedAt": "2026-11-02T01:25:50.000Z",
        "descriptionSnippet": "Canonical is a leading provider of open-source software and operating systems to the global enterprise and technology markets...",
        "remote": true,
        "jobType": "full_time",
        "salaryMin": 48000,
        "salaryMax": 70000,
        "salaryCurrency": "USD",
        "salaryPeriod": "year",
        "applyUrl": "https://himalayas.app/companies/canonical/jobs/web-frontend-engineer-js-css-react-flutter?utm_source=api"
    }
]
```

### Tips

- Keep `maxItems` small while testing, then raise it.
- Use several keywords in one run instead of many runs; you pay the start fee once and duplicates are merged.
- Use `sources` to search one route only, for example `["dice"]` for US tech jobs or `["linkedin"]` for a city.
- The remote boards are filtered by your keywords and country inside the Actor, so they return fewer rows for narrow searches than LinkedIn does.
- Schedule the Actor at most daily: the remote boards ask API users not to poll more often.

### Limitations

- Only publicly visible listings are read. Nothing behind a login is supported.
- LinkedIn serves at most about 1,000 results per search. Descriptions on LinkedIn come from each listing's public page; a listing whose page cannot be read keeps its row without a snippet.
- Dice covers US jobs only. The remote boards list remote jobs only and mostly in English, and their location is a region such as "USA Only" or "Worldwide", not a city.
- Salary appears only where the route publishes one; it is never guessed. Posting dates on LinkedIn are day-accurate.
- Duplicates are matched on company, title and place. A job posted under a different title on another route is treated as a different job.
- Results reflect the public listings at the time of the run; a route may change its layout, in which case it returns one `error` row and the Actor is updated quickly (report it in the **Issues** tab).
- Remote OK, We Work Remotely, Himalayas, Jobicy, Working Nomads, Arbeitnow and Remotive ask to be credited and linked when their listings are shown; each row keeps `sourceName` and `jobUrl` for that purpose.

### FAQ

#### Is it legal to scrape job listings?

Scraping publicly available data is generally legal, but you are responsible for how you use the output. Read the legal notice below and each job site's terms of service, and do not collect personal data without a legitimate reason.

#### Can I use this Actor from an AI agent or MCP client?

Yes. Every row is self-describing (`status` + `error`), inputs are plain strings, and results can be fetched via the dataset API or the Apify MCP server.

#### Why did I get fewer rows than `maxItems`?

The search matched fewer unique jobs than requested (duplicates across routes are merged), a route was unavailable (see the `error` rows), or the run hit your **Max total charge** limit. Check the run log.

#### Why does a LinkedIn row have no description?

LinkedIn answers some listing pages with a temporary block. The row is still delivered with title, company, location and link.

#### What if a field is missing?

Optional fields are omitted when a route does not publish them. If a field that should always be present is missing, open an issue in the **Issues** tab with the run ID.

### Legal and data-protection notice

This Actor extracts only data that the job sites publish publicly; it does not extract private user data such as personal e-mail addresses, phone numbers, gender or precise location, and it does not log in or circumvent access controls. E-mail addresses, phone numbers and personal names inside descriptions are removed. However, your results could still contain personal data. Personal data is protected by the GDPR in the European Union and by other regulations around the world. You should not scrape personal data unless you have a legitimate reason to do so. If you are unsure whether your reason is legitimate, consult your lawyers. You are responsible for complying with each job site's terms of service and applicable law when using the extracted data.

This Actor is an independent tool and is not affiliated with, endorsed by or sponsored by LinkedIn, Dice or any job board it reads, or their owners. All trademarks belong to their respective owners.

# Changelog

This Actor's version history is a separate document: https://apify.com/glasswing/jobs-search-api/changelog.md

# Actor input Schema

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

Job titles or skills to search for, one per line (up to 5), e.g. python developer, nurse, data analyst. Every line is searched on its own and the results are merged.

## `location` (type: `string`):

Optional city, state or region, e.g. Berlin, Austin TX, London. Used by LinkedIn and Dice. Leave empty to search the whole country or worldwide.

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

Optional country name or two-letter code, e.g. US, Germany, GB. Dice lists US jobs only and is skipped for other countries. The remote boards keep jobs open to applicants in this country plus jobs open worldwide. Leave empty for all countries.

## `sources` (type: `array`):

Routes to search. Leave empty for all: LinkedIn, Dice and the six remote boards (Remotive is opt-in because it asks API users to call it at most 4 times a day).

## `postedWithinDays` (type: `integer`):

Keep only jobs posted in the last N days. Jobs from a route that does not publish a date are kept. Leave empty for no limit.

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

Stop after this many unique jobs. Each saved job (status `ok`) is one billable result. Routes are interleaved, so a small number still shows every route.

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

Apify Proxy (datacenter, included in the price) is used for LinkedIn so it does not throttle the run. Residential proxies are not needed; a proxy group picked here is ignored. Your own proxy URLs are used as given.

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

Optional LinkedIn or Dice job-search page URLs. Only the search words (keywords / q) and the location in the URL are used; they are added to Keywords and Location. Other filters in the URL are ignored.

## Actor input object example

```json
{
  "keywords": [
    "python developer"
  ],
  "country": "US",
  "maxItems": 20,
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "startUrls": [
    "https://www.linkedin.com/jobs/search/?keywords=python%20developer"
  ]
}
```

# 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": [
        "python developer"
    ],
    "country": "US",
    "maxItems": 20,
    "proxyConfiguration": {
        "useApifyProxy": true
    },
    "startUrls": [
        "https://www.linkedin.com/jobs/search/?keywords=python%20developer"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("glasswing/jobs-search-api").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": ["python developer"],
    "country": "US",
    "maxItems": 20,
    "proxyConfiguration": { "useApifyProxy": True },
    "startUrls": ["https://www.linkedin.com/jobs/search/?keywords=python%20developer"],
}

# Run the Actor and wait for it to finish
run = client.actor("glasswing/jobs-search-api").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": [
    "python developer"
  ],
  "country": "US",
  "maxItems": 20,
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "startUrls": [
    "https://www.linkedin.com/jobs/search/?keywords=python%20developer"
  ]
}' |
apify call glasswing/jobs-search-api --silent --output-dataset

```

## MCP server setup

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

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/lbLejII3dwKV1oYQh/builds/2JUf9GsIvxBxMZZUE/openapi.json
