# SmartRecruiters Jobs Scraper: Job Board Snapshots & Changes (`cliqtomedia/smartrecruiters-jobs-monitor-scraper`) Actor

Read open jobs from SmartRecruiters company boards and compare each run with your last snapshot to see new, changed and removed postings. No login or API key.

- **URL**: https://apify.com/cliqtomedia/smartrecruiters-jobs-monitor-scraper.md
- **Developed by:** [Cliqto Media](https://apify.com/cliqtomedia) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.72 / 1,000 job posting rows

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

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

Get the current public job postings of SmartRecruiters companies you name, as clean rows with stable IDs. Run it again with your last snapshot and see which jobs are new, changed or gone.

Add a company ID such as `smartrecruiters`, press **Start**, and open the Dataset. A first run with one small company takes a few seconds. You do not need a SmartRecruiters login, an API key or a proxy.

![Input, job rows and change list in three steps](https://api.apify.com/v2/key-value-stores/8tMksdJK0bfTHMdPW/records/hero.png)

### Table of contents

- [What this Actor does](#what-this-actor-does)
- [What data you can get](#what-data-you-can-get)
- [Quick start](#quick-start)
- [Full input reference](#full-input-reference)
- [Input examples](#input-examples)
- [Output example](#output-example)
- [Full output field reference](#full-output-field-reference)
- [Pricing and cost examples](#pricing-and-cost-examples)
- [Use cases](#use-cases)
- [Scheduling and monitoring](#scheduling-and-monitoring)
- [API, MCP and integrations](#api-mcp-and-integrations)
- [Limits and expected partial results](#limits-and-expected-partial-results)
- [Troubleshooting](#troubleshooting)
- [FAQ](#faq)
- [Related Actors](#related-actors)
- [Support](#support)

### What this Actor does

The Actor reads the public SmartRecruiters Posting API for each company you list. It returns one dataset row for each open posting. For each company it also tells you if the read was complete, stopped by your limit, or partial.

You can then compare two runs. Save the `SNAPSHOT` record of one run and pass it as `previousSnapshot` to the next run. Each row gets a `changeType`: `new`, `updated`, `unchanged` or `removed`.

#### Good fit

- Track the open jobs of a known list of employers on SmartRecruiters.
- Export job titles, locations, departments and apply links to JSON, CSV or Excel.
- Find out when a posting is added, edited or taken down, without building your own diff.

#### Not a fit

- It does not search all SmartRecruiters companies. You must know the company ID.
- It reads only public postings. It cannot see internal or private jobs, candidates or applications.
- It does not send alerts. You can schedule runs and use Apify integrations to react to the output.
- It is an independent tool. It is not made by, or affiliated with, SmartRecruiters.

### What data you can get

Each row has 64 fields. The main groups are:

- **Identity:** `source`, `sourceId`, `companyIdentifier`, `postingId`, `uuid`, `jobId`, `jobAdId`, `refNumber`.
- **Job:** `title`, `companyName`, `releasedDate`, `visibility`, `active`.
- **Place:** `locationCity`, `locationRegion`, `locationCountry`, `fullLocation`, `remote`, `hybrid`, `latitude`, `longitude`, `address`, `postalCode`.
- **Classification:** `industry`, `department`, `function`, `typeOfEmployment`, `experienceLevel`, `language` (a code such as `en`) and `languageName`, most with an ID field.
- **Links and text:** `detailUrl`, `postingUrl`, `applyUrl`, `referralUrl`, and four description sections as HTML and as plain text. Some fields exist only when `includeDetails` is on.
- **Status and diff:** `companyStatus`, `scanComplete`, `detailStatus`, `changeType`, `event`, `changedFields`, `closedAt`, `contentHash`.
- **Provenance:** `fetchedAt`, `observedAt`, `sourceListUrl`, `sourceDetailUrl`, `warnings`.

The run also writes four records to the key-value store: `OUTPUT` (one entry per company), `RUN_SUMMARY` (counters and warnings), `SNAPSHOT` (feed this into the next run) and `CHANGES` (only when you passed `previousSnapshot`).

### Quick start

1. Find the company ID. Open the company career page. If the address is `https://jobs.smartrecruiters.com/McDonaldsCorporation`, the ID is `McDonaldsCorporation`. IDs are not case sensitive.
2. Open the **Input** tab and add the ID to **Companies**. You can also paste the board URL.
3. Keep the other fields as they are. The defaults read up to 10 jobs per company with full details.
4. Click **Start**.
5. Open the **Output** tab, then **Job rows**. Check `title`, `fullLocation` and `postingUrl` of the first row.

Expected result: one row per posting, up to your limit. Open the key-value store record `RUN_SUMMARY` to confirm `outcome` is `succeeded`.

### Full input reference

| Field | Plain name | Type | Required | Default | Allowed values | Effect | Recommendation |
| --- | --- | --- | --- | --- | --- | --- | --- |
| `companies` | Companies | array of text | No | `["smartrecruiters"]` | 1 to 25 items. Each item is a company ID or a `https://jobs.smartrecruiters.com/{id}` or `https://careers.smartrecruiters.com/{id}` URL. | Boards to read. Items are trimmed. Duplicates are merged without regard to case. A bad item stops the run with an input error that names it. | Start with 1 to 3 companies. |
| `maxJobsPerCompany` | Max jobs per company | integer | No | `10` | 1 to 1000 | Stops reading a company after this many postings. A company stopped by this limit has status `capped` and never produces removals. | Use 10 to test. Use 1000 to read a whole board. |
| `includeDetails` | Include posting details | boolean | No | `true` | `true`, `false` | `true` makes one extra request per posting and fills links, descriptions and compensation. `false` is much faster but those fields are `null`. | Keep `true` unless you only need titles and locations. |
| `descriptionFormat` | Description format | text | No | `both` | `both`, `html`, `text` | Chooses which description fields are filled when details are on. The unchosen fields stay `null`. | `both` for exports. `text` for search or AI input. |
| `previousSnapshot` | Previous snapshot | array of objects | No | not set | Up to 20000 entries. Use the `entries` array from a past `SNAPSHOT` record. | Turns on `changeType` and `removed` rows and writes the `CHANGES` record. | Pass the last run's `SNAPSHOT.entries` unchanged. |
| `outputMode` | Output mode | text | No | `all` | `all`, `changes` (`full` is accepted as `all`) | `all` returns every current posting. `changes` returns only `new`, `updated` and `removed` rows and needs `previousSnapshot`. | `changes` for daily monitors. |
| `timeoutSecs` | Run deadline (seconds) | integer | No | `900` | 30 to 3600 | After this time the Actor stops, saves what it has, and marks unfinished companies `partial`. | Raise it for 1000 jobs with details. |

An empty input uses the defaults. Invalid input fails the run with `RUN_SUMMARY.outcome` set to `input_error`, no rows and no requests.

#### Interacting settings

| When | Fields | Behavior |
| --- | --- | --- |
| Details off | `includeDetails: false` with `descriptionFormat` | `descriptionFormat` has no effect. Link, description, address and compensation fields are `null`. |
| Changes only | `outputMode: "changes"` without `previousSnapshot` | Input error. |
| Details switched between runs | `includeDetails` differs from the run that made the snapshot | Old and new hashes are not comparable. Rows keep `changeType: "unchanged"` and show `updateComparable: false`. Keep this setting the same between runs. |
| Cap reached | `maxJobsPerCompany` smaller than the board | Status `capped`. Postings after the cap are not read, so no `removed` rows are made for that company. |

### Input examples

#### Quick check, one company

```json
{
  "companies": ["smartrecruiters"],
  "maxJobsPerCompany": 5,
  "includeDetails": true
}
```

Expected behavior: reads one board and up to 5 postings with details. Expected output: one row per posting with `title`, `fullLocation`, `postingUrl`, `companyStatus`. See the [output example](#output-example), which comes from this input.

#### Several boards, plain text descriptions

```json
{
  "companies": ["smartrecruiters", "McDonaldsCorporation", "https://jobs.smartrecruiters.com/Sodexo"],
  "maxJobsPerCompany": 200,
  "descriptionFormat": "text",
  "timeoutSecs": 600
}
```

Expected behavior: reads three boards in order. Each is `complete` if it has 200 postings or fewer, otherwise `capped`.

#### Daily monitor, only changes

```json
{
  "companies": ["McDonaldsCorporation"],
  "maxJobsPerCompany": 1000,
  "outputMode": "changes",
  "previousSnapshot": [
    {
      "companyIdentifier": "McDonaldsCorporation",
      "postingId": "744000120624847",
      "title": "Franchisee UK & Ireland",
      "releasedDate": "2026-04-14T07:57:07.974Z",
      "contentHash": "cad217196c635e815cf322e6b27c09ba1127e68403ea25737c4dcbca07c68fa5",
      "hashBasis": "detail"
    }
  ]
}
```

Expected behavior: rows for new, updated and removed postings only. Normally you paste the whole `entries` array of the last `SNAPSHOT`; one entry is shown here to keep the example short. Removed rows are made only if the company ended `complete`.

### Output example

This row is from a real run of the current source on the public API with the first example input (the Cloud build is refreshed before release). The four description pairs are shortened here; the real values are full text.

```json
{
  "source": "smartrecruiters",
  "sourceId": "smartrecruiters:smartrecruiters:744000148454651",
  "companyIdentifier": "smartrecruiters",
  "postingId": "744000148454651",
  "uuid": "f4a3d5b9-5fa9-48af-ba55-30669c9c3e81",
  "jobId": "77d771c2-3c32-4eec-b9a1-9b7cd6684a16",
  "jobAdId": "f9fbb59b-d9db-4c26-a4c9-959f920b370a",
  "refNumber": "REF2025N",
  "title": "Data Operations Consultant",
  "companyName": "SmartRecruiters Inc",
  "releasedDate": "2026-09-09T09:43:26.403Z",
  "visibility": "PUBLIC",
  "active": true,
  "locationCity": "Poland",
  "locationRegion": "Remote",
  "locationCountry": "pl",
  "fullLocation": "Poland, Remote, Poland",
  "remote": true,
  "hybrid": false,
  "latitude": null,
  "longitude": null,
  "address": null,
  "postalCode": null,
  "industry": "Computer Software",
  "industryId": "computer_software",
  "department": "Technical Services",
  "departmentId": "5408931",
  "function": "Information Technology",
  "functionId": "information_technology",
  "typeOfEmployment": "Contract",
  "typeOfEmploymentId": "contract",
  "experienceLevel": "Associate",
  "experienceLevelId": "associate",
  "language": "en",
  "languageName": "English",
  "detailUrl": "https://api.smartrecruiters.com/v1/companies/smartrecruiters/postings/744000148454651",
  "postingUrl": "https://jobs.smartrecruiters.com/smartrecruiters/744000148454651-data-operations-consultant-",
  "applyUrl": "https://jobs.smartrecruiters.com/smartrecruiters/744000148454651-data-operations-consultant-?oga=true",
  "referralUrl": "https://jobs.smartrecruiters.com/external-referrals/company/smartrecruiters/publication/f4a3d5b9-5fa9-48af-ba55-30669c9c3e81?dcr_ci=smartrecruiters",
  "companyDescriptionHtml": "<p>SmartRecruiters is the Recruiting AI Company that transfo…",
  "jobDescriptionHtml": "<p><strong>Contract Type: 12-Month Fixed-Term Contract</stro…",
  "qualificationsHtml": "<p><strong>Qualifications</strong></p><p>At least 2 years of…",
  "additionalInformationHtml": "<p>SmartRecruiters is proud to be an Equal Employment Opport…",
  "companyDescriptionText": "SmartRecruiters is the Recruiting AI Company that transforms…",
  "jobDescriptionText": "Contract Type: 12-Month Fixed-Term Contract\n\nWe're looking f…",
  "qualificationsText": "Qualifications\n\nAt least 2 years of experience with ETL tool…",
  "additionalInformationText": "SmartRecruiters is proud to be an Equal Employment Opportuni…",
  "compensation": null,
  "companyStatus": "complete",
  "scanComplete": true,
  "detailStatus": "ok",
  "detailHttpStatus": 200,
  "event": "NEW",
  "closedAt": null,
  "changeType": null,
  "changedFields": null,
  "updateComparable": null,
  "contentHash": "1e423cdd2467f140c1efe305e2c673e18a318f42853330a1cedcd085dee9195f",
  "hashBasis": "detail",
  "fetchedAt": "2026-10-02T05:04:11.375Z",
  "observedAt": "2026-10-02T05:04:11.375Z",
  "sourceListUrl": "https://api.smartrecruiters.com/v1/companies/smartrecruiters/postings?limit=25&offset=0",
  "sourceDetailUrl": "https://api.smartrecruiters.com/v1/companies/smartrecruiters/postings/744000148454651",
  "warnings": []
}
```

`null` means the source has no value or the field needs details. `changeType` is `null` because no `previousSnapshot` was given. An empty string means the source section is empty. `event` is `NEW` for every row when no `previousSnapshot` was given. A removed row only has the keys `source`, `sourceId`, `event` (`CLOSED`), `observedAt`, `scanComplete`, `closedAt`, `companyIdentifier`, `postingId`, `title`, `releasedDate`, `changeType`, `contentHash`, `hashBasis`, `companyStatus` and `fetchedAt`.

### Full output field reference

All 64 job row fields. Types are JSON types.

| Field | Meaning | Type | Can be empty? | Example |
| --- | --- | --- | --- | --- |
| `source` | Always smartrecruiters. | string | no | `"smartrecruiters"` |
| `sourceId` | smartrecruiters:{companyIdentifier}:{postingId}. | string | no | `"smartrecruiters:smartrecruiters:744000148454651"` |
| `companyIdentifier` | Canonical SmartRecruiters company identifier from the API (not your spelling). | string | no | `"smartrecruiters"` |
| `postingId` | Stable ID of the job inside the company. With companyIdentifier it is the row key. | string | no | `"744000148454651"` |
| `uuid` | Posting UUID from the source. | string or null | yes (null) | `"f4a3d5b9-5fa9-48af-ba55-30669c9c3e81"` |
| `jobId` | Job ID from the detail record. Null when details are off or unavailable. | string or null | yes (null) | `"77d771c2-3c32-4eec-b9a1-9b7cd6684a16"` |
| `jobAdId` | ID of the job ad. | string or null | yes (null) | `"f9fbb59b-d9db-4c26-a4c9-959f920b370a"` |
| `refNumber` | Employer reference number, if set. | string or null | yes (null) | `"REF2025N"` |
| `title` | Job title from the source with surrounding spaces removed. | string or null | yes (null) | `"Data Operations Consultant"` |
| `companyName` | Display name of the company. | string or null | yes (null) | `"SmartRecruiters Inc"` |
| `releasedDate` | When the posting was released (ISO 8601 text). | string or null | yes (null) | `"2026-09-09T09:43:26.403Z"` |
| `visibility` | Posting visibility from the source, for example PUBLIC. | string or null | yes (null) | `"PUBLIC"` |
| `active` | Whether the posting is active. Detail only. | boolean or null | yes (null) | `true` |
| `locationCity` | City from the posting location. | string or null | yes (null) | `"Poland"` |
| `locationRegion` | Region or state from the posting location. | string or null | yes (null) | `"Remote"` |
| `locationCountry` | Country code from the posting location. | string or null | yes (null) | `"pl"` |
| `fullLocation` | Readable location line. | string or null | yes (null) | `"Poland, Remote, Poland"` |
| `remote` | True when the posting is remote. | boolean or null | yes (null) | `true` |
| `hybrid` | True when the posting is hybrid. | boolean or null | yes (null) | `false` |
| `latitude` | Latitude as text. Often null. | string or null | yes (null) | `null` |
| `longitude` | Longitude as text. Often null. | string or null | yes (null) | `null` |
| `address` | Street address. Detail only, often null. | string or null | yes (null) | `null` |
| `postalCode` | Postal code. Detail only, often null. | string or null | yes (null) | `null` |
| `industry` | Industry label. | string or null | yes (null) | `"Computer Software"` |
| `industryId` | Industry ID as text. | string or null | yes (null) | `"computer_software"` |
| `department` | Department label. Null when the employer left it empty. | string or null | yes (null) | `"Technical Services"` |
| `departmentId` | Department ID as text. | string or null | yes (null) | `"5408931"` |
| `function` | Job function label. | string or null | yes (null) | `"Information Technology"` |
| `functionId` | Job function ID as text. | string or null | yes (null) | `"information_technology"` |
| `typeOfEmployment` | Employment type label, for example Full-time. | string or null | yes (null) | `"Contract"` |
| `typeOfEmploymentId` | Employment type ID as text. | string or null | yes (null) | `"contract"` |
| `experienceLevel` | Experience level label. | string or null | yes (null) | `"Associate"` |
| `experienceLevelId` | Experience level ID as text. | string or null | yes (null) | `"associate"` |
| `language` | Language code, for example en or en-GB. | string or null | yes (null) | `"en"` |
| `languageName` | Readable language name from the source, for example English. | string or null | yes (null) | `"English"` |
| `detailUrl` | Public API URL of the posting detail record. Always set. | string | no | `"https://api.smartrecruiters.com/v1/companies/smartrecrui…` |
| `postingUrl` | Public job page. With details it is the posting URL from the source; in list-only runs it is derived from the company ID and posting ID (warning posting_url_derived). | string or null | yes (null) | `"https://jobs.smartrecruiters.com/smartrecruiters/7440001…` |
| `applyUrl` | Apply link. Detail only: null when includeDetails is false. | string or null | yes (null) | `"https://jobs.smartrecruiters.com/smartrecruiters/7440001…` |
| `referralUrl` | Public referral link from the detail record. Null without details. | string or null | yes (null) | `"https://jobs.smartrecruiters.com/external-referrals/comp…` |
| `companyDescriptionHtml` | Company section as raw HTML. Detail only. | string or null | yes (null) | `"<p>SmartRecruiters is the Recruiting AI Company that tra…` |
| `jobDescriptionHtml` | Job section as raw HTML. Detail only. | string or null | yes (null) | `"<p><strong>Contract Type: 12-Month Fixed-Term Contract</…` |
| `qualificationsHtml` | Qualifications section as raw HTML. Can be an empty string. | string or null | yes (null) | `"<p><strong>Qualifications</strong></p><p>At least 2 year…` |
| `additionalInformationHtml` | Additional information section as raw HTML. Can be an empty string. | string or null | yes (null) | `"<p>SmartRecruiters is proud to be an Equal Employment Op…` |
| `companyDescriptionText` | Plain-text version of the company section. | string or null | yes (null) | `"SmartRecruiters is the Recruiting AI Company that transf…` |
| `jobDescriptionText` | Plain-text version of the job section. | string or null | yes (null) | `"Contract Type: 12-Month Fixed-Term Contract\n\nWe're loo…` |
| `qualificationsText` | Plain-text version of the qualifications section. | string or null | yes (null) | `"Qualifications\n\nAt least 2 years of experience with ET…` |
| `additionalInformationText` | Plain-text version of the additional information section. | string or null | yes (null) | `"SmartRecruiters is proud to be an Equal Employment Oppor…` |
| `compensation` | Pay object when the employer publishes it, otherwise null. | object or null | yes (null) | `null` |
| `companyStatus` | How completely the company was read: complete, capped, partial, empty_or_unknown or error. | string | no | `"complete"` |
| `scanComplete` | True when the company was read completely (companyStatus complete). | boolean | no | `true` |
| `detailStatus` | ok, unavailable, error or skipped for this posting. | string | no | `"ok"` |
| `detailHttpStatus` | HTTP status of the detail request. Null when skipped. | integer or null | yes (null) | `200` |
| `event` | NEW, UPDATED, UNCHANGED or CLOSED, derived from changeType. Without previousSnapshot every row is NEW. | string | no | `"NEW"` |
| `closedAt` | Time a removed row was detected. Null for all other rows. | string or null | yes (null) | `null` |
| `changeType` | new, updated, unchanged or removed. Null when no previousSnapshot was given. | string or null | yes (null) | `null` |
| `changedFields` | Names of changed fields when they can be named (title, releasedDate). | array or null | yes (null) | `null` |
| `updateComparable` | False when the old and new snapshot used different hash bases, so an update could not be tested. | boolean or null | yes (null) | `null` |
| `contentHash` | SHA-256 of the normalized posting content. Used for diffs. | string | no | `"1e423cdd2467f140c1efe305e2c673e18a318f42853330a1cedcd085…` |
| `hashBasis` | list or detail: which data the hash was built from. | string | no | `"detail"` |
| `fetchedAt` | When the Actor read this row. Never counts as a change. | string | no | `"2026-10-02T05:04:11.375Z"` |
| `observedAt` | Same value as fetchedAt. Never counts as a change. | string | no | `"2026-10-02T05:04:11.375Z"` |
| `sourceListUrl` | Public API list page the row came from. | string | no | `"https://api.smartrecruiters.com/v1/companies/smartrecrui…` |
| `sourceDetailUrl` | Public API detail URL. Null when details were not read. | string or null | yes (null) | `"https://api.smartrecruiters.com/v1/companies/smartrecrui…` |
| `warnings` | Row-level notes, for example detail_unavailable. | array | no | `[]` |

#### Key-value store records

| Record | Content |
| --- | --- |
| `OUTPUT` | `companies[]`: `input`, `identifier`, `canonicalIdentifier`, `status`, `reasons`, `totalFound`, `rowsEmitted`, `pages`, `detailsOk`, `detailsUnavailable`, `detailsError`, `detailsSkipped`, `duplicatesDropped`, `startedAt`, `finishedAt`. |
| `RUN_SUMMARY` | `outcome` (`succeeded`, `partial`, `failed`, `aborted`, `deadline`, `charge_limit`, `input_error`), `message`, `durationMs`, normalized `input`, `inputErrors`, `counters` (rows, requests, retries, bytes, HTTP status counts, `changeCounts`), `removalsSuppressed`, `warnings`. |
| `SNAPSHOT` | `entries[]` with `companyIdentifier`, `postingId`, `title`, `releasedDate`, `contentHash`, `hashBasis`, plus per-company status. Pass `entries` as `previousSnapshot`. |
| `CHANGES` | Only with `previousSnapshot`: `counts`, and keys of `added`, `updated`, `removed`, plus `removalsSuppressed`. |

#### Company status values

| `companyStatus` | Meaning |
| --- | --- |
| `complete` | All postings were read and the count matched the source. |
| `capped` | Stopped at `maxJobsPerCompany` while more exist. |
| `partial` | A page or a detail failed, the count changed during the run, or the deadline came. Rows read so far are kept. |
| `empty_or_unknown` | The source returned 0 postings. The API cannot tell a company with no jobs from a wrong ID. |
| `error` | The board could not be read. |

### Pricing and cost examples

The Actor uses pay per event pricing. You pay only for these two events. Apify platform usage (compute, storage) is included.

| Event | Price (USD) | When it is charged |
| --- | ---: | --- |
| Actor start | 0.002 | Once per run (memory up to 1 GB, which covers every allowed setting). |
| Job posting row | 0.00072 | For each row stored in the dataset, including `removed` rows. |

Price examples:

| Rows stored in the run | Price (USD) |
| ---: | ---: |
| 0 (for example a daily monitor run with no changes, or an empty board) | 0.002 |
| 10 | 0.0092 |
| 100 | 0.074 |
| 1,000 | 0.722 |
| 2,000 (largest tested run) | 1.442 |

Rows are never charged twice in one run, and companies that return nothing add no row charge. A run with invalid input fails before any request but still costs the start event. To cap the cost of one run, set a maximum cost per run in the run options (API parameter `maxTotalChargeUsd`); the Actor then stops with `RUN_SUMMARY.outcome` set to `charge_limit`.

Cost drivers (run time and memory), measured on Apify in October 2026:

| Scenario | Input | Rows | Requests to SmartRecruiters | Run time | Peak memory |
| --- | --- | ---: | ---: | ---: | ---: |
| Small | 1 company, cap 5, details | 1 | 2 | about 2 s | under 50 MB |
| Medium | 1 board of 161 jobs, details | 161 | 163 | 40 s | 60 MB |
| Large | 2 boards, 1124 jobs, details | 1124 | 1137 | 235 s | 159 MB |
| Largest tested | 2 companies, cap 1000 each, details | 2000 | 2020 | 269 s | 179 MB |
| List only | 5 companies, cap 1000, no details | 3284 | 35 | 16 s | 88 MB |

With details on, the Actor makes one request per row plus one list request per 100 rows. Without details it is much faster; the price per row is the same.

### Use cases

| Buyer job | Input path | Output | Key fields |
| --- | --- | --- | --- |
| Watch the hiring of target employers | `companies`, `outputMode: "changes"`, `previousSnapshot` | New, updated and removed postings | `changeType`, `title`, `postingUrl` |
| Build a job list for a research or job board | `companies`, `maxJobsPerCompany` | All current postings | `title`, `fullLocation`, `department`, `applyUrl` |
| Prepare text for search or AI tools | `descriptionFormat: "text"` | Plain-text job sections | `jobDescriptionText`, `qualificationsText` |
| Check a known board is read completely | any run | Status per company | `companyStatus`, `OUTPUT`, `RUN_SUMMARY` |

### Scheduling and monitoring

The Actor does not schedule itself. To run it every day or week, open the Actor, choose **Schedules**, and create a schedule that starts it with your input. A schedule is plain Apify; see the [Apify schedules guide](https://docs.apify.com/platform/schedules).

To monitor changes, each run must receive the `SNAPSHOT.entries` of the run before. Two ways: copy it into the input by hand, or have your own code read the last run's `SNAPSHOT` record through the API and start the next run with it. The Actor does not store history for you.

What to expect: the first run has no `previousSnapshot`, so every row is `NEW` and `changeType` is `null`. Pass its `SNAPSHOT.entries` to the next run. If nothing changed on the board, every posting is `unchanged`. With `outputMode` set to `changes` the dataset is then empty, `RUN_SUMMARY.counters.changeCounts` shows for example `{"unchanged": 4}`, and the run costs only the start event.

### API, MCP and integrations

- **API:** start a run and read results with the [Apify API](https://docs.apify.com/api/v2). Keep your token in an environment variable, never in code.

```bash
curl -s -X POST "https://api.apify.com/v2/acts/81TefPThmCXMKxPXw/runs?waitForFinish=60" \
  -H "Authorization: Bearer $APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"companies":["smartrecruiters"],"maxJobsPerCompany":5}'
```

Read the rows with `GET https://api.apify.com/v2/datasets/{defaultDatasetId}/items?format=json` and the snapshot with `GET https://api.apify.com/v2/key-value-stores/{defaultKeyValueStoreId}/records/SNAPSHOT`.

- **Export:** on the Dataset tab choose JSON, CSV or Excel, and pick the fields. See the [dataset storage guide](https://docs.apify.com/platform/storage/dataset).
- **Webhooks and no-code tools:** use an Apify webhook or integration on run success, then read `RUN_SUMMARY.counters.changeCounts` or the `changeType` of rows.
- **MCP and AI agents:** we did not test an MCP setup for this Actor. If you use Apify's MCP server, check that it can see this Actor in your account.
- **Official source:** the data comes from the public [SmartRecruiters Posting API](https://developers.smartrecruiters.com/docs/posting-api).

### Limits and expected partial results

Tested on Apify with the Actor's own runs (build 0.1.1):

- Up to **1000 jobs per company** is proven. A board with 4814 postings was read up to the cap and marked `capped`.
- With details, **2000 rows in one run** took 269 s at 1024 MB. List-only runs returned 3284 rows in 16 s.
- Memory: choose 512 MB for up to about 1100 rows with details, and 1024 MB for about 2000. The Apify default for new runs can be higher; set memory yourself.
- Allowed by the input form but **not tested**: more than 2000 rows with details in one run, 25 companies in one run, and a `previousSnapshot` over 2000 entries. Use smaller batches first.
- The Actor reads about 4 detail requests per second. No rate limit was hit in tests.
- A detail that fails (for example, a posting that disappeared between the list and the detail read) keeps the row with list fields only, sets `detailStatus` and marks the company `partial`. Such a posting is never reported as removed.
- `removed` rows are made only for companies that ended `complete`. Capped, partial, error and empty companies list `removalsSuppressed` in `RUN_SUMMARY` instead.
- With `includeDetails: false`, `postingUrl` is built from the company ID and posting ID (warning `posting_url_derived`, no title slug). `applyUrl`, `referralUrl` and descriptions are `null`. Turn details on if you need them.
- If you set a maximum total charge and the run reaches it, the Actor stops, `RUN_SUMMARY.outcome` is `charge_limit`, the company that was cut and all unread companies are `partial` with reason `charge_limit` (`rowsEmitted` counts only stored rows), and `SNAPSHOT.incomplete` is `true`. Do not use that snapshot as `previousSnapshot`.
- The source can change while you read it. Job counts on live boards move.

### Troubleshooting

#### The run failed with `input_error`

Symptom: no rows, `RUN_SUMMARY.outcome` is `input_error`. Likely reason: a company item is not an ID or a SmartRecruiters board URL, or `outputMode` is `changes` without `previousSnapshot`. Check `RUN_SUMMARY.inputErrors`. Action: fix the named item and start again.

#### The dataset is empty but the run succeeded

Symptom: 0 rows. Likely reason: the company has no public postings, or the ID is wrong. The API answers the same way for both, so the status is `empty_or_unknown`, not "not found". Check the ID on the company career page.

#### Apply link and descriptions are `null`

Symptom: `applyUrl` and descriptions are `null`. Reason: `includeDetails` is `false`, or `descriptionFormat` excludes that form. Action: turn details on.

#### A company shows `partial` or `capped`

`capped` means your `maxJobsPerCompany` was reached. Raise it. `partial` means a page or detail failed, or the deadline came. Read `OUTPUT[].reasons`, raise `timeoutSecs` if needed, and run again. Do not trust `removed` rows for that company.

#### Rows show `updateComparable: false`

The old snapshot was made with different details settings. Run once more with the same settings, and feed that new snapshot next time.

### FAQ

#### Do I need a SmartRecruiters account or API key?

No. The Actor uses the public Posting API without login. It cannot read internal or private jobs.

#### How do I find a company ID?

Open the career page. The ID is the first part of the path after `jobs.smartrecruiters.com/`. It is not case sensitive; rows show the exact spelling from the source.

#### Can it search jobs by keyword or country across all companies?

No. You choose the companies. Filter the dataset afterwards by `fullLocation`, `locationCountry`, `department` or `typeOfEmployment`.

#### Is the result complete?

Check `companyStatus`. `complete` means the count matched the source at read time. Other values tell you why not. A posting can still be opened or closed right after the run.

#### How does change detection work?

The Actor hashes the normalized content of each posting and compares it with your `previousSnapshot` by company and posting ID. A new `fetchedAt` or `observedAt` is never a change. Title and release date changes are named in `changedFields`; other changes give `updated` with an empty list.

#### Does it send alerts?

No. Schedule runs and connect Apify integrations to the dataset or `RUN_SUMMARY`.

#### Can I use it in place of another SmartRecruiters monitor Actor?

For the main job fields, yes: it uses the same field names and values as a popular SmartRecruiters monitor Actor (`source`, `sourceId`, `postingId`, `title`, location, `postingUrl`, `applyUrl`, `detailUrl`, `referralUrl`, `language`, description HTML, `event`, `observedAt`, `scanComplete`). In a test on 5 postings all of those matched. Differences: empty fields are `null` or `""` here and are left out there; `event` values other than `NEW` were not observed on the other Actor; it has no `previousSnapshot` input. This Actor also adds fields such as plain-text descriptions and `contentHash`. Test with your own data before you switch.

### Related Actors

None yet.

### Support

For a bug, a question or a feature request, use the **Issues** tab on this Actor's Store page. Add the run ID, the input mode and non-sensitive error text. Never post tokens, cookies or private data. This is an independent tool and is not affiliated with SmartRecruiters. It reads only data that the public Posting API returns.

# Actor input Schema

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

SmartRecruiters company identifiers (e.g. McDonaldsCorporation) or board URLs such as https://jobs.smartrecruiters.com/McDonaldsCorporation. Identifiers are case-insensitive; duplicates are merged.

## `maxJobsPerCompany` (type: `integer`):

Stop reading a company after this many postings. A company stopped by this cap is reported as capped and never produces removals.

## `includeDetails` (type: `boolean`):

Fetch the detail record of every posting (descriptions, apply URL, compensation). One extra request per posting; off = list fields only.

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

Which description representation to output when details are included.

## `previousSnapshot` (type: `array`):

The SNAPSHOT.entries array saved by an earlier run. Enables changeType (new/updated/unchanged) and removed rows. Removals are only reported for companies read completely in this run.

## `outputMode` (type: `string`):

all = every current posting; changes = only new, updated and removed postings (requires previousSnapshot).

## `timeoutSecs` (type: `integer`):

Stop gracefully after this time; unfinished companies are reported as partial.

## Actor input object example

```json
{
  "companies": [
    "smartrecruiters"
  ],
  "maxJobsPerCompany": 10,
  "includeDetails": true,
  "descriptionFormat": "both",
  "outputMode": "all",
  "timeoutSecs": 900
}
```

# Actor output Schema

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

No description

## `output` (type: `string`):

No description

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

No description

## `snapshot` (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 = {
    "companies": [
        "smartrecruiters"
    ],
    "maxJobsPerCompany": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("cliqtomedia/smartrecruiters-jobs-monitor-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 = {
    "companies": ["smartrecruiters"],
    "maxJobsPerCompany": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("cliqtomedia/smartrecruiters-jobs-monitor-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 '{
  "companies": [
    "smartrecruiters"
  ],
  "maxJobsPerCompany": 10
}' |
apify call cliqtomedia/smartrecruiters-jobs-monitor-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,cliqtomedia/smartrecruiters-jobs-monitor-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/81TefPThmCXMKxPXw/builds/QxLaFZdzvh8knI0hP/openapi.json
