# Recruitee Jobs Scraper — Company Job Boards (`adderleydata/recruitee-jobs-scraper`) Actor

Every open role on any company's Recruitee careers site as structured data: title, department, locations with country, employment type, work arrangement, pay range where stated, dates, full text on request. One request per board. Incremental mode charges only for what changed. No personal data.

- **URL**: https://apify.com/adderleydata/recruitee-jobs-scraper.md
- **Developed by:** [Adderley Data](https://apify.com/adderleydata) (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.45 / 1,000 posting saveds

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 Recruitee Jobs Scraper do?

Recruitee Jobs Scraper reads every open role on any company's public Recruitee careers site and returns it as structured data you can load straight into a spreadsheet, a database or a model. If a company's jobs live at `<name>.recruitee.com` — or on its own domain with Recruitee behind it — this Actor reads them, given the company's Recruitee name.

Give it the company's Recruitee name (`acme`) or any `acme.recruitee.com` link, and for each job you get the title, the company's name, the department, every location it is open in with its country, the employment type and work arrangement in Recruitee's own terms, the pay range where the company states one, the dates it was published and last changed, the closing date where the company set one, and a link to the job — and, if you ask for it, the full text of the job.

What makes it different:

- **One request per board.** The whole board, and every job's full text with it, comes back in a single call to Recruitee's public Careers Site API. No HTML parsing, no page-by-page crawling, nothing to break when a careers page is redesigned. A thousand jobs across fifty companies is fifty requests.
- **Pay ranges as numbers.** Where a company fills in Recruitee's salary fields, the range comes back as `min`, `max`, `currency` and `period`, with `salary.raw` worded the way Recruitee's own careers page prints a range (`€115,000 - €130,000 per year`). Where it does not, but the job's text states a range, that range is read and its sentence kept in `salary.raw`. Where no range is stated anywhere, the fields are `null`; nothing is estimated.
- **Incremental mode.** Put the Actor on a schedule and each run returns only jobs that are new, changed, back again or gone. The Actor compares each job with the last run — title, company, locations, department, employment type, work arrangement, pay, published and closing dates, and every word of the text. Unchanged jobs are skipped and **not charged**.
- **One stable schema.** Every row has every field, every time. Unknown is `null`, never a missing key, so nothing downstream breaks on a sparse job. The schema is versioned (`job.v1`) and every Adderley Data jobs Actor uses it, so a Recruitee board, a Lever board and a job board sit in the same table.
- **No personal data.** Recruitee's feed carries a mailbox address for applications, the questions on the application form, and cover and sharing images; this Actor reads none of them and has nowhere to put them. Contact details inside job text are redacted by default.
- **Bounded cost.** You set a maximum number of results; the run stops there. It also stops at the spending limit you set on the run in Apify.

### What Recruitee data can you extract?

| Field | What it holds |
| --- | --- |
| `id` | Stable across runs: `source:market:sourceJobId`. Use it as your primary key. |
| `title` | Job title as listed. |
| `company.name` | The hiring company, where the listing names one. |
| `advertiser.name` | The business that placed the listing — often a recruitment agency. Never a person. |
| `location.raw` | Location text as listed. Several locations are joined with `\|`. |
| `location.suburb` | Suburb, when the listing states one. |
| `location.city` | City or area, when the listing states one. |
| `location.region` | State or region, e.g. `VIC`. |
| `location.postcode` | Postcode, when the source provides it. |
| `location.country` | ISO 3166-1 alpha-2 country code. |
| `workArrangement` | `on_site`, `hybrid`, `remote` or `unknown`. |
| `employmentTypes` | Normalised: `full_time`, `part_time`, `contract`, `casual`, `temporary`, `internship`, `volunteer`. |
| `salary.raw` | The salary text exactly as shown, or null when the listing shows none. |
| `salary.min` | Lower bound as a number, when the text contains one. |
| `salary.max` | Upper bound as a number. Equal to min for a single figure. |
| `salary.currency` | ISO 4217. Taken from the text, otherwise the market default. |
| `salary.period` | `hour`, `day`, `week`, `month` or `year`; null when the text does not say. |
| `salary.includesSuper` | true / false when the text says so ("plus super", "inc. super"); otherwise null. |
| `classifications` | The source's category and subcategory pairs. |
| `teaser` | The short summary shown on the results page. |
| `bulletPoints` | Selling points shown on the results page. |
| `postedAt` | When the listing was posted, ISO 8601 UTC. |
| `updatedAt` | The source's own last-modified time, ISO 8601 UTC. Published by ATS and API sources; null where the site does not show one. |
| `expiresAt` | Expiry, ISO 8601 UTC, where the source states one. |
| `isPromoted` | true for paid placements. A listing shown both promoted and organic is returned once. |
| `url` | Link to the listing. |
| `description` | Null unless requested. `text`, optional sanitised `html`, and `contactsRedacted`. |
| `changeType` | Incremental runs: `NEW`, `UPDATED`, `REAPPEARED`, `EXPIRED` (or `UNCHANGED` if you ask for those). Otherwise null. |
| `firstSeenAt` | Incremental runs: when this monitor first saw the listing. |
| `contentHash` | SHA-256 over the fields that define a change. Compare it to detect edits yourself. |
| `scrapedAt` | When this row was produced, ISO 8601 UTC. |
| `source` | Source key, e.g. `seek`. |
| `market` | Market key, e.g. `au`, `nz`. |
| `sourceJobId` | The source's own identifier for the listing. |
| `company.sourceCompanyId` | The source's identifier for the company, when exposed. |
| `company.url` | The company's page on the source site, when exposed. |
| `advertiser.sourceAdvertiserId` | The source's identifier for the advertiser. |
| `schemaVersion` | Always `job.v1`. Breaking changes ship as `job.v2` in a new Actor version, never silently. |

How Recruitee's fields fill the schema:

- `company.name` is the company name Recruitee gives with each job. `company.sourceCompanyId` is the board's Recruitee name and `company.url` its `recruitee.com` address.
- `url` is the job's own page as Recruitee gives it — on `recruitee.com`, or on the company's own careers domain where it has one.
- `location.raw` is Recruitee's own location label for the job (`Utrecht, Utrecht, Netherlands`, or `Remote job`), then every other place the job is open in, written the same way, joined with `|`. `city`, `region` and `postcode` are the primary location's. `location.country` is Recruitee's own country code; where that is missing or not an ISO code, it comes from the country's name or the label, never from a city.
- `classifications` holds the job's department as `category`. Recruitee has no second level, so `subcategory` is `null`.
- `employmentTypes` comes from Recruitee's employment type: "Full-time, Permanent" gives `full_time`; "Full-time, Fixed-term" gives `full_time` and `contract`; "Freelance" gives `contract`; "Seasonal" gives `temporary`.
- `workArrangement` comes from the remote, hybrid and on-site options the company ticks. A job can tick more than one; the most flexible is given — remote, then hybrid, then on-site.
- `postedAt` is when the job was published on Recruitee (its `published_at`), `updatedAt` Recruitee's own last-modified time, and `expiresAt` the closing date where the company set one.
- `market` is always `global`: a Recruitee board is the company's, not a country's.

### How much does it cost to scrape Recruitee job boards?

You pay per job saved to your dataset — **$1.75 per 1,000 jobs** on Apify's Starter plan — plus $0.005 each time a run starts. There is no monthly rental.

| Apify plan | Price | Per listing |
| --- | --- | --- |
| Free | $1.75 per 1,000 listings | $0.00175 |
| Starter (Bronze) | $1.75 per 1,000 listings | $0.00175 |
| Scale (Silver) | $1.60 per 1,000 listings | $0.00160 |
| Business (Gold) | $1.45 per 1,000 listings | $0.00145 |

Plus $0.005 per run start. Compute and proxy are included in these prices.

| What you run | Cost (USD, Starter plan) |
| --- | --- |
| 100 listings, one run | $0.18 |
| 1,000 listings, one run | $1.75 |
| 10,000 listings, one run | $17.50 |
| 50,000 listings, one run | $87.50 |
| A daily incremental monitor finding about 150 new or changed listings a day, for a month | $8.03 |

Descriptions cost nothing extra here: they arrive in the same request as the listing. Use incremental mode for anything you run more than once — after the first run you pay only for what changed.

### How to scrape a Recruitee careers site

1. Find the company's Recruitee name. It is the first part of the address in `https://acme.recruitee.com`: here, `acme`. A job link (`acme.recruitee.com/o/<job>`) or the apply page works too.
2. Open the Actor in Apify Console and go to the **Input** tab. Paste one name or link per line into **Job boards**. Up to 500 boards per run.
3. Optionally filter: **Title keywords**, **Locations**, **Departments**, **Published within (days)**.
4. Set **Maximum results**. This is also your cost cap.
5. Press **Start**. When the run finishes, open the **Output** tab and export as JSON, CSV, Excel, XML or HTML, or read the dataset through the Apify API.

A job that matches more than one board or filter is returned once.

### Input

| Field | Type | Default | What it does |
| --- | --- | --- | --- |
| `boards` | array | — | One entry per company: its Recruitee name (in https://acme.recruitee.com it is "acme"), or any link on that address — the careers site, one of its jobs, or its apply page. A company that shows its jobs on its own domain (careers.example.com) still has a recruitee.com name, and that name is needed: the company's own domain does not say which Recruitee account is behind it. Up to 500 boards per run; each is one request. |
| `keywords` | array | — | Keep jobs whose title contains every word of any keyword, in any order — "engineer data" matches "Senior Data Engineer". Leave empty for all titles. |
| `locations` | array | — | Keep jobs with a location containing this text ("Utrecht", "Berlin"), open in this country by Recruitee's own country code ("Germany", "Australia"), or with this work arrangement ticked ("Remote", "Hybrid", "On-site"). Every location a job lists counts. Leave empty for all locations. |
| `departments` | array | — | Keep jobs whose department contains this text, e.g. "Engineering". Leave empty for all departments. |
| `postedWithinDays` | integer | — | Keep jobs published on Recruitee in the last N days. Leave empty for any time. |
| `maxResults` | integer | `100` | The run stops once this many jobs are saved. You are charged per job saved, so this is also your cost cap. |
| `includeDescription` | boolean | `false` | On: the full job text — the job description, then the job requirements — comes back with each row, read from the same request as the listing, so it costs no extra requests. Off: listing fields only. |
| `descriptionFormat` | `text`, `text_and_html` | `"text"` | Plain text, or plain text plus sanitised HTML. |
| `redactContacts` | boolean | `true` | On by default: email addresses and phone numbers inside description text are replaced with \[redacted]. This Actor never outputs recruiter names or contact fields. |
| `incremental` | boolean | `false` | Remember what earlier runs saw and save only jobs that are new, changed or gone. Unchanged jobs are skipped and not charged. Put the Actor on a schedule with this on. |
| `stateKey` | string | — | Optional name for this monitor, e.g. "competitor-engineering". Runs with the same key share memory. Left empty, a key is derived from the boards and filters themselves. |
| `emitExpired` | boolean | `true` | Incremental mode only. When a complete run no longer finds a job it saw before, save one row with changeType EXPIRED. |
| `emitUnchanged` | boolean | `false` | Incremental mode only. Saves (and charges for) every job, labelled UNCHANGED where nothing moved. |
| `proxyConfiguration` | object | `{"useApifyProxy":true}` | Apify Proxy, automatic group, is the default and is what this Actor is tested with. |
| `maxConcurrency` | integer | `4` | Parallel requests. The default is deliberately modest. |
| `maxRequestsPerMinute` | integer | `90` | An upper bound on request rate across the whole run. |

A typical input:

```json
{
  "boards": [
    "jobs"
  ],
  "maxResults": 100
}
```

The prefilled board, `jobs`, is the careers site of Tellent, the company behind Recruitee.

### Output

One row per job. This is a synthetic example in the exact shape the Actor returns:

```json
{
  "schemaVersion": "job.v1",
  "id": "recruitee:global:2900001",
  "source": "recruitee",
  "market": "global",
  "sourceJobId": "2900001",
  "url": "https://example-freight.recruitee.com/o/data-analyst",
  "title": "Data Analyst",
  "company": {
    "name": "Example Freight Co",
    "sourceCompanyId": "example-freight",
    "url": "https://example-freight.recruitee.com"
  },
  "advertiser": {
    "name": "Example Freight Co",
    "sourceAdvertiserId": "example-freight"
  },
  "location": {
    "raw": "Melbourne, Victoria, Australia | Sydney, New South Wales, Australia",
    "suburb": null,
    "city": "Melbourne",
    "region": "Victoria",
    "postcode": "3121",
    "country": "AU"
  },
  "workArrangement": "hybrid",
  "employmentTypes": [
    "full_time"
  ],
  "salary": {
    "raw": "A$95,000 - A$110,000 per year",
    "min": 95000,
    "max": 110000,
    "currency": "AUD",
    "period": "year",
    "includesSuper": null
  },
  "classifications": [
    {
      "category": "Data",
      "subcategory": null
    }
  ],
  "teaser": null,
  "bulletPoints": [],
  "postedAt": "2026-09-20T22:14:05.000Z",
  "updatedAt": "2026-09-21T01:03:44.000Z",
  "expiresAt": "2026-10-19T13:59:59.000Z",
  "isPromoted": false,
  "description": null,
  "changeType": "NEW",
  "firstSeenAt": "2026-09-21T19:30:12.000Z",
  "contentHash": "c13bc7d9037b2c8361d12313e99059ced1fcb82ba45b60e1805861e7b330caca",
  "scrapedAt": "2026-09-21T19:30:12.000Z"
}
```

The Output tab has two table views: **Overview** (the fields most people want, flattened) and **Changes** (for incremental runs).

### Incremental mode: monitor new roles across companies

Turn on **Incremental mode** and run the same input on a schedule — hourly, daily, weekly. The Actor keeps a small record of what it has seen and every row tells you what happened:

| `changeType` | Meaning |
| --- | --- |
| `NEW` | First time this monitor has seen the job |
| `UPDATED` | Seen before, and the title, company name, locations, department, employment type, work arrangement, pay range, published date, closing date or any word of the job's text has changed |
| `REAPPEARED` | Was reported as expired and is back |
| `EXPIRED` | Seen before and no longer on the board. One row, once |
| `UNCHANGED` | Only if you turn on **Also save unchanged jobs** |

How it behaves, so there are no surprises:

- The first run returns everything as `NEW`. From the second run you pay only for the difference.
- A change is found by comparing the job itself. An edit to the text counts: a corrected typo is reported as `UPDATED` and charged like any other change. A change of formatting alone does not count.
- Recruitee's own last-modified time is delivered in `updatedAt` but is not compared: a new `updatedAt` on its own never makes a job `UPDATED`. Only a change to what the job says does.
- `EXPIRED` is only ever reported by a complete run. If a run hits your result cap or your spending limit, or a board cannot be read, nothing is declared expired — a job on a board the run never read is not gone.
- Each board is read in one response, so a run compares whole boards, never pages read seconds apart.
- Runs share memory when they share a **State key**. Leave it empty and the key is derived from the boards and filters themselves, so the same input always continues the same monitor. Name it (`competitor-engineering`) if you want to change filters later without starting again.
- A job not seen for 45 days is forgotten.

### Descriptions and contact details

Full descriptions are off by default. Turn on **Include full descriptions** and each row carries `description.text` (and sanitised `description.html` if you choose that format): the job description, then the job requirements, in the order Recruitee's own careers page shows them. Because Recruitee returns the text in the same response as the listing, this costs no extra requests and no extra time.

Job text sometimes contains a recruiter's email address or phone number. With **Redact contact details** on — the default — those are replaced with `[redacted]` and `description.contactsRedacted` is `true`; so is a personal profile address (linkedin.com/in/…). Neither `description.text` nor the optional HTML carries the address behind a link: the HTML keeps each link's words and drops its address, and drops images. The Actor never returns recruiter names or contact details as fields, under any setting: it never reads the application mailbox address, the application form or the cover photos in Recruitee's feed, and the schema has nowhere to put a person. If your use case is contacting individuals, this is the wrong tool.

### What people use it for

- **Competitor and market hiring signals.** Which companies are opening which roles, in which departments and countries, and how often — a daily monitor across a list of boards is one scheduled run.
- **Pay-transparency datasets.** Stated ranges across companies, roles and countries, as numbers with their currency and period.
- **Job aggregators and alert products.** A clean feed of new jobs from a curated list of employers, deduplicated and labelled by change.
- **Sales and partnership research at company level.** Growth signals from hiring, without collecting anything about the individuals involved.
- **Research and teaching.** A clean, repeatable dataset with a documented schema.

### Using the API

Run it from code with the Apify client, using your own API token:

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

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('adderleydata/recruitee-jobs-scraper').call({"boards":["jobs"],"maxResults":100});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.length, items[0]?.salary);
```

```python
import os
from apify_client import ApifyClient

client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("adderleydata/recruitee-jobs-scraper").call(run_input={"boards":["jobs"],"maxResults":100})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["title"], item["location"]["raw"], item["postedAt"])
```

Schedules, webhooks and the Make, Zapier, n8n and Google Sheets integrations all work the way they do for any Apify Actor. The Actor runs with limited permissions and is priced per event, so AI agents can call it through Apify's MCP server as well.

### Is it legal to scrape Recruitee careers sites?

The Actor reads Recruitee's public Careers Site API — the feed Recruitee documents for companies that list their jobs on their own website, with no login, no key and no rate tricks — and returns facts about job postings. It does not log in, does not solve CAPTCHAs, does not submit applications and does not collect personal information.

What you do with the data is your responsibility. Each company's job text is its own copyright — analyse it, do not republish it. If your project touches personal information, privacy law applies to you wherever you are. This is general information, not legal advice.

### Questions

**Where do I find a company's Recruitee name?** Open any job on the company's careers site. If the address is `acme.recruitee.com/o/<job>`, the name is `acme`; paste the whole link or just the name.

**The company's careers site is on its own domain.** Many companies show their Recruitee jobs at an address such as `careers.example.com`. That address does not say which Recruitee account is behind it, so the Actor asks for the `recruitee.com` name instead of guessing, and never sends a request to a domain Recruitee does not run. Every company on Recruitee has one; if the careers site links to `<name>.recruitee.com` anywhere, that is it.

**A board I gave came back as "not found".** Recruitee answers "Not Found" when no company on Recruitee uses that name. The run log names the board, and the other boards in the run are unaffected. A missing board is asked for once, not retried, and a run in which every board is missing finishes with an empty dataset and a message naming each one.

**Does it need a Recruitee login or API key?** No. The Careers Site API is public.

**Why is `employmentTypes` sometimes empty?** It is empty when the job states no employment type, when it is an apprenticeship (the schema has no such type), or for a type Recruitee adds later — nothing is guessed. Every other employment type Recruitee's careers site offers is mapped.

**Why does a job show `hybrid` when the company also ticked on-site?** Recruitee lets a company tick remote, hybrid and on-site together. `workArrangement` holds one value, the most flexible ticked. The **Locations** filter reads all of them, so "On-site" still finds that job.

**Can I get recruiter emails or phone numbers?** No, by design.

**How current is the data?** It is read from Recruitee while your run is in progress. `postedAt` is when the job was published and `updatedAt` when Recruitee last changed it; `scrapedAt` records when the row was produced.

**The field I need is not there.** Open an issue on the **Issues** tab. Fields are added to the schema without breaking existing ones.

### Support

Use the **Issues** tab on this page. We read it every day. Include the run ID and what you expected to see.

### Other Adderley Data Actors

Every Actor in a vertical returns the same fields, so adding a source needs no new code on your side.

- [Ashby Jobs Scraper — Company Job Boards](https://apify.com/adderleydata/ashby-jobs-scraper) — same `job.v1` fields
- [Breezy HR Jobs Scraper — Company Job Boards](https://apify.com/adderleydata/breezy-jobs-scraper) — same `job.v1` fields
- [Greenhouse Jobs Scraper — Company Job Boards](https://apify.com/adderleydata/greenhouse-jobs-scraper) — same `job.v1` fields
- [Lever Jobs Scraper — Company Job Boards](https://apify.com/adderleydata/lever-jobs-scraper) — same `job.v1` fields
- [Rippling Jobs Scraper — Company Job Boards](https://apify.com/adderleydata/rippling-jobs-scraper) — same `job.v1` fields
- [Workday Jobs Scraper — Company Job Boards](https://apify.com/adderleydata/workday-jobs-scraper) — same `job.v1` fields

### About

Made by Adderley Data, Melbourne — https://adderleydata.com. Not affiliated with, endorsed by or sponsored by Recruitee or Tellent. Recruitee is a trade mark of its owner and is used here only to describe what this Actor reads.

# Changelog

This Actor's version history is a separate document: https://apify.com/adderleydata/recruitee-jobs-scraper/changelog.md

# Actor input Schema

## `boards` (type: `array`):

One entry per company: its Recruitee name (in https://acme.recruitee.com it is "acme"), or any link on that address — the careers site, one of its jobs, or its apply page. A company that shows its jobs on its own domain (careers.example.com) still has a recruitee.com name, and that name is needed: the company's own domain does not say which Recruitee account is behind it. Up to 500 boards per run; each is one request.

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

Keep jobs whose title contains every word of any keyword, in any order — "engineer data" matches "Senior Data Engineer". Leave empty for all titles.

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

Keep jobs with a location containing this text ("Utrecht", "Berlin"), open in this country by Recruitee's own country code ("Germany", "Australia"), or with this work arrangement ticked ("Remote", "Hybrid", "On-site"). Every location a job lists counts. Leave empty for all locations.

## `departments` (type: `array`):

Keep jobs whose department contains this text, e.g. "Engineering". Leave empty for all departments.

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

Keep jobs published on Recruitee in the last N days. Leave empty for any time.

## `maxResults` (type: `integer`):

The run stops once this many jobs are saved. You are charged per job saved, so this is also your cost cap.

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

On: the full job text — the job description, then the job requirements — comes back with each row, read from the same request as the listing, so it costs no extra requests. Off: listing fields only.

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

Plain text, or plain text plus sanitised HTML.

## `redactContacts` (type: `boolean`):

On by default: email addresses and phone numbers inside description text are replaced with \[redacted]. This Actor never outputs recruiter names or contact fields.

## `incremental` (type: `boolean`):

Remember what earlier runs saw and save only jobs that are new, changed or gone. Unchanged jobs are skipped and not charged. Put the Actor on a schedule with this on.

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

Optional name for this monitor, e.g. "competitor-engineering". Runs with the same key share memory. Left empty, a key is derived from the boards and filters themselves.

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

Incremental mode only. When a complete run no longer finds a job it saw before, save one row with changeType EXPIRED.

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

Incremental mode only. Saves (and charges for) every job, labelled UNCHANGED where nothing moved.

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

Apify Proxy, automatic group, is the default and is what this Actor is tested with.

## `maxConcurrency` (type: `integer`):

Parallel requests. The default is deliberately modest.

## `maxRequestsPerMinute` (type: `integer`):

An upper bound on request rate across the whole run.

## Actor input object example

```json
{
  "boards": [
    "jobs"
  ],
  "maxResults": 20,
  "includeDescription": false,
  "descriptionFormat": "text",
  "redactContacts": true,
  "incremental": false,
  "emitExpired": true,
  "emitUnchanged": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "maxConcurrency": 4,
  "maxRequestsPerMinute": 90
}
```

# Actor output Schema

## `postings` (type: `string`):

One row per posting in the job.v1 schema; every key is always present and unknown is null. In incremental runs each row carries a changeType of NEW, UPDATED, REAPPEARED or EXPIRED.

## `runSummary` (type: `string`):

Requests made, first-attempt success, rows saved and skipped, rows that failed validation, and contact details redacted.

# 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 = {
    "boards": [
        "jobs"
    ],
    "maxResults": 20,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("adderleydata/recruitee-jobs-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "boards": ["jobs"],
    "maxResults": 20,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("adderleydata/recruitee-jobs-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "boards": [
    "jobs"
  ],
  "maxResults": 20,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call adderleydata/recruitee-jobs-scraper --silent --output-dataset

```

## MCP server setup

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

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/vjrgDBN1tUkbvS217/builds/GlrfksqOJN6agaQiU/openapi.json
