# InfoJobs Spain Job Scraper | All Filters, Salaries & Alerts (`corvuslab/infojobs-scraper`) Actor

Scrape InfoJobs.net jobs across Spain with real dropdown filters: 52 provinces, category, contract, working day, remote work, education, posting date. Get salary band, company, city and full description, plus an incremental change feed, job-count facets and alerts.

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

## Pricing

from $1.49 / 1,000 results

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

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 InfoJobs Spain Job Scraper do?

InfoJobs Spain Job Scraper pulls job ads from **[InfoJobs.net](https://www.infojobs.net/)**, Spain's largest job board, using the same filters you'd use on the site: **all 52 Spanish provinces**, job category, contract type, working day, remote work, minimum education and posting date, all as dropdowns. Every job comes back as a clean, flat record with **25 fields**, including a parsed **salary band**, contract, working day, remote-work mode, company and the full description. It's no-code, and the output goes to JSON, CSV, Excel, the API or an AI agent.

Type a keyword (or several, comma-separated), pick filters, and click Start. You can also paste any InfoJobs search URL, or leave the input empty to get the newest jobs across Spain. Every run also saves a **SEARCH_FACETS** record with InfoJobs' own job counts per province, category, contract and more: a snapshot of the Spanish job market from a single run.

> New to Apify? Sign up free and use the included monthly platform credit to try this Actor. No credit card needed.

**Why this scraper**

- 🎚️ **Every InfoJobs filter as a dropdown**, with no search URL to build by hand.
- 💶 **Salary as numbers you can sort and average**, not a text blob.
- ♻️ **A change feed for scheduled runs**, so you pay only for jobs that are new or changed.
- 📊 **Job-count facets for free** with every search.

***

### What data can you extract from InfoJobs Spain?

Every job record carries **25 fields**, and incremental mode adds **8 change-tracking fields** on top:

| Group | What you get |
|---|---|
| **Job** | `id`, `title`, `url`, `description` (full text), `publishedAt`, `isExecutive` |
| **Salary** | `salaryMin`, `salaryMax`, `salaryCurrency`, `salaryPeriod` (month / year / hour), `salaryType` (gross / net), plus a readable `salaryText` |
| **Conditions & location** | `contractType`, `workday`, `remoteWork`, `city` |
| **Company** | `company`, `companyUrl` (the employer's InfoJobs page), `companyLogo` |
| **Search context** | `searchPosition`, `isPromoted` (sponsored placement), `searchKeyword`, `source`, `scrapedAt`, `contentHash` |
| **Change tracking** (incremental mode) | `changeType`, `firstSeenAt`, `lastSeenAt`, `previousSeenAt`, `expiredAt`, `isRepost`, `repostOfId`, `repostDetectedAt` |

***

### Key features

- 🎚️ **Real InfoJobs filters**: 52 provinces, 21 categories, 8 contract types, 10 working-day options, 4 remote-work modes, 19 education levels, 4 posting-date windows and sort order. InfoJobs applies them itself, so you never pay for rows you filtered out. Most InfoJobs scrapers make you build a search URL in your browser and paste it in. This one doesn't.
- 🔎 **Multi-keyword search, balanced and de-duplicated**: `"enfermera, auxiliar de enfermería, celador"` runs three searches, splits **Max results** evenly between them and merges the results. A job that matches twice is returned once.
- 📋 **Search URLs still work**: paste any InfoJobs search or `/ofertas-trabajo/...` listing URL and its filters are kept as they are.
- 💶 **Structured salary**: typed min / max / currency / period / gross-net, plus a readable `salaryText` (`€1,200 – €1,500 per month (gross)`, `From €25,000 per year (gross)`). `onlyWithSalary` keeps only jobs that publish one.
- 📊 **SEARCH_FACETS labour-market record**: InfoJobs' own job counts per province, city, category, contract, working day, remote work and education, covering every matching job and not only the ones you downloaded.
- ♻️ **Full incremental change feed**: NEW, UPDATED and REAPPEARED jobs (optionally UNCHANGED and EXPIRED), with **repost detection** that flags a job re-listed under a new ID. Unchanged jobs are skipped and cost nothing.
- 🏷️ **Promoted & position flags**: `isPromoted` and `searchPosition` show which employers pay for visibility and where each ad ranks.
- 🔔 **Alerts**: Telegram, Slack, Discord or any webhook (n8n / Make / Zapier).
- 🤖 **AI-ready**: compact mode, a description length cap and drop-empty output keep payloads small for LLMs and MCP.

***

### 🚀 How to scrape InfoJobs Spain

1. Open the Actor and type **search keywords** (e.g. `enfermera`), or leave it empty for every job.
2. Pick **filters** from the dropdowns, e.g. provinces and contract type.
3. Set **Max results** (0 = every matching job).
4. (Optional) Turn on **incremental mode** and a **notification** channel, then **schedule** it.
5. Click **Start**. Results stream into the Output tab, ready to download as **JSON, CSV or Excel** or to pull from the **API**.

#### Quick-start example input

```json
{
  "query": "enfermera",
  "provinces": ["33", "9"],
  "contractTypes": ["1"],
  "postedWithin": "_7_DAYS",
  "maxResults": 200
}
```

This returns up to 200 permanent-contract (*indefinido*) nursing jobs in Madrid and Barcelona posted in the last 7 days, newest first, each with its salary band, working day and full description.

***

### Input

Configure it in the visual editor, or pass JSON through the API. There are **30 inputs**, grouped as the editor shows them. Through the API, filters take the InfoJobs option IDs shown in the editor's dropdowns (e.g. Madrid = `33`, Barcelona = `9`).

#### 🔎 Search

| Field | Type | What it does |
|---|---|---|
| `query` | string | Job title, skill or company, e.g. `enfermera` or `desarrollador python`. Separate several with commas. Each runs on its own and results are merged and de-duplicated. Leave empty for all jobs. |
| `startUrls` | array | InfoJobs search or listing URLs. A pasted URL keeps its own filters; the dropdown filters below apply to keyword searches. |
| `maxResults` | integer | Max jobs for the **whole run** (default 25), split evenly across your keywords and URLs. `0` = every matching job. |

#### 🎚️ Filters

InfoJobs applies these itself. Several values inside one filter widen the search (OR), and different filters narrow it (AND).

| Field | Type | What it does |
|---|---|---|
| `provinces` | array | Any of the **52 Spanish provinces**, including Ceuta, Melilla, Las Palmas and Santa Cruz de Tenerife. |
| `categories` | array | 21 InfoJobs categories, e.g. Comercial y ventas, Informática y telecomunicaciones, Sanidad y salud. |
| `contractTypes` | array | Permanent (indefinido), fixed-term, seasonal permanent (fijo discontinuo), part-time, training, relief, freelance (autónomo), other. |
| `workdays` | array | Full-time, any, or part-time / intensive, each with morning, afternoon or night variants (10 options). |
| `remoteWork` | array | On-site, hybrid, fully remote, not specified. |
| `education` | array | 19 minimum-education levels, from Sin estudios and ESO through FP, Grado and Licenciatura to Máster and Doctorado. |
| `postedWithin` | enum | `ANY`, `_24_HOURS`, `_7_DAYS`, `_15_DAYS`. |
| `sortBy` | enum | `PUBLICATION_DATE` (newest first, best for monitoring) or `RELEVANCE`. |
| `onlyWithSalary` | boolean | Keep only jobs that publish a salary. Skipped jobs are not charged. |

#### 📄 Output

| Field | Type | What it does |
|---|---|---|
| `compact` | boolean | Core fields only: id, title, company, city, salary text, contract, remote work, date, URL (plus `changeType` in incremental mode). |
| `descriptionMaxLength` | integer | Cut each description to N characters. Empty = full text. |
| `excludeEmptyFields` | boolean | Drop null and empty fields from each record. |

#### ♻️ Incremental monitoring

| Field | Type | What it does |
|---|---|---|
| `incrementalMode` | boolean | Return only what changed since the last run, tagged NEW / UPDATED / REAPPEARED. |
| `stateKey` | string | Stable name for the tracked search. Auto-derived from your search settings if blank. |
| `emitUnchanged` | boolean | Also emit jobs that haven't changed. |
| `emitExpired` | boolean | Also emit jobs that disappeared since the last run (needs newest-first sort and `maxResults: 0`). |

#### 🔔 Notifications

Credentials are secret inputs: encrypted at rest, masked in the UI and never written to the run log.

| Field | Type | What it does |
|---|---|---|
| `telegramToken` + `telegramChatId` | string | Telegram bot token (from @BotFather) + chat or channel ID. |
| `slackWebhookUrl` / `discordWebhookUrl` | string | Incoming webhook URL. |
| `webhookUrl` + `webhookHeaders` | string / object | POST the matched jobs as JSON to n8n, Make, Zapier or your own backend. |
| `notificationLimit` | integer | Jobs listed in each message (1–20, default 5). |

#### ⚙️ Advanced

| Field | Type | What it does |
|---|---|---|
| `proxyConfiguration` | object | Keep Apify Proxy on (the default). Without it, InfoJobs stops answering after a couple of hundred jobs. |
| `maxConcurrency` | integer | Result pages loaded in parallel (1–8, default 3). |
| `maxRequestRetries` | integer | Retries per failed request (0–10, default 3). |
| `ignoreUrlFailures` | boolean | Skip URLs that can't be interpreted instead of failing the run. |

#### More example inputs

Remote IT jobs anywhere in Spain that publish a salary:

```json
{
  "query": "desarrollador python, data engineer",
  "categories": ["150"],
  "remoteWork": ["2"],
  "onlyWithSalary": true,
  "maxResults": 0
}
```

Daily monitor for new hospitality jobs in the Balearic Islands, with a Telegram alert:

```json
{
  "query": "camarero",
  "provinces": ["26"],
  "sortBy": "PUBLICATION_DATE",
  "maxResults": 0,
  "incrementalMode": true,
  "emitExpired": true,
  "telegramToken": "<bot token>",
  "telegramChatId": "@yourchannel"
}
```

A pasted search URL, kept exactly as filtered on the site:

```json
{
  "startUrls": [{ "url": "https://www.infojobs.net/jobsearch/search-results/list.xhtml?keyword=camarero&provinceIds=33" }],
  "maxResults": 100
}
```

***

### Output

Each dataset item is one InfoJobs job ad. Here is a real record (description shortened):

```json
{
  "id": "985050dcd94e89959146062a6c2a77",
  "title": "CONDUCTOR (H/M) TRÁILER CISTERNA ADR NACIONAL + PORTUGAL Y FRANCIA",
  "company": "Transtal Jobs",
  "city": "Tarragona",
  "contractType": "Contrato indefinido",
  "workday": "Jornada completa",
  "remoteWork": "Presencial",
  "salaryMin": 2900,
  "salaryMax": 3200,
  "salaryCurrency": "EUR",
  "salaryPeriod": "month",
  "salaryType": "gross",
  "salaryText": "€2,900 – €3,200 per month (gross)",
  "publishedAt": "2026-10-05T08:52:07Z",
  "description": "Buscamos un par de conductores o conductoras de tráiler con experiencia en cisterna ADR para realizar ámbito nacional + Portugal y Francia… Se requiere un mínimo de 6 meses de experiencia habiendo llevado tráiler cisterna de combustible y ADR vigente básico y cisternas.",
  "url": "https://www.infojobs.net/tarragona/conductor-h-m-trailer-cisterna-adr-nacional-portugal-francia/of-i985050dcd94e89959146062a6c2a77",
  "companyUrl": "https://transtaljobs.ofertas-trabajo.infojobs.net",
  "companyLogo": "https://multimedia-logos.infojobs.net/image/upload/10/1051f1f2-4294-4647-9ab4-56aed9bfae8a",
  "isPromoted": false,
  "isExecutive": false,
  "searchPosition": 2,
  "searchKeyword": "conductor",
  "source": "infojobs.net",
  "scrapedAt": "2026-10-13T09:39:19.926359+00:00",
  "contentHash": "88b512c20b9aac976fe210e86c1d15aa0646c888"
}
```

#### Data fields

All 25 fields above are present on every record, with `null` where InfoJobs has no value (most often the salary, logo and remote-work mode). Incremental mode adds the change-tracking fields. An EXPIRED record carries only the id, title, URL and tracking dates, since the ad is gone. **Compact mode** returns the core fields only, and **`excludeEmptyFields`** drops empty values entirely.

The Output tab ships two ready-made views: **Overview** (title, company, city, salary, contract, working day, remote work, published, URL) and **Changes (incremental)** (change type, job, salary, published, first seen).

#### The SEARCH_FACETS record

Next to the dataset, every run writes a **SEARCH_FACETS** record to its key-value store (linked from the Output tab as *Search facets*). For each search you get the total job count and InfoJobs' breakdown. Here is a real, trimmed extract for `conductor` in Tarragona:

```json
{
  "conductor": {
    "totalResults": 27,
    "facets": {
      "contractTypes": [
        { "value": "1", "label": "Indefinido", "count": 24 },
        { "value": "9", "label": "Fijo discontinuo", "count": 2 },
        { "value": "10", "label": "Autónomo", "count": 1 }
      ],
      "workdays": [
        { "value": "1", "label": "Completa", "count": 22 },
        { "value": "5", "label": "Parcial - Indiferente", "count": 2 }
      ],
      "cities": [
        { "value": "Tarragona", "label": "Tarragona", "count": 10 },
        { "value": "Reus", "label": "Reus", "count": 4 }
      ]
    }
  }
}
```

The same record also holds `provinces`, `categories`, `remoteWork`, `education` and `countries`. For a filter you've applied, the facet still lists every value, so a search narrowed to Tarragona also shows how many matching jobs Madrid, Barcelona and every other province have.

***

### ♻️ How to monitor InfoJobs with incremental mode

Schedule the Actor with **incremental mode** on. Each run compares the search against the previous one and emits only **NEW / UPDATED / REAPPEARED** jobs, plus **EXPIRED** ones when you ask for them. A job taken down and re-listed under a new ID is flagged as a **repost**, so it doesn't come back as a fresh lead.

| | First run (baseline) | Next run (nothing changed) |
|---|---|---|
| Jobs found on InfoJobs | all | all |
| Records emitted & billed | all | **none** |
| Alerts sent | all | **none** |

After the baseline, what you pay depends on how much InfoJobs changes, not on how many jobs you watch. With 5 % daily churn on 1,000 tracked jobs, you're billed for about 50 records a day instead of 1,000.

1. Configure your keywords and filters, and keep **Sort by: newest first**.
2. Turn on **Incremental mode**. Set `maxResults` to `0` if you want expired jobs reported too.
3. Run it once to seed the baseline.
4. Open the **Schedules** tab and set a recurring run (e.g. hourly or daily).

***

### 🔔 How to set up InfoJobs alerts

Fill in only the channels you want in the **Notifications** input section (Telegram, Slack, Discord or any webhook; see the table above). A webhook receives the jobs as structured JSON, ready for n8n, Make or Zapier. Each channel fires independently, so a broken channel can't stop the scrape or the others.

Combine it with **incremental mode** and each alert lists only genuinely new or changed jobs, with no repeats across runs. `notificationLimit` sets how many jobs each message shows.

***

### 💡 What can you do with InfoJobs data?

#### Spanish labour-market analytics

The SEARCH_FACETS record gives you job counts by province, category, contract type, working day, remote work and education for any search. Run it weekly and you have a time series of hiring demand across Spain without counting a single ad yourself.

#### Salary benchmarking in Spain

`salaryMin`, `salaryMax`, `salaryPeriod` and `salaryType` are typed values, so you can average pay by role, province or contract type right away. Turn on `onlyWithSalary` to work only with ads that publish a band.

#### Recruitment and staffing pipelines

Agencies and in-house recruiters can track every new ad in their niche, such as nurses in Madrid or developers working remotely, and get it in Slack or Telegram within the hour.

#### Sales prospecting from hiring signals

A company hiring is a company spending. Group ads by `company` and `companyUrl` to find employers that are scaling up in your region or sector, and use `isPromoted` to spot the ones paying for visibility.

#### Remote-work and contract trends

Track how the share of hybrid and fully remote jobs, or of *indefinido* versus fixed-term contracts, moves over time by sector and province, for HR research, policy work and journalism.

#### Job boards, newsletters and AI pipelines

Feed fresh Spanish job ads into your own job site, newsletter, LLM context or vector store, with clean fields, the full description and the original InfoJobs link.

***

### 💰 How much does it cost to scrape InfoJobs Spain?

This Actor uses Apify's **pay-per-event** model: a small fee when a run starts, plus a fee for each job record. The Actor's **Pricing** tab shows the current numbers.

Records are built straight from InfoJobs search results, more than 20 jobs per page, with no extra page load per job. Three things cut your bill further:

- **The site's own filters**: jobs you filtered out are never fetched and never billed.
- **`onlyWithSalary`**: jobs without a published salary are skipped and not charged.
- **Incremental mode**: after the baseline you pay only for what changed.

You can also set a maximum cost per run in the run options. The Actor stops cleanly when it reaches it.

***

### 🔌 Integrations & export

Export to **JSON, CSV, Excel** or an HTML table, or pull from the **REST API** and the **JavaScript / Python** clients. It runs on a **schedule**, connects to **Google Sheets, Slack, Make, Zapier and n8n**, and works as an **MCP tool** for AI agents.

The same publisher also covers Spanish-language job markets in Latin America: **Computrabajo Job Scraper** (19 LATAM countries), **OCC Mundial Jobs Scraper** (Mexico) and **elempleo Job Scraper** (Colombia).

***

### 🔗 Using the API

Run this Actor from your own code. Example with the Apify Python client:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_API_TOKEN>")

run_input = {
    "query": "enfermera",
    "provinces": ["33"],
    "remoteWork": ["1", "3"],
    "maxResults": 100,
}

run = client.actor("corvuslab/infojobs-scraper").call(run_input=run_input)

for job in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(job["title"], job["company"], job["salaryText"])

facets = client.key_value_store(run["defaultKeyValueStoreId"]).get_record("SEARCH_FACETS")
print(facets["value"])
```

***

### ❓ FAQ

**Does it cover InfoJobs Brazil (infojobs.com.br)?** No. This Actor covers **InfoJobs Spain (infojobs.net)** only. InfoJobs Brazil is a separate site with different data.

**Do I need an InfoJobs account or login?** No. It reads only public job ads, so it needs no account, cookies or setup.

**What happens if I leave the input empty?** You get the 25 newest jobs across all of Spain. Raise `maxResults`, or set it to `0`, to take more.

**How many jobs can I get?** Every job that matches your search. With `maxResults: 0` the Actor pages through the full result set, up to 20,000 jobs per run. For bigger pulls, split the search by province or category.

**How is `maxResults` split when I enter several keywords?** It's one limit for the whole run, so you never get or pay for more jobs than you set. It's shared evenly: `"enfermera, celador, auxiliar"` with `maxResults: 30` returns about 10 jobs per keyword. If one keyword has fewer jobs than its share, the rest goes to the others, so you still get 30 when they have enough. A job that matches two keywords is returned and counted once.

**Do the dropdown filters apply to a pasted search URL?** No. A pasted URL keeps exactly the filters it was built with, so set them on InfoJobs before copying it. `onlyWithSalary` and the output options still apply.

**Why is the salary empty on some jobs, and what does "From €25,000" mean?** Many InfoJobs employers don't publish a salary. Their `salaryMin`, `salaryMax` and `salaryText` are `null`, and `onlyWithSalary` drops them without charge. Some publish only a minimum: `salaryMax` is then `null` and `salaryText` reads "From …". `salaryPeriod` and `salaryType` tell you whether it is monthly, yearly or hourly, and gross or net.

**Do records include applicant counts, vacancies, required skills or languages?** No. Each record is built from the InfoJobs search results, which carry the title, full description, salary band, contract, working day, remote work, city, company, date and promotion flag. Requirements written into the ad still appear in the `description`.

**Can I get employer emails or phone numbers?** No. InfoJobs handles applications on its own platform, so job ads don't carry contact details. You do get the company name, its InfoJobs company page and logo.

**Why are fields like `contractType` and `workday` in Spanish?** They are InfoJobs' own labels (e.g. *Contrato indefinido*, *Jornada completa*, *Presencial*), kept exactly as published. Descriptions are in the employer's original language, usually Spanish, sometimes Catalan or English.

***

### ⚖️ Is it legal to scrape InfoJobs?

This Actor collects only **publicly available** job ads that InfoJobs shows to any visitor without logging in. You're responsible for how you use the data, in particular any personal information, and for complying with InfoJobs' terms and applicable law, including the EU GDPR and Spain's data-protection law (LOPDGDD). Not affiliated with, endorsed by or sponsored by InfoJobs or its owners.

***

**Keywords:** infojobs scraper · infojobs spain scraper · infojobs.net scraper · infojobs api · scrape infojobs jobs · spain job scraper · ofertas de empleo infojobs · spain jobs data · salary data spain · job market analysis spain · recruitment research · job listings spain · job monitoring · remote jobs spain · export to CSV · export to Excel · no-code scraper · MCP tool for AI agents

# Changelog

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

# Actor input Schema

## `query` (type: `string`):

Job title, skill or company, e.g. "enfermera" or "desarrollador python". Separate several searches with commas — each runs on its own and results are merged and de-duplicated. Leave empty for all jobs.

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

Paste any InfoJobs search or listing URL with your filters already applied (e.g. https://www.infojobs.net/jobsearch/search-results/list.xhtml?keyword=camarero\&provinceIds=33). Filters inside the URL are kept as they are; the filter dropdowns below apply to keyword searches only.

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

Maximum number of jobs for the whole run. With several keywords or URLs the limit is split evenly — 3 keywords and 30 results give about 10 each — and a search that runs out of jobs passes its unused share to the others. Set 0 for no limit (every matching job).

## `provinces` (type: `array`):

Only jobs in these Spanish provinces. Pick several to widen the search.

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

Only jobs in these InfoJobs categories.

## `contractTypes` (type: `array`):

Only jobs with these contract types.

## `workdays` (type: `array`):

Full-time, part-time or intensive schedules.

## `remoteWork` (type: `array`):

On-site, hybrid or fully remote jobs.

## `education` (type: `array`):

Only jobs asking for these education levels.

## `postedWithin` (type: `string`):

Only jobs published in this time window.

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

Newest first is best for monitoring and is required to report expired jobs. Relevance ranks keyword matches first.

## `onlyWithSalary` (type: `boolean`):

Skip jobs that don't publish a salary. Skipped jobs are not charged.

## `compact` (type: `boolean`):

Emit only the core fields (title, company, city, salary, contract, remote work, date, URL). Ideal for AI agents and MCP clients.

## `descriptionMaxLength` (type: `integer`):

Cut each job description to this many characters. Leave empty for the full text.

## `excludeEmptyFields` (type: `boolean`):

Remove null and empty fields from each record.

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

Track jobs between runs and return only what changed. Every job is tagged NEW / UPDATED / REAPPEARED (or UNCHANGED / EXPIRED when enabled below). Unchanged jobs are skipped and cost nothing — the cheap way to poll a search on a schedule.

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

Stable name for the tracked search. Leave empty to derive one automatically from your search settings.

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

Also emit records that have not changed since the previous run.

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

Also emit jobs that were present last run but are gone now (needs Sort by: newest first and Max results 0).

## `telegramToken` (type: `string`):

Bot token from @BotFather.

## `telegramChatId` (type: `string`):

Chat or channel ID, e.g. "-100123456789" or "@yourchannel".

## `slackWebhookUrl` (type: `string`):

Slack incoming-webhook URL.

## `discordWebhookUrl` (type: `string`):

Discord incoming-webhook URL.

## `webhookUrl` (type: `string`):

Any HTTPS endpoint. Receives a JSON POST with the matched records — works with n8n, Make and Zapier.

## `webhookHeaders` (type: `object`):

Extra headers for the webhook request, e.g. {"Authorization": "Bearer xyz"}.

## `notificationLimit` (type: `integer`):

How many records to include in each notification message.

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

Keep Apify Proxy on (the default). Without it, InfoJobs stops answering after a couple of hundred jobs.

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

How many result pages to load at once. Higher is faster.

## `maxRequestRetries` (type: `integer`):

How many times to retry a failed request before giving up on it.

## `ignoreUrlFailures` (type: `boolean`):

Skip URLs that cannot be interpreted instead of failing the whole run.

## Actor input object example

```json
{
  "query": "enfermera",
  "maxResults": 25,
  "postedWithin": "ANY",
  "sortBy": "PUBLICATION_DATE",
  "onlyWithSalary": false,
  "compact": false,
  "excludeEmptyFields": false,
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "notificationLimit": 5,
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "maxConcurrency": 3,
  "maxRequestRetries": 3,
  "ignoreUrlFailures": true
}
```

# Actor output Schema

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

No description

## `allItems` (type: `string`):

No description

## `searchFacets` (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 = {
    "query": "enfermera",
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("corvuslab/infojobs-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 = {
    "query": "enfermera",
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("corvuslab/infojobs-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 '{
  "query": "enfermera",
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call corvuslab/infojobs-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,corvuslab/infojobs-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/d8Cyo1bDNWBx6xeFp/builds/6xVVeemzAhKBS6Ds4/openapi.json
