# Hiring.Cafe Scraper - Jobs, Salaries, Companies & Benefits (`abotapi/hiring-cafe-scraper`) Actor

Scrape Hiring.Cafe jobs from $1/1K results. Search with advanced filters or URLs and extract 100+ fields per job, including salaries, company insights, benefits and job details. Fast, scalable job data extraction with Gold discounts starting at $1.

- **URL**: https://apify.com/abotapi/hiring-cafe-scraper.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Categories:** Jobs, Automation, Developer tools
- **Features:** Limited permissions
- **Stats:** 29 total users, 0 monthly users, 100.0% runs succeeded, 3 bookmarks
- **User rating**: 3.00 out of 5 stars

## Pricing

from $1.00 / 1,000 results

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

## Hiring Cafe Scraper: Job Listings, Salaries, Skills & Company Data

Hiring Cafe scraper turns Hiring Cafe job search into a clean data feed. Get job titles, salary ranges and currency, seniority and workplace type, required skills, certifications, visa sponsorship, benefits, and the hiring company's size, funding and headquarters, for roles indexed from company career sites. Search by keyword and filters, or paste search links and single job links, then export to JSON, CSV or Excel, or pull the results straight into your app through the API.

### Why This Scraper?

- **Two ways to search.** Filter by keyword, location, workplace type, employment type and seniority, or paste Hiring Cafe search links and single job links directly.
- **100+ structured attributes per job.** The complete nested payload the site publishes, plus a curated flat view for filtering and sorting.
- **Full job descriptions on request.** Switch on Fetch full job descriptions to pull each posting's complete text; leave it off for a fast, listing only crawl.
- **Company context on every job.** Employee count, founding year, headquarters country, funding stage and stock ticker, when the posting publishes them.
- **Pay, requirements and benefits at a glance.** Salary range and frequency, minimum years of experience, required education, certifications, visa sponsorship, security clearance, benefits and schedule flags.
- **Built for schedules.** Incremental mode returns only new and changed jobs on recurring runs, and a run that cannot connect at all fails loudly instead of returning an empty dataset.
- **Send results into your apps.** Optional MCP connectors pipe a summary of each job into Notion, Linear or Airtable right after the scrape.

### Use Cases

- **Job boards and aggregators:** mirror Hiring Cafe postings with structured salary, benefits and requirement fields ready to filter and republish.
- **Recruiting and sourcing teams:** track new roles at target companies, along with funding stage and headcount, to time outreach.
- **Compensation and market research:** build salary benchmarks by title, seniority, location and workplace type.
- **Job seekers and career tools:** power a personal alert feed for a keyword and location search, refreshed daily with incremental mode.
- **Labor market analysis:** study hiring trends across employment types, seniority levels, required skills and certifications over time.

### Data You Get

> Sample shape: values are illustrative placeholders, not from a live record.

| Field | Example |
| --- | --- |
| `id` | `"abc123XYZ"` |
| `job_title` | `"Senior Backend Engineer"` |
| `company_name` | `"Sample Robotics Inc."` |
| `employment_types` | `"Full Time"` |
| `seniority` | `"Senior Level"` |
| `workplace_type` | `"Remote"` |
| `location` | `"United States"` |
| `remote_worldwide` | `true` |
| `salary_min` / `salary_max` | `120000` / `160000` |
| `salary_currency` / `salary_frequency` | `"USD"` / `"yearly"` |
| `salary_transparent` | `true` |
| `posted_at` | `"2026-09-20"` |
| `min_years_experience` | `5` |
| `education_required` | `["bachelors"]` |
| `certifications` / `technical_tools` | `["AWS Certified Solutions Architect"]` / `["Python", "Kubernetes"]` |
| `visa_sponsorship` / `security_clearance` | `"No"` / `"Not required"` |
| `benefits` | `["retirement_plan", "generous_paid_time_off"]` |
| `schedule` | `["four_day_work_week"]` |
| `company_employees` / `company_founded` | `"51-200"` / `2015` |
| `is_public_company` | `false` |
| `investors` / `latest_investment_series` / `latest_investment_amount` | `["Sample Capital"]` / `"Series B"` / `20000000` |
| `views_on_site` / `applies_on_site` | `340` / `28` |
| `job_url` / `apply_url` | Hiring Cafe link to the job / the employer's own application link |

Every record also carries the complete nested payload the site publishes: `job_information`, `v5_processed_job_data` (85+ structured attributes), the company record (`v5_processed_company_data`), and `_geoloc` coordinates, so no source field is ever dropped. With Fetch full job descriptions on, records also carry `description` (HTML) and `description_text` (plain text), plus `detailFetched` (true when this job's page was read to build the record; false when the page was refused, the toggle was off, or the job's page was not needed). Incremental mode runs add `changeType`, `changedFields`, `firstSeenAt` and `lastSeenAt` to every record.

### How to Use

1. Pick a way to search: paste Start URLs (Hiring Cafe search links or single job links), or fill in Search keyword and the filters below it.
2. Turn Fetch full job descriptions on or off; it is on by default.
3. Set Maximum jobs to control run size and cost, then click Start.
4. Download the dataset as JSON, CSV or Excel, or read it through the API.

**Search by keyword and location:**

```json
{
  "keyword": "data scientist",
  "location": "Germany",
  "workplaceType": "Remote",
  "maxItems": 50
}
```

**Filter by employment type, seniority and recency:**

```json
{
  "keyword": "product manager",
  "commitmentType": "Full Time",
  "seniorityLevel": "Senior Level",
  "postedWithinDays": 7,
  "salaryTransparentOnly": true
}
```

**Start from URLs (a search link plus a single job link):**

```json
{
  "startUrls": [
    "https://hiringcafe.com/?searchState=%7B%22searchQuery%22%3A%22nurse%22%7D",
    "https://hiringcafe.com/job/1234567"
  ],
  "fetchDetails": true
}
```

**Fast listing only crawl, no descriptions:**

```json
{
  "keyword": "warehouse associate",
  "location": "Chicago",
  "fetchDetails": false,
  "maxItems": 200
}
```

#### Run it from your code

Python:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("abotapi/hiring-cafe-scraper").call(run_input={"keyword": "data scientist", "location": "Germany"})
for job in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(job["job_title"], job["company_name"], job.get("salary_min"))
```

JavaScript:

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

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('abotapi/hiring-cafe-scraper').call({ keyword: 'data scientist', location: 'Germany' });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the **Integrations** tab.

#### Start URLs take priority

**Default behaviour:** Start URLs ships with six example links, one per supported shape (the table below). A run started with the field untouched, or an API call that leaves the field out and sets no keyword or filter, scrapes those six links: a bare search page, four searches for `software engineer` (one starting at page 2, one with the older base64-encoded filter form) and one single job. Filter inputs that still hold their form default (Posted within days 30, Salary transparent only off, Sort by default, "Any" choices, empty lists) count as "not set", so the platform filling those in for you does not switch the actor to filter mode.

**Precedence:** (1) Start URLs you changed always win and the filters are ignored. (2) If Start URLs is empty or still holds exactly the six example links, and you set a keyword, location, or any filter to a non-default value, the actor searches by your filters instead. (3) With neither, it runs the six example links. The keyword box is not pre-filled in the input form, so a form run left untouched runs the six example links.

| Link shape | Handled as |
| --- | --- |
| `https://hiringcafe.com/classic?searchState=…` | search (this is the example) |
| `…?searchState=…&page=N` | search, starts at page N |
| `https://hiringcafe.com/?searchState=…` | search (same as the classic link) |
| `https://hiringcafe.com/classic` | search, no filters |
| `https://hiringcafe.com/?searchState=<base64>` | search (older base64-encoded filter form) |
| `https://hiringcafe.com/job/<id>` (also old `/viewjob/<id>`) | single job |

Any other link is skipped with a warning in the log naming the link.

Start URLs you change take priority and the filters (Search keyword, Location, Workplace type, Employment type, Seniority level, Posted within, Salary transparent only, Sort by and the extra company, industry, salary and experience filters) are ignored for that run; the untouched example links give way to a keyword or filter you set. Clear Start URLs to search by filters instead.

#### Run summary

Every run saves a record named `OUTPUT` in the run's key-value store: one entry per search link (or the filter-built search) and per single-job link, each with the link, start page, rows returned, pages read and why it stopped (`last_page`: the last page of results was read; `empty_page`: a page came back with no jobs; `max_items`: the Maximum jobs cap was reached; `fetch_failed`: the page could not be loaded; `saved`: a single job link was returned; `no_data`: a single job link had no data, for example an expired job; `duplicate_or_suppressed`: a single job link loaded but nothing was returned because it was already returned in this run or incremental mode left it out as unchanged), plus run totals. It is not part of the dataset, which stays one job per row.

#### How the filters were checked

Each filter was tested against the live site by comparing the number of matching jobs with and without it, plus a nonsense value that must match nothing (2026-10-15, about 111,000 jobs without filters): job title text (`nurse` 5,226; nonsense 0), company names (`Google` 69; nonsense 0), excluded company names (`Google` 111,063), industries (`Software Development` 2,578; nonsense 0), excluded industries (108,543), years of experience (0 to 2 years 84,103; 10 or more 12,152), employment types (Temporary 10,484; Seasonal 3,699; Volunteer 1,620; Full Time 76,873; nonsense 0), workplace Remote 4,097, salary transparent 23,294. Pay bounds narrow only jobs that disclose pay (disclosed-pay jobs: no bound 23,294; minimum 150,000 gives 1,750; minimum 1,000,000,000 gives 0), which is why setting a bound also limits results to disclosed pay. The site applies the "Posted within days" window only for some values: 2 days (6,048), 14 days (24,546) and 1,440 days (109,815) narrowed the count, while 1, 3, 7, 30, 60, 90, 180, 365 and 1,000 returned the full count, so treat that filter as approximate.

#### Output views

The dataset has four views: **Overview** (id, links, change fields), **Job Details** (title, company, pay, level, workplace, requirements), **Companies** (company record and coordinates) and **Changes** (incremental mode rows with `changeType`, `changedFields`, `firstSeenAt`, `lastSeenAt`).

#### Resume and recurring updates

- **Resume** (`resumeFromRunId`) continues one interrupted run: paste its run or dataset ID and the actor skips every job already collected there, so you don't pay twice.
- **Incremental mode** (`incrementalMode`) is for running the same search again and again, for example daily. The first run returns everything as `NEW`. Later runs return only `NEW`, `UPDATED` (with `changedFields`) and `REAPPEARED` jobs; `UNCHANGED` jobs are suppressed and not billed unless `emitUnchanged` is on, and `EXPIRED` jobs are only produced after a run that scanned the tracked search all the way through, and only with `emitExpired`. `stateKey` names or shares the stored state. A job counts as `UPDATED` when any published field changes; `views_on_site` and `applies_on_site` are running counters and never trigger `UPDATED` (they stay in the output), and `ignoreFieldsForChanges` lets you exclude more fields, by name or by dotted path such as `v5_processed_job_data.some_field`. A Start URL that begins after page 1 never produces `EXPIRED` jobs, and `EXPIRED` rows never exceed the room left under `maxItems`. **Saved state keeps loading after the example links were added:** a recurring call that uses incremental mode with no keyword, no filter and no Start URLs of its own still runs the six example links, and it is tracked under the same saved state as before, so nothing already tracked is returned or billed again as `NEW`. Changing any filter or Start URL starts a separate saved state, as it always did. With incremental mode off, output is exactly as before. **One-time change on upgrade:** this version adds new top-level job fields (for example `core_job_title`, the `*_min_compensation` / `*_max_compensation` family, `workplace_*`, `industries`, `subsidiaries`, `investors`). Jobs already tracked by an earlier version are not re-billed as `UPDATED` because of those new fields alone: the first run after the upgrade records them silently as `UNCHANGED`. A real change to the job in that same run still returns `UPDATED`, and after that first run any later change to the new fields counts as a normal update.

### Send results into your apps (MCP connectors)

Optionally pipe the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. This is an extra delivery step **after** the scrape: the Apify dataset is never changed.

**What gets written to the connector:** a condensed, human readable **summary** of each job, not the full JSON. Each job becomes one entry with a **title** and its key fields flattened to plain text. The **complete record always stays in the Apify dataset**.

1. Authorize a connector once under **Apify → Settings → Integrations** (Notion, Linear, Airtable, or Apify).
2. Select it in the **"Pipe results into your apps"** input field. (If the picker is empty, you haven't authorized a connector yet.)
3. For **Notion**, also set `notionParentPageUrl` to the page where jobs should be created.

The connection is mediated by Apify's MCP proxy, so this actor never sees your third party credentials. Leave the field empty to skip.

### Input Parameters

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `startUrls` | array | six example links (one per shape) | Hiring Cafe search links (with `?searchState=…`) and/or single job links (`…/job/…`). Links you change take priority over the filters below; the untouched example links give way to a keyword or filter you set. |
| `keyword` | string | (none) | Job title, skill, or company name. Used when Start URLs is empty or still the six example links; other Start URLs take priority. |
| `location` | string | (none) | Country or city. Combined with the keyword; leave empty for worldwide results. |
| `workplaceType` | string | `Any` | `Any`, `Remote`, `Hybrid`, or `Onsite`. |
| `commitmentType` | string | `Any` | `Any`, `Full Time`, `Part Time`, `Contract`, or `Internship`. |
| `seniorityLevel` | string | `Any` | `Any`, `No Prior Experience Required`, `Entry Level`, `Mid Level`, or `Senior Level`. |
| `postedWithinDays` | integer | `30` | Only jobs published in the last N days (maximum 1440). Approximate: the site applies this window only for some values (measured: 2, 14 and 1440 days narrowed the results, 30 did not), so treat it as a hint, not an exact cut-off. |
| `salaryTransparentOnly` | boolean | `false` | Return only jobs whose pay is publicly disclosed. |
| `sortBy` | string | `default` | Result order: `default` (relevance), `date` (newest first), `date_asc` (oldest first), `compensation`, `experience`. |
| `workplaceTypes` | array | (none) | Several values at once; when set they override the single-value fields above. |
| `commitmentTypes` | array | (none) | Several values at once; when set they override the single-value fields above. |
| `seniorityLevels` | array | (none) | Several values at once; when set they override the single-value fields above. |
| `jobTitleQuery` | string | (none) | Match text against the job title only. |
| `companyNames` | array | (none) | Only, or never, these companies. |
| `excludedCompanyNames` | array | (none) | Only, or never, these companies. |
| `industries` | array | (none) | Only, or never, these industries. |
| `excludedIndustries` | array | (none) | Only, or never, these industries. |
| `minSalary` | integer | (none) | Yearly compensation bounds (site currency units). Setting a bound also limits results to jobs that disclose their pay, because the site otherwise lets jobs without pay information through. |
| `maxSalary` | integer | (none) | Yearly compensation bounds (site currency units). Same disclosed-pay behaviour as Minimum salary. |
| `minYoe` | integer | (none) | Bounds on required years of experience. |
| `maxYoe` | integer | (none) | Bounds on required years of experience. |
| `maxItems` | integer | `1000` | Stop after this many jobs (`0` = unlimited). The input form pre-fills `20` as a light starting run; raise it for a full pull. Each results page carries up to ~100 jobs. |
| `resumeFromRunId` | string | (none) | Paste a previous run ID or dataset ID to continue a large search without returning or charging for jobs already collected there. |
| `incrementalMode` | boolean | `false` | Turn on for daily or recurring monitoring of the same search: returns only new and changed jobs on later runs. |
| `stateKey` | string | (none) | Name this monitoring campaign, or deliberately share state across differently configured runs. Left empty, the actor derives a key from the search/filters/URLs automatically. |
| `emitUnchanged` | boolean | `false` | Also return (and bill) jobs that have not changed since the last run, marked `UNCHANGED`. Incremental mode only. |
| `emitExpired` | boolean | `false` | Also return (and bill) jobs no longer found after a complete scan of the tracked search, marked `EXPIRED`. Incremental mode only. |
| `ignoreFieldsForChanges` | array | (none; counters ignored by default) | Field names or dotted paths left out of `UPDATED` detection (incremental mode only). They remain in the output. |
| `proxy` | object | Residential (Apify Proxy) | Connection settings. Hiring Cafe challenges datacenter connections, so keep the Residential group selected (see Plan Requirement). |
| `mcpConnectors` | array | (none) | Optional: send a summary of each job to apps you authorized under Integrations. |
| `notionParentPageUrl` | string | (none) | Notion connector only: page under which job pages are created. |
| `maxNotifyListings` | integer | `50` | Cap on jobs written to each connector per run. Does not affect the dataset. |
| `fetchDetails` | boolean | `true` | Read each matched job's own page for the full description and the freshest view/apply counts. Each delivered job whose page was read is charged as one detail-page event on top of the per-result charge. Turn off for a fast, listing only crawl. |

### Output Example

> Sample shape: values are illustrative placeholders, not from a live record.

```json
{
  "id": "abc123XYZ",
  "requisition_id": "REQ-4821",
  "source": "greenhouse",
  "job_title": "Senior Backend Engineer",
  "company_name": "Sample Robotics Inc.",
  "employment_types": "Full Time",
  "seniority": "Senior Level",
  "workplace_type": "Remote",
  "location": "United States",
  "remote_worldwide": true,
  "salary_min": 120000,
  "salary_max": 160000,
  "salary_currency": "USD",
  "salary_frequency": "yearly",
  "salary_transparent": true,
  "posted_at": "2026-09-20",
  "min_years_experience": 5,
  "education_required": ["bachelors"],
  "certifications": ["AWS Certified Solutions Architect"],
  "technical_tools": ["Python", "Kubernetes"],
  "visa_sponsorship": "No",
  "benefits": ["retirement_plan", "generous_paid_time_off"],
  "company_employees": "51-200",
  "company_founded": 2015,
  "company_hq_country": "United States",
  "views_on_site": 340,
  "applies_on_site": 28,
  "description_text": "We are looking for a Senior Backend Engineer to join our platform team...",
  "job_url": "https://hiringcafe.com/job/REQ-4821",
  "apply_url": "https://sample-robotics.example.com/careers/req-4821/apply",
  "job_information": {},
  "v5_processed_job_data": {},
  "v5_processed_company_data": {},
  "_geoloc": { "lat": 37.77, "lng": -122.41 }
}
```

### Plan Requirement

**A paid Apify plan with residential proxy access is required for reliable results.** Hiring Cafe sits behind a managed browser challenge, and datacenter connections do not get through it. The default **Proxy configuration** is the Residential group, so keep it selected. This is not a "works with no action needed" actor on a free plan.

On an Apify free plan (no residential proxies) the run does not stop at once: it logs this notice and continues on a backup connection, which may still be refused:

> The selected connection is unavailable to this run. Apify free plans do not include residential proxies. Continuing on a backup connection; some pages may still be unavailable. For residential access, upgrade at https://apify.com/pricing or supply your own proxy URLs.

If the backup connection serves pages, the log says: "Served over the backup connection (the default connection was unavailable)." If nothing can be loaded the run fails with one of these messages:

- **Connection refused for the account:** "The proxy connection was refused for this account, so no page could be read. Your Apify plan does not include proxy access; a paid plan is required: https://apify.com/pricing. If you entered your own proxy URLs, check that their credentials are valid."
- **Free plan, backup also refused:** "No results: residential proxies are unavailable on this run (Apify free plans do not include them), and the backup connection could not load the requested hiring.cafe pages. Upgrade at https://apify.com/pricing or supply your own proxy URLs, then try again."
- **Pages failed to load:** "Could not load any results page (the requests failed), so nothing was collected. This is a connection problem, not an empty search. Please try again later."

### FAQ

#### How much does it cost?

You pay per job returned plus a small per-run start fee, on the pay per event model. With **Fetch full job descriptions** on (the default), each delivered job whose own page was read is also charged as one detail-page event. All events are charged by the platform at the rates shown on the **Pricing** tab, which are the authoritative live rates and depend on your Apify plan tier. Jobs suppressed by incremental mode (`UNCHANGED`) are never charged. Use **Maximum jobs** to cap the cost of any run.

#### Is it legal to scrape Hiring Cafe?

This actor collects only publicly available job listings. You are responsible for how you use it: follow Hiring Cafe's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution. Job descriptions and company details may carry the original poster's rights.

#### Can I get only new or changed jobs on a schedule?

Yes. Schedule the actor from the **Schedules** tab and turn on **Incremental mode**. The first run returns every matching job as `NEW`; later runs return only `NEW`, `UPDATED` and `REAPPEARED` jobs, classified against a fingerprint of each job's own content, and unchanged jobs are not billed unless you turn on **Emit unchanged**.

#### Why do my filters seem to have no effect?

**Start URLs** you change take priority over the filters below it: keyword, location, workplace type, employment type, seniority and posted within days are all ignored for that run. The untouched example links give way to a keyword or filter you set. Clear Start URLs to search by filters instead.

#### Why did my run fail instead of returning an empty dataset?

A run fails only when it cannot connect at all before loading a single page, when every Start URL is unrecognized (neither a Hiring Cafe search link nor a job link), when Resume from a previous run points at an id that cannot be read, or when Resume from a previous run is combined with Incremental mode for a search that already has saved incremental state; each of these stops the run with a clear message. If the connection works but an individual page is later refused, or your filters simply match nothing, the run finishes normally and returns fewer jobs, or none, with the reason logged.

#### Can I use it with AI agents or MCP?

Yes. Call it from any Apify integration or MCP client, and use the connector field to push results into Notion, Linear or Airtable.

### 🔗 Want more jobs data?

Pair this actor with these related scrapers from the same team:

<table>
<tr><td>💼 <a href="https://apify.com/abotapi/careerbuilder-scraper"><b>CareerBuilder Scraper</b></a><br>Scrape CareerBuilder.com job listings by search or URL. Returns title, company profile...</td><td>💼 <a href="https://apify.com/abotapi/jobsite-co-uk-scraper"><b>Jobsite UK Scraper</b></a><br>Scrape Jobsite.co.uk job listings at scale by keyword, location, filters or URL. Extract...</td></tr>
<tr><td>💼 <a href="https://apify.com/abotapi/dice-com-scraper"><b>Dice.com Scraper</b></a><br>Scrape tech job listings from Dice.com by keyword, filters, or URL. Extract job titles...</td><td>💼 <a href="https://apify.com/abotapi/nofluffjobs-scraper"><b>NoFluffJobs Scraper</b></a><br>Scrape No Fluff Jobs IT listings across Poland, Czechia, Slovakia, Hungary and the...</td></tr>
<tr><td>🧩 <a href="https://apify.com/abotapi/naukri-com-scraper"><b>Naukri.com Scraper</b></a><br>Scrape Naukri.com jobs by keyword or URL. Extract job title, company, skills, experience...</td><td>💼 <a href="https://apify.com/abotapi/we-work-remotely-jobs-scraper"><b>WeWorkRemotely Scraper</b></a><br>Extract current remote job listings from weworkremotely.com. Use keyword search or WWR...</td></tr>
</table>

👉 [Browse all abotapi scrapers](https://apify.com/abotapi)

### 💬 Support

- 🐞 **Found a bug or a missing field?** Open a ticket on the [Issues tab](https://apify.com/abotapi/hiring-cafe-scraper/issues/open). We usually reply within hours.
- 🛠️ **Need another site, extra fields or a private build?** Email <contact@abotapi.com>.
- ⭐ **Enjoying it?** A quick review on the actor page helps other users find it.

# Actor input Schema

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

Paste Hiring.Cafe search URLs (links with ?searchState=…) and/or single job URLs (…/job/…). Each search link is walked forward from its own page (a ?page=N in the link is honoured). Start URLs you change take priority and the filters are ignored; the untouched example links give way to a keyword or filter you set. Supported link shapes: /classic search, /classic with ?page=N, root-path ?searchState=, a bare /classic page, the legacy base64 searchState form, and single /job/<id> pages.

## `keyword` (type: `string`):

Job title, skill, or company name (for example: python developer, data scientist). Used when Start URLs is empty or still holds the example links; any other Start URLs you set take priority over this field.

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

Country or city (for example: United States, London, Germany). Combined with the keyword; leave empty for worldwide results.

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

Filter by workplace arrangement: Remote, Hybrid, or Onsite.

## `commitmentType` (type: `string`):

Filter by employment type: Full Time, Part Time, Contract, or Internship.

## `seniorityLevel` (type: `string`):

Filter by experience level, from No Prior Experience Required to Senior Level.

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

Only jobs published in the last N days. Default 30; maximum 1440. Approximate: the site applies this window only for some values (measured: 2, 14 and 1440 days narrowed the results, 30 did not), so treat it as a hint, not an exact cut-off.

## `salaryTransparentOnly` (type: `boolean`):

Return only jobs whose pay is publicly disclosed.

## `sortBy` (type: `string`):

Result order: default (relevance), date (newest first), date_asc (oldest first), compensation, experience.

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

Several workplace arrangements at once. When set, overrides Workplace Type.

## `commitmentTypes` (type: `array`):

Several commitment types at once. When set, overrides Commitment Type.

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

Several seniority levels at once. When set, overrides Seniority Level.

## `jobTitleQuery` (type: `string`):

Match this text against the job title only.

## `companyNames` (type: `array`):

Company names to include.

## `excludedCompanyNames` (type: `array`):

Company names to leave out.

## `industries` (type: `array`):

Industry names to include.

## `excludedIndustries` (type: `array`):

Industry names to leave out.

## `minSalary` (type: `integer`):

Lowest yearly compensation (site currency units). Setting it also limits results to jobs that disclose their pay.

## `maxSalary` (type: `integer`):

Highest yearly compensation. Setting it also limits results to jobs that disclose their pay.

## `minYoe` (type: `integer`):

Lower bound of required years of experience.

## `maxYoe` (type: `integer`):

Upper bound of required years of experience.

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

Stop after this many jobs (0 = unlimited). Each results page carries up to ~100 jobs.

## `fetchDetails` (type: `boolean`):

On by default. Reads each matched job's own page so every record carries the full description (the search listings themselves do not include it), plus the freshest on-site view/apply counts. Adds one page read per job (~200 KB of traffic per job on your proxy plan). Each delivered job whose page was read is charged as one detail-page event on top of the per-result charge; jobs whose page the source refuses, jobs read with this toggle off, and jobs incremental mode suppresses as unchanged are not charged. Turn off for a fast, listing-only crawl.

## `proxy` (type: `object`):

Residential proxy is strongly recommended: hiring.cafe blocks datacenter IP ranges, so non-residential runs may return no results.

## `resumeFromRunId` (type: `string`):

Paste a previous run ID or dataset ID to continue a large search without returning or charging for jobs already collected there. Use this after an interrupted run, or when continuing a jobs pull in another run. For recurring daily monitoring of the same search, use Incremental mode below instead.

## `incrementalMode` (type: `boolean`):

Turn this on for daily or recurring monitoring. The first run returns all matching jobs as NEW. Later runs normally return only NEW, UPDATED, and REAPPEARED jobs. A job counts as UPDATED when any of its published fields changes (title, description, salary, location, the structured job and company attributes); views_on_site and applies_on_site (running counters) and any field you list in Ignore fields never trigger UPDATED. Turn on "Emit unchanged" or "Emit expired" only when you also want those jobs returned (and billed). State is kept separately per search/filter/URL setup; use State key when you want to name or deliberately share a monitoring campaign. To continue one specific interrupted run instead, use Resume from a previous run above.

## `stateKey` (type: `string`):

Optional. Name this monitoring campaign to keep its state stable, or to deliberately share state across differently-configured runs. Leave empty to let the actor derive a key automatically from the search/filters/URLs; different searches then never mix state with each other.

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

Off by default. Turn on to also return jobs that have not changed since the last run, marked UNCHANGED. This returns: and bills: extra rows you already have, so leave it off unless you specifically want the full snapshot every run.

## `emitExpired` (type: `boolean`):

Off by default. Turn on to also return jobs that were present in a previous run but are no longer found, marked EXPIRED. Only produced once a run has fully scanned the tracked search: not when Maximum jobs capped it or when Resume was used. This returns: and bills: extra synthetic rows, so leave it off unless you need expiry tracking.

## `ignoreFieldsForChanges` (type: `array`):

Optional. Field names to leave out of UPDATED change detection (still present in the output). Use a top-level name such as company_name or a dotted path such as v5_processed_job_data.some_field. Built in: views_on_site and applies_on_site never trigger UPDATED, because they are running counters that rise on their own.

## `mcpConnectors` (type: `array`):

Optionally send the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. Authorize a connector once under Apify → Settings → Integrations, then select it here. The connector receives a condensed, human-readable summary per item (title + key fields), not the full JSON: the complete record stays in the dataset. Leave empty to skip. Supported: Notion (https://mcp.notion.com/mcp), Linear (https://mcp.linear.app/sse), Airtable (https://mcp.airtable.com/mcp), Apify (https://mcp.apify.com).

## `notionParentPageUrl` (type: `string`):

URL (or id) of the Notion page under which item pages are created. Required to enable the Notion export; ignored by other connectors.

## `maxNotifyListings` (type: `integer`):

Cap on items written to each connector per run. Does not affect the dataset.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://hiringcafe.com/classic"
    },
    {
      "url": "https://hiringcafe.com/classic?searchState=%7B%22searchQuery%22%3A%22software%20engineer%22%7D"
    },
    {
      "url": "https://hiringcafe.com/classic?searchState=%7B%22searchQuery%22%3A%22software%20engineer%22%7D&page=2"
    },
    {
      "url": "https://hiringcafe.com/?searchState=%7B%22searchQuery%22%3A%22software%20engineer%22%7D"
    },
    {
      "url": "https://hiringcafe.com/?searchState=eyJzZWFyY2hRdWVyeSI6InNvZnR3YXJlIGVuZ2luZWVyIn0%3D"
    },
    {
      "url": "https://hiringcafe.com/job/bt38fz2pqe76nimr"
    }
  ],
  "location": "",
  "workplaceType": "Any",
  "commitmentType": "Any",
  "seniorityLevel": "Any",
  "postedWithinDays": 30,
  "salaryTransparentOnly": false,
  "sortBy": "default",
  "maxItems": 20,
  "fetchDetails": true,
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "ignoreFieldsForChanges": [],
  "maxNotifyListings": 50
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

## `jobs` (type: `string`):

No description

## `companies` (type: `string`):

No description

## `changes` (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 = {
    "startUrls": [
        {
            "url": "https://hiringcafe.com/classic"
        },
        {
            "url": "https://hiringcafe.com/classic?searchState=%7B%22searchQuery%22%3A%22software%20engineer%22%7D"
        },
        {
            "url": "https://hiringcafe.com/classic?searchState=%7B%22searchQuery%22%3A%22software%20engineer%22%7D&page=2"
        },
        {
            "url": "https://hiringcafe.com/?searchState=%7B%22searchQuery%22%3A%22software%20engineer%22%7D"
        },
        {
            "url": "https://hiringcafe.com/?searchState=eyJzZWFyY2hRdWVyeSI6InNvZnR3YXJlIGVuZ2luZWVyIn0%3D"
        },
        {
            "url": "https://hiringcafe.com/job/bt38fz2pqe76nimr"
        }
    ],
    "location": "",
    "workplaceType": "Any",
    "commitmentType": "Any",
    "seniorityLevel": "Any",
    "maxItems": 20,
    "fetchDetails": true,
    "proxy": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    },
    "incrementalMode": false,
    "emitUnchanged": false,
    "emitExpired": false,
    "ignoreFieldsForChanges": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/hiring-cafe-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 = {
    "startUrls": [
        { "url": "https://hiringcafe.com/classic" },
        { "url": "https://hiringcafe.com/classic?searchState=%7B%22searchQuery%22%3A%22software%20engineer%22%7D" },
        { "url": "https://hiringcafe.com/classic?searchState=%7B%22searchQuery%22%3A%22software%20engineer%22%7D&page=2" },
        { "url": "https://hiringcafe.com/?searchState=%7B%22searchQuery%22%3A%22software%20engineer%22%7D" },
        { "url": "https://hiringcafe.com/?searchState=eyJzZWFyY2hRdWVyeSI6InNvZnR3YXJlIGVuZ2luZWVyIn0%3D" },
        { "url": "https://hiringcafe.com/job/bt38fz2pqe76nimr" },
    ],
    "location": "",
    "workplaceType": "Any",
    "commitmentType": "Any",
    "seniorityLevel": "Any",
    "maxItems": 20,
    "fetchDetails": True,
    "proxy": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
    "incrementalMode": False,
    "emitUnchanged": False,
    "emitExpired": False,
    "ignoreFieldsForChanges": [],
}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/hiring-cafe-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 '{
  "startUrls": [
    {
      "url": "https://hiringcafe.com/classic"
    },
    {
      "url": "https://hiringcafe.com/classic?searchState=%7B%22searchQuery%22%3A%22software%20engineer%22%7D"
    },
    {
      "url": "https://hiringcafe.com/classic?searchState=%7B%22searchQuery%22%3A%22software%20engineer%22%7D&page=2"
    },
    {
      "url": "https://hiringcafe.com/?searchState=%7B%22searchQuery%22%3A%22software%20engineer%22%7D"
    },
    {
      "url": "https://hiringcafe.com/?searchState=eyJzZWFyY2hRdWVyeSI6InNvZnR3YXJlIGVuZ2luZWVyIn0%3D"
    },
    {
      "url": "https://hiringcafe.com/job/bt38fz2pqe76nimr"
    }
  ],
  "location": "",
  "workplaceType": "Any",
  "commitmentType": "Any",
  "seniorityLevel": "Any",
  "maxItems": 20,
  "fetchDetails": true,
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "ignoreFieldsForChanges": []
}' |
apify call abotapi/hiring-cafe-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,abotapi/hiring-cafe-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/hJvzrBDqAPXMuSoKN/builds/d6fwa6KfyjgjGeuEO/openapi.json
