# InfoJobs Scraper - Spain Jobs, Salaries & Company Data API (`parseforge/infojobs-scraper`) Actor

Scrape public InfoJobs job offers across all 52 Spanish provinces. Full job descriptions, structured salary ranges, contract type, working hours and remote-work flag. No API key.

- **URL**: https://apify.com/parseforge/infojobs-scraper.md
- **Developed by:** [ParseForge](https://apify.com/parseforge) (community)
- **Categories:** Jobs, Lead generation, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $5.33 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

[![ParseForge](https://raw.githubusercontent.com/ParseForge/apify-assets/main/banner.jpg)](https://apify.com/parseforge?fpr=vmoqkp)

### InfoJobs Scraper - Spain Jobs, Salaries & Company Data API

Scrape job offers from InfoJobs.net, Spain's largest job board, across all 52 provinces and 21 sectors. Every offer comes with the **complete job description** and a **structured salary range** — minimum, maximum, period and currency as separate numbers, not a string to parse. No login or API key. Export to CSV, JSON, Excel, or XML.

InfoJobs has no public API. This Actor reads the same data the website's own front end loads, which means the full description arrives with the listing — no second request per offer, and nothing that breaks when InfoJobs restyles a page. Turn on `includeDetails` and it also opens each offer page for the fields the listing never carries: **how many people have applied**, the required skills, the minimum experience and studies, and the normalised job title.

| Who uses it | What they scrape InfoJobs for |
| --- | --- |
| Recruiters | Which roles a Spanish province is hiring for right now, and who is posting them |
| Compensation analysts | Published pay ranges in EUR, split into gross yearly and monthly |
| Talent-intelligence teams | Salary benchmarks by sector, contract type and education level |
| Market researchers | Which companies are expanding into which Spanish regions |
| Remote-work platforms | Offers flagged fully remote, hybrid or on-site |

### What it does

This Actor collects InfoJobs offers by keyword, province, sector and ten other filters, and returns each one as a flat row. Every offer carries:

- 🧾 **Core fields**: title, company, city, contract type, working hours, and canonical offer URL.
- 📄 **The full job description** — the entire posting text, not a truncated card summary.
- 💰 **Structured salary**: `salaryMin`, `salaryMax`, `salaryPeriod`, `salaryCurrency` and `salaryType` as separate fields.
- 📅 **Exact dates**: `publishedAt` as a real ISO 8601 timestamp, straight from InfoJobs.
- 🚩 **Flags**: fully remote, promoted listing, executive role, and whether a salary is published at all.
- 🏢 **Employer**: company name, its InfoJobs profile URL, and its logo.
- 👥 **Optional offer-page fields** (`includeDetails`): applicants, vacancies, required skills, minimum experience and studies, seniority, industry, normalised job title and company rating.
- 🔎 **Several searches in one run**: pass a list of keywords or of search URLs and get one deduplicated dataset.

Results export to CSV, JSON, Excel, or XML, or stream from the API.

### What you can do with InfoJobs data

💰 **Benchmark salaries across Spain.**

Run the same job title in Madrid, Barcelona and Valencia, then compare `salaryMin` and `salaryMax`. Filter on `hasSalary` to drop the offers that publish no range, and always group by `salaryPeriod` — InfoJobs carries `YEAR`, `MONTH` and `HOUR` in the same field.

📈 **Track hiring demand by province.**

Scrape a sector on a schedule with `sortBy: PUBLICATION_DATE` and watch volume and freshness move week over week.

🏠 **Map the remote-work market.**

`teleworking` separates on-site, hybrid and fully remote. `isRemote` is true only for fully remote roles, so hybrid never inflates your remote counts.

🎯 **Find companies that are actively hiring.**

Group by `companyName` and sort by count. `companyUrl` links straight to the employer's InfoJobs profile.

👥 **Measure how contested a job is.**

With `includeDetails`, `applicants` tells you how many candidates already applied and `vacancies` how many seats there are. Sort ascending to find the openings nobody has found yet.

🧠 **Build a skills map.**

`skills` carries InfoJobs' own normalised skill list per offer, and `normalizedTitle` groups synonyms of the same role — the two fields that make sector-wide aggregation possible without string matching.

### Why choose this scraper

| What you get | |
| --- | --- |
| **Salary already split** | `salaryMin`, `salaryMax`, `salaryPeriod`, `salaryCurrency` and `salaryType` as separate fields. No string parsing, nothing to break when a symbol moves. |
| **The description included** | The complete posting text ships with the listing page, so 100 full offers cost 100 rows — not 100 rows plus 100 detail requests. |
| **Honest salary coverage** | `hasSalary` marks every row, and the measured coverage by search type is published below rather than hidden. |
| **Filters by name, not ID** | `Madrid`, `Indefinido`, `Solo teletrabajo` — InfoJobs' own labels. An unknown value fails with the valid list instead of silently returning everything. |
| **Strict remote flag** | `isRemote` is true only for `Solo teletrabajo`. Hybrid roles stay false, so remote counts stay honest. |
| **Fields the listing hides** | `includeDetails` opens each offer page for applicants, vacancies, skills, experience, studies, seniority and the normalised title. |
| **Salary filter InfoJobs lacks** | The site has no minimum-salary filter — `salaryMin` returns HTTP 500. `minSalary` and `maxSalary` work here because the range is already structured, and monthly ranges are annualised by 12. |
| **City-level search** | `city` narrows below the province, using InfoJobs' own city names. An unknown city fails with the list of cities that search really has, instead of returning zero rows in silence. |
| **Several searches, one dataset** | `keywords` or `searchUrls` fan out over many searches and deduplicate by offer ID. |
| **Four export formats** | CSV, JSON, Excel, and XML, from the dashboard or the API. |

### How it compares

Several InfoJobs scrapers exist and most return the same core fields. The difference is in the salary: the others publish "salary" as one value; this one splits it into five typed fields and tells you how often it is actually there.

The competitor columns below reflect what each Actor's own public listing states, not a test run of it. Prices and start fees were read from the Apify Store API on 19 August 2026; the start-fee row multiplies each Actor's published fee by its own default memory, because Apify charges one start event per gigabyte.

| Feature | ParseForge | easyapi | alvaraaz | delectable\_incubator | crawlerbros |
| --- | --- | --- | --- | --- | --- |
| Salary split into min / max / period / currency / type | Yes, five fields | Not stated | One "salaries" field | One "salary" field | One "salaries" field |
| `hasSalary` flag + published coverage figures | Yes | No | No | No | No |
| Full job description | Yes | Yes | Yes | Not stated | Yes |
| Filters validated against InfoJobs' own vocabulary | Yes, 8 filters | Not stated | Not stated | Not stated | Not stated |
| Fully-remote flag distinct from hybrid | Yes | Not stated | Not stated | "work mode" | Not stated |
| Applicants per offer | Yes, `applicants` | No | No | No | No |
| Normalised job title and skill list | Yes | No | No | No | No |
| Filter by minimum salary | Yes | No | No | No | No |
| City-level filter | Yes | No | No | No | No |
| Price per result | $0.00338 | $0.00299 | $0.0025 | $0.00198 | $0.002 |
| Run-start fee at the Actor's own default memory | **$0.054** | **$0.18** | none | $0.0002 | $0.02 |

### What an offer looks like

Every offer returns as one flat JSON row. Here is a real record, unedited apart from a trimmed description:

```json
{
  "offerId": "2b375a996045f9aa71b5f1b3f09e72",
  "title": "Desarrollador LabVIEW",
  "url": "https://www.infojobs.net/majadahonda/desarrollador-labview/of-i2b375a996045f9aa71b5f1b3f09e72",
  "city": "Majadahonda",
  "description": "DESARROLLADOR/A LabVIEW\nSomos ingenieros unidos por una vocación dedicada al diseño de control y automatización de sistemas...",
  "companyName": "Murmann LabVIEW Consultants",
  "companyUrl": "https://www.infojobs.net/murmann-labview-consultants/em-i98565551545250771171142016164387613158",
  "companyLogo": "https://multimedia-logos.infojobs.net/image/upload/29/29846812-709a-482e-949f-e84a7b662cac",
  "contractType": "Contrato indefinido",
  "workday": "Jornada completa",
  "teleworking": "Híbrido",
  "isRemote": false,
  "isPromoted": true,
  "isExecutive": false,
  "hasSalary": true,
  "salaryMin": 25000,
  "salaryMax": 33000,
  "salaryPeriod": "YEAR",
  "salaryCurrency": "EUR",
  "salaryType": "GROSS",
  "publishedAt": "2026-08-14T15:54:02Z",
  "searchUrl": "https://www.infojobs.net/jobsearch/search-results/list.xhtml?keyword=desarrollador&provinceIds=33",
  "scrapedAt": "2026-08-19T13:47:49.677Z",

  "hasDetails": true,
  "applicants": 45,
  "vacancies": 1,
  "minimumExperience": "Al menos 1 año",
  "minimumStudies": "Diplomatura",
  "seniority": "Empleado/a",
  "industry": "Atención sanitaria y hospitalaria",
  "subcategory": "Enfermería",
  "normalizedTitle": "Enfermero/Enfermera",
  "staffInCharge": 0,
  "skills": ["Enfermería", "Emergencias", "Soporte vital avanzado"],
  "companyRating": 3.43,
  "companyRatingCount": 14
}
```

The block after `scrapedAt` only appears when `includeDetails` is on. `hasDetails` marks every row, so a missing field means InfoJobs did not publish it — not that the page went unread.

Offers without a published range carry `hasSalary: false` and no salary fields at all — they are not filled with zeros.

`salaryPeriod` is `YEAR`, `MONTH` or `HOUR`, exactly as the employer set it. It is passed through unchanged rather than normalised, so an obviously wrong one stays visible instead of being silently converted.

### How much salary data you actually get

Salary coverage depends on how you search, so here are figures measured on real runs rather than a marketing number:

| Search | Offers with a published range |
| --- | --- |
| Keyword search (`desarrollador`) | ~58–62% |
| Keyword plus province (`desarrollador`, Madrid) | ~56–58% |
| No keyword, everything open | ~32% |

**Searching by keyword roughly doubles salary coverage** against browsing everything, because employers who write a targeted posting are the ones who also publish a range. Filter on `hasSalary` to keep only the offers that pay openly. Every run prints its own coverage in the log.

### What the offer page adds

`includeDetails` opens the page of every offer it returns. That is one extra request per offer, so it is off by default and billed as a separate event. Coverage below was measured over 121 offers across seven searches in different sectors on 19 August 2026, and what is published is the spread between the best and the worst of those searches, not the best one:

| Field | Coverage | What it is |
| --- | --- | --- |
| `applicants` | 100% | How many candidates already applied |
| `vacancies` | 100% | Seats the offer covers |
| `minimumExperience` | 100% | `Al menos 1 año` |
| `minimumStudies` | 100% | `Diplomatura` |
| `seniority` | 100% | `Empleado/a` |
| `subcategory` | 100% | InfoJobs' subcategory |
| `normalizedTitle` | 95–100% | InfoJobs' normalised role name, which groups synonyms |
| `industry` | 76–100% | Employer's industry, finer than the sector filter |
| `staffInCharge` | 71–100% | People reporting to the role |
| `skills` | 67–95% | Normalised skill list |
| `companyRating` / `companyRatingCount` | 15–70% | Employee rating of the company |
| `languages` | 5–40% | Required languages with level |
| `benefits` | 5–36% | Social benefits |
| `canApply` | 100% | Whether InfoJobs still accepts applications for the offer |
| `minimumRequirements` | 0–100% | The advertiser's stated must-haves, in their words |
| `industryId` | 0–100% | Numeric id behind `industry`, for joining against your own tables |
| `department` | 0–38% | Hiring department |
| `residentIn` | 0–13% | Whether residency in the area is required |
| `referenceCode` | 0–13% | The advertiser's own reference, for matching against their ATS |
| `desiredRequirements` | 0–13% | Nice-to-have requirements |
| `workingHours` | 0–13% | Concrete hours, beyond the `workday` category |

The last four vary by sector rather than by luck: hospitality offers published a company rating four times as often as accounting ones. `hasDetails` marks every row that was read, so an empty field there is a field InfoJobs does not publish for that offer.

The eight rows from `canApply` down were measured separately, over 40 offers across five searches on the same day, so their spread rests on a smaller sample than the rows above it. `minimumRequirements` is the one worth knowing about: it carries the advertiser's own must-haves and reaches every offer in some sectors while staying empty in others.

One field is deliberately left out. `contractDuration` exists on InfoJobs' offer pages but came back empty on all 53 offers inspected, so it is not published rather than shipped as an always-null column.

**Offer pages multiply requests by 20.** A run of 1,000 offers is 50 search pages without them and 1,050 requests with them. InfoJobs rate-limits by IP: while testing this Actor, a single unproxied IP started returning HTTP 403 after roughly 250 requests in half an hour, and the block cleared **3.8 minutes** later. That is why the retries rotate the proxy session instead of waiting the block out — each attempt asks Apify Proxy for a different IP, which is the only remedy that works inside a run. Without a proxy the Actor stops with a clear error instead of writing half a dataset.

Coverage moves a lot with the search. Only the first eight fields are close enough to always-there to plan around; treat the rest as bonus and check them before building on one.

### Configure the run

Drive the Actor with a keyword, twelve filters, or a raw InfoJobs search URL. The Input tab lists every parameter.

Search by keyword, newest first:

```json
{ "keyword": "desarrollador", "sortBy": "PUBLICATION_DATE", "maxItems": 100 }
```

Narrow to a province and a contract type:

```json
{ "keyword": "enfermero", "province": "Barcelona", "contractType": "Indefinido", "maxItems": 200 }
```

Every fully remote offer in a sector, published this week:

```json
{ "category": "Informática y telecomunicaciones", "teleworking": "Solo teletrabajo", "sinceDate": "_7_DAYS", "maxItems": 500 }
```

Skip the descriptions to keep the dataset small:

```json
{ "keyword": "camarero", "province": "Madrid", "includeDescription": false, "maxItems": 300 }
```

Several roles at once, deduplicated into one dataset:

```json
{ "keywords": ["enfermero", "fisioterapeuta", "logopeda"], "province": "Madrid", "maxItems": 500 }
```

One city, only offers that pay above a threshold:

```json
{ "keyword": "desarrollador", "city": "Barcelona", "minSalary": 35000, "maxItems": 200 }
```

Open every offer page for applicants, skills and experience:

```json
{ "keyword": "data engineer", "province": "Madrid", "includeDetails": true, "maxItems": 100 }
```

Or paste a search URL straight from your browser — it overrides every filter above:

```json
{ "searchUrl": "https://www.infojobs.net/jobsearch/search-results/list.xhtml?keyword=qa&provinceIds=33", "maxItems": 100 }
```

**Filters take names, not IDs.** `province`, `category`, `contractType`, `workday`, `teleworking` and `education` accept the same labels InfoJobs shows on its own filter panel — `Madrid`, `Indefinido`, `Jornada completa`, `Solo teletrabajo`. An unknown value fails immediately with the list of valid ones rather than silently returning the unfiltered search.

### Pricing

Pay-per-event, so you are charged for the work the run actually does — and every charge lands **after** the row is written, never before.

| Event | Price | Charged |
| --- | --- | --- |
| Actor start | $0.054 | Once per run |
| Search query | $0.009 | Once per keyword or search URL, and only once it has returned an offer |
| Result | $0.00338 per offer | Per row written to your dataset |
| Offer page details | $0.00451 per offer | Only with `includeDetails`, and only when the page was actually read |

Prices are the no-discount tier; Apify plan discounts take them down to $0.048, $0.00814, $0.003 and $0.004 on Gold. The event ids the API reports are `apify-actor-start`, `search-query`, `result-item` and `result-details`.

| Run | Cost |
| --- | --- |
| 100 offers, one search | $0.40 |
| 1,000 offers, one search | $3.44 |
| 1,000 offers with offer-page details | $7.95 |
| 10,000 offers across five keywords | $33.90 |

The full job description is included in the result price — no separate charge, and no second request. The start fee is charged once per gigabyte of Actor memory, and this Actor is capped at 1 GB, so **the start fee you see on this page is the one you pay**.

### Free users

Free-plan runs return up to 100 offers as a preview. [Upgrade your Apify plan](https://console.apify.com/sign-up?fpr=vmoqkp) to collect up to 50,000 offers per run and to get the volume discounts above.

New Apify accounts also come with $5 in credit, worth roughly 1,460 offers on a single search, or about 620 with offer-page details on.

**A paid run is bounded by its timeout.** Measured on the platform: 100 offers in 36 seconds without offer pages and 60 in 121 seconds with them, so the default 900-second timeout holds about **2,400 offers** listing-only and about **440 offers** with `includeDetails` on, because that turns every offer into its own request. Raise the timeout in the run options for a bigger job, or split it across scheduled runs.

### Run it

1. [Create a free Apify account with $5 in credit](https://console.apify.com/sign-up?fpr=vmoqkp).
2. Open the [InfoJobs Scraper](https://apify.com/parseforge/infojobs-scraper?fpr=vmoqkp).
3. Type a `keyword`, optionally pick a `province` or `city`, set `maxItems`, and click **Start**.
4. Export the results as CSV, Excel, JSON, or XML from the **Dataset** tab.

Run it programmatically through the [Apify API](https://docs.apify.com/api/v2) or the [ApifyClient](https://docs.apify.com/api/client/js) for JavaScript and Python.

### Use with AI agents (MCP)

Give an AI agent live access to InfoJobs through the Model Context Protocol. Add the Actor to Claude, Cursor, or any MCP client:

```bash
claude mcp add --transport http apify "https://mcp.apify.com?tools=parseforge/infojobs-scraper"
```

Then prompt it in plain language:

- *"Find developer jobs in Madrid posted this week that publish a salary."*
- *"Compare published salaries for nurses in Barcelona, Valencia and Sevilla."*
- *"Pull the 200 newest fully remote offers in Spain and rank the companies by how many applicants they already have."*

Copy this into ChatGPT, Claude, or Cursor to start:

```
Use the Apify Actor "parseforge/infojobs-scraper" to collect public InfoJobs.net job offers from Spain. Input: { "keyword": "<role>", "keywords": ["<role>", "<role>"], "province": "<Spanish province name, e.g. Madrid>", "city": "<InfoJobs city name, e.g. Barcelona>", "category": "<optional sector>", "contractType": "<optional>", "teleworking": "<Presencial|Híbrido|Solo teletrabajo|Sin especificar>", "segment": "<Executive>", "minSalary": <euros per year>, "maxSalary": <euros per year>, "sinceDate": "<ANY|_24_HOURS|_7_DAYS|_15_DAYS>", "sortBy": "<RELEVANCE|PUBLICATION_DATE>", "includeDetails": <true to open each offer page>, "maxItems": <n> }. It returns offerId, title, url, city, the full description, companyName, companyUrl, contractType, workday, teleworking, isRemote, hasSalary, salaryMin, salaryMax, salaryPeriod, salaryCurrency, salaryType and publishedAt per offer, plus applicants, vacancies, skills, minimumExperience, minimumStudies, seniority, industry, normalizedTitle and companyRating when includeDetails is true. Filters take InfoJobs' own Spanish labels, not numeric ids. Call it with the ApifyClient and my APIFY_TOKEN.
```

### Troubleshooting

**Why am I getting no results?**

The filter combination may have no matches. Drop `province` first, then `category`, and confirm the keyword alone returns offers. The run log prints the search URL — open it in a browser to see exactly what InfoJobs sees.

**Why did my province name get rejected?**

`province` takes InfoJobs' own spelling, including the bilingual ones — `Valencia/València`, `Alicante/Alacant`, `Vizcaya/Bizkaia`. The error message lists valid values. Only the 52 Spanish provinces are offered; for offers abroad, use `onlyForeignCountry` or paste a `searchUrl`.

**Why fewer offers than I asked for?**

`maxItems` is a ceiling, not a target. The search ran out — InfoJobs returns 20 offers per page, sometimes 22 when it inserts promoted ones, and a narrow search often holds only a handful of pages. Widen it, or drop a filter.

**Why is `salaryMin` empty?**

That offer publishes no salary. Roughly a third to two thirds do, depending on how you search — see the table above. Filter on `hasSalary` to keep only the ones that do.

**Why is `description` empty?**

`includeDescription` is off. Turn it back on; it costs no extra requests.

**Why is `applicants` — or any other offer-page field — empty?**

Check `hasDetails`. If it is `false`, `includeDetails` was off and the offer page was never opened. If it is `true`, the page was read and InfoJobs simply publishes nothing for that field on that offer — see the coverage table above.

**My city returned nothing.**

An unknown city gives InfoJobs zero results rather than an error, so the run fails on purpose with the list of cities that search actually holds. Use the site's own spelling — `Santa Cruz de Tenerife`, not `santa-cruz-de-tenerife` — though capitals and accents do not matter. `city` narrows inside whatever `province` you set, so a mismatched pair returns nothing.

**The salary filter dropped everything.**

`minSalary` and `maxSalary` can only match offers that publish a range, and roughly a third to two thirds do. Offers priced by the hour are excluded too, because an hourly rate cannot be turned into a yearly figure without inventing an assumption. The run log prints how many rows the filter dropped.

**Why is the run slow?**

Each page holds 20 offers, so a large `maxItems` fetches many pages, and `includeDetails` adds one request per offer on top. InfoJobs also rate-limits by IP, and a retried page adds backoff delay. Lower `maxItems`, turn `includeDetails` off, or split the job across scheduled runs.

**I got HTTP 403.**

That is InfoJobs' rate limiter, not a ban. It was measured clearing in under four minutes. Each retry inside the run asks Apify Proxy for a fresh IP, so leave the proxy on; if you are running without one, wait a few minutes and lower `maxItems` or turn `includeDetails` off.

**A field stopped filling.**

InfoJobs changed its front end. Email us with your run ID so we can update the parser.

### FAQ

| Question | Answer |
| --- | --- |
| Do I need an InfoJobs account or API key? | No. This reads public search pages, so there is nothing to register or authorize. |
| Which regions does it cover? | All 52 Spanish provinces across 21 sectors. Offers outside Spain are reachable with `onlyForeignCountry` or a pasted `searchUrl`. |
| Does it return the full job description? | Yes, the complete posting text, at no extra cost and with no second request. Turn it off with `includeDescription` if you want a smaller dataset. |
| Does it return salary data? | Yes, already split into `salaryMin`, `salaryMax`, `salaryPeriod`, `salaryCurrency` and `salaryType`. Coverage runs about 32% to 62% depending on the search. |
| Why do some salaries look small? | They are monthly or hourly, not yearly. `salaryPeriod` carries `YEAR`, `MONTH` or `HOUR` — always group by it before comparing. |
| Can I trust `salaryPeriod`? | It is what the employer selected on InfoJobs, passed through unchanged. Most are right, but a few are not — we have seen a `33000–36000 EUR` range tagged `HOUR`. Treat an hourly range in the thousands as an annual figure that was mislabelled. |
| What is `isRemote`? | True only for offers InfoJobs marks "Solo teletrabajo". Hybrid roles are false, so remote counts stay honest. Use `teleworking` for the three-way split. |
| Can I filter by contract type or education? | Yes. Twelve filters in total: province, city, country, sector, contract type, working hours, remote mode, education level, segment, publication window, salary range, and foreign-country only. That is every filter dimension InfoJobs itself honours — its own search panel exposes no others that change the result set. |
| Can I search several roles in one run? | Yes. `keywords` takes a list and runs one search per entry, `searchUrls` does the same with pasted URLs, and both deduplicate by offer ID. Each search is charged as one search query, and only after it has actually returned an offer. |
| What does `applicants` mean? | How many candidates have applied to that offer, as InfoJobs publishes it on the offer page. It needs `includeDetails`, and it was present on every offer measured. |
| Does `includeDetails` cost extra? | Yes — one extra request per offer, billed as `result-details`. It is off by default, and a row is only charged for it when the page was actually read. |
| How many offers per run? | Free plan: 100. Paid: up to 50,000, bounded by how many offers the search actually holds and by the run timeout. Measured on the platform on 19 August 2026: 100 offers in 36 seconds without offer pages and 60 in 121 seconds with them, so the default 900-second timeout holds roughly **2,400 offers** listing-only or about **440 offers** with `includeDetails` on. An unfiltered search held 57,251 offers across 2,863 pages, and page 1,000 still returned fresh offers, so the ceiling is the clock, not the site. |
| Do I need a proxy? | Yes, and Apify Proxy is on by default. InfoJobs rate-limits by IP: an unproxied IP was measured returning HTTP 403 after about 250 requests in half an hour. It matters most with `includeDetails`, which turns every offer into its own request. |
| Is this an official InfoJobs product? | No. It is unofficial and reads only public InfoJobs data. |

### Related actors

- [ZipRecruiter Ireland Jobs Scraper](https://apify.com/parseforge/ziprecruiter-scraper?fpr=vmoqkp): Irish job listings from keyword, location, or direct URL.
- [LinkedIn Jobs Scraper](https://apify.com/parseforge/linkedin-jobs-scraper?fpr=vmoqkp): LinkedIn postings by search query and location.
- [ClearedJobs.net Jobs Scraper](https://apify.com/parseforge/clearedjobs-scraper?fpr=vmoqkp): security-cleared roles with clearance level.

Browse the full [ParseForge collection](https://apify.com/parseforge?fpr=vmoqkp) for more scrapers.

🆘 **Need help?** Email parseforge@protonmail.com with your run ID, your input, and what you expected.

⚠️ **Disclaimer.** This Actor is unofficial and is not affiliated with, endorsed by, or sponsored by InfoJobs or Grupo Adevinta. It collects only publicly available InfoJobs data. You are responsible for using the data in compliance with InfoJobs' terms and applicable laws, including GDPR. Do not use it to identify, profile, or target individuals.

# Actor input Schema

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

What to search for, exactly as you would type it into the InfoJobs search box — a role, a skill or a company name. Leave empty to list every open offer.

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

Run one search per keyword and merge the results into a single deduplicated dataset. Every filter below applies to all of them. Each keyword is charged as one search query.

## `province` (type: `string`):

Restrict the search to one of Spain's 52 provinces. Leave as Any to search the whole country.

## `city` (type: `string`):

Restrict the search to one city, using InfoJobs' own spelling — Madrid, Barcelona, Santa Cruz de Tenerife. Accents and capitals do not matter, but an unknown city returns zero offers, so the run fails with the list of cities that search actually has.

## `country` (type: `string`):

Country the offer is based in. InfoJobs is a Spanish board, so almost everything is Spain — measured: 57,255 offers total, 4 in Belgium. Use this to isolate its foreign postings.

## `category` (type: `string`):

Restrict the search to one InfoJobs sector.

## `contractType` (type: `string`):

Restrict the search to one contract type.

## `workday` (type: `string`):

Restrict the search to one working-hours arrangement.

## `teleworking` (type: `string`):

Restrict the search by how remote the job is.

## `education` (type: `string`):

Restrict the search to offers asking for one education level.

## `segment` (type: `string`):

Restrict the search to a special InfoJobs segment. Executive lists only management-level offers.

## `sinceDate` (type: `string`):

Only return offers published inside this window.

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

Only keep offers whose published range reaches this gross yearly figure. Monthly ranges are annualised by 12. InfoJobs itself cannot filter by salary — this is applied on the structured range, so offers that publish no salary never match.

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

Only keep offers whose published range starts below this gross yearly figure.

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

InfoJobs offers exactly two orderings. Newest first is the one you want for monitoring; relevance is better for a one-off search.

## `searchUrl` (type: `string`):

Paste a full InfoJobs search URL straight from your browser. When set, it overrides every filter above — useful for a search you already built on the site.

## `searchUrls` (type: `array`):

Paste several full InfoJobs search URLs. When set, they replace every filter above, and each URL is charged as one search query.

## `onlyForeignCountry` (type: `boolean`):

Return only offers located outside Spain.

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

InfoJobs ships the complete job description inside the listing page, so this costs no extra requests. Turn it off to keep the dataset small.

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

Adds applicants, vacancies, required skills, minimum experience and studies, seniority, industry, the normalised job title and the company rating. This costs one extra request per offer and is charged as result-details, so it is off by default.

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

Stop after this many offers. Free-plan runs return up to 100 as a preview.

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

InfoJobs rate-limits by IP — a single unproxied IP was measured returning HTTP 403 after about 250 requests in half an hour. Apify Proxy with the default datacenter group is enough for normal runs, and it matters most with includeDetails, which turns every offer into its own request.

## Actor input object example

```json
{
  "keyword": "desarrollador",
  "keywords": [],
  "province": "",
  "city": "",
  "category": "",
  "contractType": "",
  "workday": "",
  "teleworking": "",
  "education": "",
  "segment": "",
  "sinceDate": "ANY",
  "sortBy": "PUBLICATION_DATE",
  "searchUrl": "",
  "searchUrls": [],
  "onlyForeignCountry": false,
  "includeDescription": true,
  "includeDetails": false,
  "maxItems": 100,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `offers` (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 = {
    "keyword": "desarrollador",
    "sinceDate": "ANY",
    "sortBy": "PUBLICATION_DATE",
    "onlyForeignCountry": false,
    "includeDescription": true,
    "includeDetails": false,
    "maxItems": 100,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("parseforge/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 = {
    "keyword": "desarrollador",
    "sinceDate": "ANY",
    "sortBy": "PUBLICATION_DATE",
    "onlyForeignCountry": False,
    "includeDescription": True,
    "includeDetails": False,
    "maxItems": 100,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("parseforge/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 '{
  "keyword": "desarrollador",
  "sinceDate": "ANY",
  "sortBy": "PUBLICATION_DATE",
  "onlyForeignCountry": false,
  "includeDescription": true,
  "includeDetails": false,
  "maxItems": 100,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call parseforge/infojobs-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,parseforge/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/5ek9x0UIwW1htjVF9/builds/3MHns5QEHntMmB7Nm/openapi.json
