# Recruitee Jobs by Company: Careers Site Offers API (`yadroo/recruitee-jobs`) Actor

Published offers of any company careers site on Recruitee, from its keyless offers endpoint: title, department, city, country, work model, employment type, experience level, weekly hours, tags, publish date and apply URL. A summary mode counts open jobs per department, city, country or tag.

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

## Pricing

from $1.05 / 1,000 job row returneds

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

## Recruitee Jobs by Company: Careers Site Offers API

Read the open jobs of a company straight from the careers site it runs on Recruitee: **title, department, city, country, work model, employment type, experience level, weekly hours, tags, publish timestamp, offer page and apply link** — one row per published offer. You name the careers sites (a slug such as `fastned`, or any URL of that site); the actor reads each board once through the site's own keyless offers endpoint. No API key, no login, no proxy, no browser. A summary mode counts the open jobs per department, city, country, work model or tag, so you can see the shape of a board before you export it.

This is a per-company reader, not a job search: it answers "what does *this* employer have open right now", which is what recruiters, market researchers, remote job boards and hiring-signal pipelines ask about a watchlist of companies.

### Use cases

- **Watch one employer**: export every open offer of a careers site with department, location and apply link, and rerun it on a schedule to see the board change.
- **Feed a remote or hybrid job board**: the source carries three separate flags, so `workModel: remote` gives you the postings the employer really flagged remote, and `hybrid` gives you the middle group most exports throw away.
- **Country and market research**: filter a list of careers sites to `["NL"]`, `["DE"]`, `["GB"]` and count how many jobs each employer has open where you work.
- **Talent mapping on a watchlist**: scan 20–200 companies for one job family (`titleKeywords: ["engineer"]`) and drop the permanent "Open Application" posting with `excludeTitleKeywords`.
- **Hiring signal for a lead list**: `postedWithinDays: 7` plus `onlyNew` on a daily schedule writes only the openings that appeared since the last run — the classic "this company started hiring" trigger.
- **Job text for an LLM screener or a search index**: `includeDescription: true` adds the description and requirements as published HTML and as plain text, in the same request and at no extra call.

### Input

| Field | Type | Default | Meaning |
|---|---|---|---|
| `companies` | string\[] | **required** | Careers-site slugs (`fastned`) or any URL of the site (`https://fastned.recruitee.com/o/<offer>`). See [Finding the slug](#finding-the-slug). One entry = one request. |
| `mode` | string | `jobs` | `jobs` = one row per offer, `summary` = one row per group with a count. See [Modes](#modes). |
| `groupBy` | string | `department` | Group of the summary: `department`, `city`, `country`, `workModel`, `employmentType`, `category`, `experienceLevel`, `tag`. Used in `summary` mode only. |
| `titleKeywords` | string\[] | empty | Keep an offer when its title contains one of these words (case-insensitive). |
| `excludeTitleKeywords` | string\[] | empty | Drop an offer whose title contains one of these words. Applied after `titleKeywords`. |
| `departments` | string\[] | empty | Keep offers whose department contains one of these. Free text each company types itself — see [Reference](#reference). |
| `tags` | string\[] | empty | Keep offers carrying one of these tags, matched in full (case-insensitive). |
| `locationContains` | string\[] | empty | Keep offers whose city, state or country contains one of these, matched against **every** location of the offer. |
| `countryCodes` | string\[] | empty | Keep offers with a location in these ISO 3166-1 alpha-2 countries, e.g. `["NL","GB"]`. |
| `workModel` | string | `any` | `any`, `remote`, `hybrid`, `onsite` or `unspecified`. See [Work model](#work-model). |
| `employmentTypes` | string\[] | empty | Substring match over the employment-type code, e.g. `["fulltime"]`. See [Codes](#codes-of-the-platform). |
| `experienceLevels` | string\[] | empty | Substring match over the experience code, e.g. `["entry_level","student"]`. |
| `categories` | string\[] | empty | Substring match over the job-family code, e.g. `["information_technology"]`. |
| `postedWithinDays` | integer | empty | Keep offers published in the last N days (1–3650), counted in whole UTC days. |
| `onlyNew` | boolean | `false` | Write only offers this actor has not delivered before. See [Only new offers](#only-new-offers). |
| `includeDescription` | boolean | `false` | Add `descriptionHtml`, `descriptionText`, `requirementsHtml`, `requirementsText` to every row. |
| `sortBy` | string | `publishedDesc` | `publishedDesc`, `publishedAsc`, `titleAsc`, `departmentAsc` or `siteOrder`. |
| `maxItems` | integer | `50` | Stop after this many rows in total (1–5000). |
| `maxItemsPerCompany` | integer | empty | Cap the rows of each careers site before the global limit (1–2000). |
| `fields` | string\[] | all | Keep only these output fields, in this order. |

Filters combine with **AND** across fields and with **OR** inside one field: `titleKeywords: ["engineer","scientist"]` plus `countryCodes: ["NL"]` means "engineer or scientist, in the Netherlands".

### Reference

#### Finding the slug

The slug is the label in front of `.recruitee.com` on the careers site: `https://fastned.recruitee.com/` → `fastned`. Paste the whole URL of the site, of a single offer (`…/o/<offer>`) or of a localized board (`…/l/en/vacatures`) and the slug is read out of it.

Some employers put the careers site on their own domain (`jobs.company.com`), which does not contain the slug. Open any offer there and press the apply button: the application page runs on `<slug>.recruitee.com`, and that is the value you need. The actor refuses a foreign domain instead of guessing a host.

#### Modes

- `jobs` — one row per published offer of every careers site, ordered by `sortBy` across all of them.
- `summary` — the same request, but one row per group with the number of open jobs: `companySlug`, `companyName`, `groupBy`, `value`, `jobCount`. An offer open in three cities counts once per city, an offer with two tags counts once per tag, and offers with the field empty are collected under `(unspecified)`, so the groups add up to the board. Summary rows cost the same as job rows but there are far fewer of them.

#### Work model

The source keeps three separate flags per offer instead of one boolean, and this actor exposes them as `isRemote`, `isHybrid`, `isOnSite` plus a single label in `workModel`:

| `workModel` | Meaning |
|---|---|
| `remote` | the employer flagged the offer remote |
| `hybrid` | flagged hybrid (and not remote) |
| `onsite` | flagged on-site only |
| `unspecified` | the employer set no flag at all — invisible to a plain remote/on-site split |

An offer carrying more than one flag (it happens) is labelled by the widest one: remote before hybrid before on-site.

#### Codes of the platform

`employmentType`, `experienceLevel`, `category` and `educationLevel` are snake\_case codes, and the platform documents no closed list — the set differs per careers site. That is why those filters are substring matches: `["fulltime"]` keeps `fulltime`, `fulltime_permanent` and `fulltime_fixed_term`. Values seen on live careers sites on 2026-09-28 (examples, not a complete list):

- **employmentType**: `fulltime`, `fulltime_permanent`, `fulltime_fixed_term`, `parttime_permanent`, `parttime_fixed_term`, `contract`, `temporary`, `internship`
- **experienceLevel**: `student_school`, `student_college`, `entry_level`, `mid_level`, `experienced`, `manager`, `senior_manager`, `executive`, `senior_executive`
- **category**: `information_technology`, `internet`, `engineering`, `technical`, `sales`, `marketing_pr`, `finance`, `customer_service`, `recruitment_hr`, `administrative`, `management`, `logistics`, `procurement`, `construction`, `manufacturing`, `automotive`, `energy`, `design`, `consulting`, `architectural_services`, `legal_services`, `education`, `healthcare`, `hospitality`, `security`, `government_nonprofit`, `other`
- **educationLevel**: `high_school_coursework`, `high_school`, `college_coursework`, `associate_degree`, `bachelor_degree`, `master_degree`, `doctorate`, `certification`, `professional`

To read the vocabulary of **your own** companies, run `mode: summary` with `groupBy: employmentType`, `category` or `experienceLevel` once — the group values are exactly the codes you can filter on afterwards. Departments and tags work the same way: they are free text, so `groupBy: department` prints the names before you filter on them.

If a filter value matches nothing on a board, a close misspelling is corrected to the spelling that board uses (and logged, e.g. `departments "Netwrok Development" read as "Network Development"`); a value that resembles nothing stays as typed, is reported in the log and in `SUMMARY.filtersWithoutMatch`, and simply keeps no offer. The search is never widened behind your back.

#### Only new offers

With `onlyNew: true` the actor remembers careers site, offer id and publish timestamp in its own key-value store (`recruitee-jobs-state`) and writes only offers that were not there before. The first run writes everything it finds; later runs write the openings that appeared since. Offers cut by `maxItems` are not remembered, so they come back next time. A posting taken offline and published again counts as new.

### Examples

**Everything one employer has open**

```json
{ "companies": ["fastned"], "mode": "jobs", "maxItems": 25 }
```

**Remote offers across a watchlist**

```json
{ "companies": ["deephealth", "nmbrs", "greenflux"], "workModel": "remote", "maxItems": 25 }
```

**Engineering roles in the Netherlands, no open applications**

```json
{
  "companies": ["fastned", "greenflux", "nmbrs", "afsenergy"],
  "titleKeywords": ["engineer", "developer"],
  "excludeTitleKeywords": ["open application"],
  "countryCodes": ["NL"],
  "maxItems": 30
}
```

**Hiring signal: what appeared in the last week, once per posting**

```json
{
  "companies": ["deephealth", "rosebelgoldmines", "fastned", "afsenergy"],
  "postedWithinDays": 7,
  "onlyNew": true,
  "maxItems": 100
}
```

**How a board is organised (summary)**

```json
{ "companies": ["deephealth", "fastned", "nmbrs", "fortrade"], "mode": "summary", "groupBy": "department", "maxItems": 30 }
```

**Posting texts for an LLM screener, trimmed to four fields**

```json
{
  "companies": ["rosebelgoldmines", "afsenergy"],
  "includeDescription": true,
  "fields": ["companyName", "title", "applyUrl", "descriptionText"],
  "maxItems": 10
}
```

### Output

One row per published offer (`jobs` mode). A real row from a cloud run of the prefilled example (descriptions off, so no posting text):

```json
{
  "companySlug": "fastned",
  "companyName": "Fastned",
  "offerId": 2760203,
  "title": "Expansion manager France / Développeur.se Immobilier - Grand Est",
  "department": "Network Development",
  "workModel": "hybrid",
  "isRemote": false,
  "isHybrid": true,
  "isOnSite": false,
  "city": "Lyon",
  "state": "Auvergne-Rhône-Alpes",
  "country": "France",
  "countryCode": "FR",
  "countryCodes": [
    "FR"
  ],
  "cities": [
    "Lyon",
    "Strasbourg"
  ],
  "countries": [
    "France (FR)"
  ],
  "locationNames": [
    "Lyon, Auvergne-Rhône-Alpes, France",
    "Strasbourg, Grand-Est, France"
  ],
  "locationCount": 2,
  "employmentType": "fulltime_permanent",
  "experienceLevel": "mid_level",
  "educationLevel": "associate_degree",
  "category": "sales",
  "minHoursPerWeek": 40,
  "maxHoursPerWeek": 40,
  "salaryMin": null,
  "salaryMax": null,
  "salaryCurrency": null,
  "salaryPeriod": null,
  "tags": [],
  "status": "published",
  "publishedAt": "2026-09-25T14:04:48.000Z",
  "createdAt": "2026-09-25T14:02:08.000Z",
  "updatedAt": "2026-09-28T13:13:49.000Z",
  "closeAt": null,
  "daysSincePublished": 3,
  "highlight": null,
  "slug": "expansion-manager-france-developpeurse-immobilier-grand-est",
  "guid": "w86r9",
  "url": "https://fastned.recruitee.com/o/expansion-manager-france-developpeurse-immobilier-grand-est",
  "applyUrl": "https://fastned.recruitee.com/o/expansion-manager-france-developpeurse-immobilier-grand-est/c/new",
  "careersSiteUrl": "https://fastned.recruitee.com/",
  "found": true,
  "note": null,
  "fetchedAt": "2026-09-28T20:19:02.945Z"
}
```

| Field | Type | Meaning |
|---|---|---|
| `companySlug` | string | Careers site the row came from |
| `companyName` | string | Company name as the careers site publishes it |
| `offerId` | number | Id of the offer on the platform |
| `title` | string | Job title |
| `department` | string|null | Team name the employer typed |
| `workModel` | string | `remote`, `hybrid`, `onsite` or `unspecified` |
| `isRemote`, `isHybrid`, `isOnSite` | boolean | The three flags of the source, unmerged |
| `city`, `state`, `country`, `countryCode` | string|null | Primary location of the offer |
| `countryCodes`, `cities`, `countries`, `locationNames` | string\[] | **Every** location of the offer (multi-city postings) |
| `locationCount` | number | How many locations the offer lists |
| `employmentType`, `experienceLevel`, `educationLevel`, `category` | string|null | The platform codes — see [Codes](#codes-of-the-platform) |
| `minHoursPerWeek`, `maxHoursPerWeek` | number|null | Weekly hours when the employer filled them |
| `salaryMin`, `salaryMax`, `salaryCurrency`, `salaryPeriod` | number/string|null | Salary — published by few employers, usually null |
| `tags` | string\[] | Free labels of the employer (often empty) |
| `status` | string | Always `published`: nothing else is served |
| `publishedAt`, `createdAt`, `updatedAt`, `closeAt` | string|null | UTC ISO-8601 timestamps |
| `daysSincePublished` | number|null | Whole UTC days since `publishedAt` |
| `highlight` | string|null | Short teaser, rarely filled |
| `slug`, `guid` | string | Identifiers in the offer URL |
| `url`, `applyUrl`, `careersSiteUrl` | string | Offer page, application form (`…/c/new`), careers site |
| `found` | boolean | `false` on a marker row (see below) |
| `note` | string|null | Why a marker row exists |
| `fetchedAt` | string | Time of the request, UTC ISO-8601 |
| `descriptionHtml`, `descriptionText`, `requirementsHtml`, `requirementsText` | string|null | Only with `includeDescription: true` |

Always filled: `companySlug`, `companyName`, `offerId`, `title`, `url`, `applyUrl`, `workModel`, `status`, `publishedAt`, `daysSincePublished`, `locationCount`, `found`, `fetchedAt`.
Usually filled: `department`, `city`, `country`, `countryCode`, `countryCodes`, `locationNames`, `slug`, `guid`, `employmentType`, `createdAt`, `updatedAt`.
Often empty because the source is empty: `state`, `experienceLevel`, `educationLevel`, `category`, `minHoursPerWeek`, `maxHoursPerWeek`, `closeAt`, `salary*`, `highlight`, `tags`, `requirements*`.

A `summary` row is short: `companySlug`, `companyName`, `groupBy`, `value`, `jobCount`, `url`, `found`, `note`, `fetchedAt`.

**Marker rows.** A slug the platform does not serve (HTTP 404) and a careers site that currently publishes nothing both yield one row with `found: false` and a `note` saying which of the two it is — a watchlist of 200 companies never dies because one slug has a typo, and an empty run never looks like a silent success. Marker rows for unknown slugs are always written; the "publishes nothing" marker appears only when the run would otherwise be empty.

The application mailbox the source ships with every offer (`…@…recruitee.com`) is deliberately not mapped — the actor reads company postings, not contact data.

Dataset views: **Job offers** (overview), **Conditions and requirements** (type, experience, education, hours, salary, closing date, apply link), **Open jobs per group** (summary mode). Download as JSON, CSV, Excel, XML or HTML, or read the dataset through the API.

Each run also writes a `SUMMARY` record into the default key-value store: the careers sites requested and found, offers per site, what the filters cut, filter corrections, filters without a match, the request count and the run status line.

### Use it from code / agents

```bash
curl -X POST "https://api.apify.com/v2/acts/yadroo~recruitee-jobs/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"companies":["fastned","deephealth"],"workModel":"remote","maxItems":25}'
```

```js
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('yadroo/recruitee-jobs').call({ companies: ['fastned'], maxItems: 25 });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

```python
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("yadroo/recruitee-jobs").call(run_input={"companies": ["fastned"], "maxItems": 25})
items = client.dataset(run["defaultDatasetId"]).list_items().items
```

Dataset items can also be pulled directly: `GET https://api.apify.com/v2/datasets/<datasetId>/items?clean=true&format=csv`. For agents with a small context window, set `fields` (e.g. `["companyName","title","city","applyUrl"]`) — the rows shrink to what the model needs, and `includeDescription` stays off.

MCP: add `https://mcp.apify.com` to Claude / Cursor / any MCP client and call the `yadroo/recruitee-jobs` tool with the same JSON input.

### Pricing

Pay per event: **$0.001 per run start + $0.0015 per dataset row** (offer row, summary row or marker row). There is no per-company fee and no extra charge for descriptions, filters or the summary mode — every careers site costs one request either way.

| Run | Rows | Cost |
|---|---|---|
| One careers site, whole board | 25 | $0.001 + 25 × $0.0015 = **$0.0385** |
| The prefilled example (three careers sites, default `maxItems` 50) | 50 | $0.001 + 50 × $0.0015 = **$0.076** |
| Watchlist of 20 companies, ~6 offers each | 120 | $0.001 + 120 × $0.0015 = **$0.181** |
| The same 20 companies as a department summary | ~60 | $0.001 + 60 × $0.0015 = **$0.091** |
| Daily watch with `onlyNew`, 3 new postings | 3 + 1 marker | $0.001 + 4 × $0.0015 = **$0.007** |

The start event is charged on every run, including a run that finds nothing. Apify platform usage (compute) is billed by Apify on top and is tiny here: the actor runs at 256 MB without a browser, and a typical run takes 20–40 seconds. `maxItems` is a hard stop, so a run can never cost more than `0.001 + maxItems × 0.0015`.

### Limits & FAQ

**Why does my company return nothing?** Only offers with status `published` are served. Drafts, internal postings and closed jobs are invisible, and an employer between hiring rounds answers with an empty board — you get a `found: false` row saying so, not an error.

**I get `found: false` with a 404 note.** That slug is not served. Open `https://<slug>.recruitee.com/` in a browser: if the page is missing too, the employer publishes under a different slug, which is often not the company name.

**Can I search all companies at once?** No. The endpoint is per careers site; there is no cross-employer search on this platform. You bring the slugs — that is also why the data is live on every run and never an index that lags behind.

**Where do I find the slug when the careers site runs on the company's own domain?** Press apply on any offer: the application page runs on `<slug>.recruitee.com`. See [Finding the slug](#finding-the-slug).

**Why is the salary empty?** Most employers on this platform do not publish salary. The fields exist (`salaryMin`, `salaryMax`, `salaryCurrency`, `salaryPeriod`) and are filled when the board has them, but do not plan a salary study around them.

**Why are the codes not documented?** The platform never published a closed list for employment type, category, experience and education, and the values differ per careers site. Use `mode: summary` to read what your companies actually use; the filters are substring matches so partial codes work.

**How fresh is the data?** Every run reads the live board, so rows are as fresh as the careers site itself — there is no cache and no index in between. `fetchedAt` records the moment of the request; all timestamps are UTC ISO-8601.

**Rate limits and blocks.** The source documents no rate limit. The actor reads at most four careers sites in parallel, one request each, and backs off on 429/5xx while honouring `Retry-After`. No proxy, no browser, no login. A careers site that keeps failing gets a marker row with the reason; a run fails only when no careers site answered at all.

**How big can a board be?** One request returns the whole published board — boards of 70+ offers came back complete. `maxItems` and `maxItemsPerCompany` cut rows after the fact, so a single big employer cannot fill a watchlist dataset.

**What is not included?** Application mailboxes and application questions (personal-data hygiene), cover images and social-sharing fields, translations of an offer into other languages, and anything behind the apply form. Closed and draft offers do not exist for this endpoint.

***

Made by **Yadroo**. Sibling actors: [greenhouse-jobs](https://apify.com/yadroo/greenhouse-jobs) · [workable-jobs](https://apify.com/yadroo/workable-jobs) · [ashby-job-postings](https://apify.com/yadroo/ashby-job-postings) · [hh-kz-vacancies](https://apify.com/yadroo/hh-kz-vacancies) · [domain-intel](https://apify.com/yadroo/domain-intel)

# Actor input Schema

## `companies` (type: `array`):

One or more careers-site slugs, e.g. `fastned`, `deephealth`, `nmbrs`. The slug is the label in front of `.recruitee.com` on the careers site — paste a whole careers-site or job URL (`https://fastned.recruitee.com/o/<job>`) and the slug is taken from it. Custom careers-site domains (`jobs.company.com`) hide the slug: open any job on that site, the apply page still carries the `<slug>.recruitee.com` host. One entry costs one request; a slug nobody publishes on answers 404 and becomes a single row with `found: false` instead of failing the run.

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

`jobs` writes a row per published offer of every careers site. `summary` groups the same offers by the field chosen below and writes a count per value — the cheap way to see how a company is organised, where it hires and which codes its filters accept. Both modes read the same single request per careers site, so a summary never costs extra calls.

## `groupBy` (type: `string`):

Only used when *What to return* is `summary`. An offer open in three cities counts once per city; an offer with two tags counts once per tag. Offers where the field is empty are collected under the value `(unspecified)`, so the counts always add up to the board. The platform never documented the `employmentType`, `category` and `experienceLevel` vocabularies — grouping by them is how you read the codes a careers site really uses before you filter on them.

## `titleKeywords` (type: `array`):

Keep an offer when its title contains at least one of these words, case-insensitive, e.g. \["engineer", "scientist"]. Empty = every published offer of the careers site.

## `excludeTitleKeywords` (type: `array`):

Drop an offer when its title contains any of these words, e.g. \["intern", "open application"]. Applied after *Job title contains any of*. Careers sites often keep a permanent "Open Application" offer online — this is how you leave it out.

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

Keep offers whose department contains one of these, case-insensitive, e.g. \["Engineering", "Sales"]. Departments are free text every company types itself, so run `summary` grouped by department once to read the exact list. Offers with no department are dropped by this filter.

## `tags` (type: `array`):

Keep offers carrying one of these tags, matched in full and case-insensitive. Tags are optional labels the employer sets; many careers sites use none at all, so check with `summary` grouped by tag before you rely on this filter.

## `locationContains` (type: `array`):

Keep offers whose city, state or country contains one of these, e.g. \["Amsterdam", "London", "Suriname"]. Matched against every location of an offer, so a posting open in three cities is kept when one of them matches.

## `countryCodes` (type: `array`):

Keep offers with a location in these countries by two-letter code, e.g. \["NL", "US", "GB"]. The code comes from the offer's own location record, so it is exact where *Location contains any of* is a text match. Every matched code of a row is listed in the output field `countryCodes`.

## `workModel` (type: `string`):

The source carries three separate flags per offer (remote, hybrid, on-site) instead of one boolean, so hybrid work is a value of its own here and not a guess. Employers who left all three flags off appear as `unspecified` — pick that value to find them, they are invisible to a plain remote/on-site split.

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

Keep offers whose employment-type code contains one of these, case-insensitive. Codes seen on live careers sites: `fulltime_permanent`, `fulltime_fixed_term`, `fulltime`, `parttime`, `contract`, `internship`, `freelance`. The platform documents no closed list, which is why this is a text match: `fulltime` keeps both fulltime codes. Run `summary` grouped by employment type to read the codes of your own list of companies.

## `experienceLevels` (type: `array`):

Keep offers whose experience code contains one of these, e.g. \["entry\_level", "mid\_level", "experienced", "manager", "student", "doctorate"]. Optional on the platform: part of the careers sites leave it empty, so use it to narrow a large board, not as your only filter.

## `categories` (type: `array`):

Keep offers whose job-family code contains one of these, e.g. \["information\_technology", "sales", "marketing", "internet", "finance"]. Like the other codes this vocabulary is undocumented — `summary` grouped by category lists what your companies actually use.

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

Keep offers first published in the last N days — the "who started hiring recently" filter for hiring-signal and lead pipelines. The source gives the publish moment with a clock time in UTC, so short windows work as well. Empty = no age limit.

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

Remember careers site and offer id in this actor's key-value store and write only offers that were not there on the previous run. The first run writes everything it finds, later runs write the openings that appeared since. Use it on a schedule to follow a list of companies without paying for the same rows twice.

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

Add `descriptionHtml` and `requirementsHtml` (as the employer published them) plus `descriptionText` and `requirementsText` (tags stripped, entities decoded, blank lines kept) to every offer row. The texts arrive in the same request, so this costs no extra call — it only makes rows roughly twenty times larger, which is why it is off by default.

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

Order of the rows before *Max rows* cuts the list. With several careers sites the rows are sorted across all of them, after *Max rows per careers site* was applied. In `summary` mode rows are ordered by job count, largest first.

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

Stop after this many rows in total. A small employer publishes 1-10 offers, a mid-size one 20-60; boards above a few hundred are rare on this platform.

## `maxItemsPerCompany` (type: `integer`):

Cap the rows taken from each careers site before the global *Max rows*, so one big employer cannot fill the whole dataset when you watch a list of companies. Empty = no per-company cap.

## `fields` (type: `array`):

Keep only these fields, in this order, e.g. \["companyName", "title", "city", "applyUrl"]. Empty = every field the mode produces.

## Actor input object example

```json
{
  "companies": [
    "fastned",
    "deephealth",
    "nmbrs"
  ],
  "mode": "jobs",
  "groupBy": "department",
  "workModel": "any",
  "onlyNew": false,
  "includeDescription": false,
  "sortBy": "publishedDesc",
  "maxItems": 50
}
```

# 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 = {
    "companies": [
        "fastned",
        "deephealth",
        "nmbrs"
    ]
};

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

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

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

```

## Python example

```python
from apify_client import ApifyClient

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

# Prepare the Actor input
run_input = { "companies": [
        "fastned",
        "deephealth",
        "nmbrs",
    ] }

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

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

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

```

## CLI example

```bash
echo '{
  "companies": [
    "fastned",
    "deephealth",
    "nmbrs"
  ]
}' |
apify call yadroo/recruitee-jobs --silent --output-dataset

```

## MCP server setup

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

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

## OpenAPI specification

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