# InfoJobs Scraper - Spain Jobs, Salaries & Companies (`scrapesage/infojobs-scraper`) Actor

Scrape InfoJobs.net, Spain’s largest job board: title, company, city, salary range, contract, workday, remote/hybrid mode, publication date and the full description. Filter by keyword, province, category, contract and remote mode. Optional enrichment adds skills and applicant counts.

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

## Pricing

from $1.65 / 1,000 job offers

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

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

## What's an Apify Actor?

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

## InfoJobs Scraper - Spain Jobs, Salaries & Companies

Scrape **[InfoJobs.net](https://www.infojobs.net)**, Spain's largest job board - roughly **57,000 live offers** at any moment - into a clean, structured dataset. Every row is one job offer with the **hiring company**, the **city**, the **contract and workday**, whether the role is **remote, hybrid or on-site**, a **structured salary range**, the exact **publication timestamp** and the **full offer description**.

No login, no cookies, no browser, no API key. InfoJobs renders its results server-side and ships them as structured data, so this actor reads a real data structure rather than guessing at CSS classes - which is why the fields below come back complete instead of half-empty.

### Why this InfoJobs scraper?

- **Structured salary, not a text blob.** `salaryMin`, `salaryMax`, `salaryPeriod`, `salaryCurrency` and `salaryType` (gross/net) as separate numeric columns, so you can sort, average and chart pay without parsing anything. About **three quarters** of InfoJobs offers publish a range.
- **The full description on every row, free.** InfoJobs ships the complete offer text with each search result, so you get it without a second request and without a second charge.
- **Remote work as a first-class field.** `remoteMode` normalises InfoJobs' Spanish labels to `remote` / `hybrid` / `on-site`, plus `isRemote`, `isHybrid` and `isOnSite` booleans for one-click filtering.
- **Real pick-lists, not free text.** Provinces, categories, contract types, workdays, remote modes, education levels and countries are all **dropdowns built from InfoJobs' own live facet lists**, so a typo is impossible and a filter can never silently fail to apply.
- **Optional deep enrichment** adds required skills, sector, minimum education and experience, vacancies, social benefits, **how many people have already applied**, and the **company's star rating**.
- **Monitor mode** returns only offers that are new since your last run - a clean feed of fresh jobs for a CRM, an alert, or a market tracker.

### Use cases

- **Recruitment & staffing** - track competitor postings, spot companies hiring in bulk, build candidate-facing job feeds.
- **Salary benchmarking** - real published ranges by role, city, contract and seniority across the whole Spanish market.
- **Lead generation** - a company posting five roles this month is growing and buying. `companyName` + `companyUrl` + city on every row.
- **Labour-market research** - remote-vs-office split, contract mix, education requirements and hiring volume by province or sector.
- **Job aggregators & AI agents** - a structured, machine-readable feed of Spain's biggest board.

### How to use

1. Type one or more **search terms** (`desarrollador`, `enfermera`, `comercial`...), **or** paste InfoJobs search URLs straight from your browser, **or** just set filters and leave the search empty to sweep a whole category.
2. Narrow it with the province, category, contract, workday, remote-mode and education pick-lists.
3. Set **Maximum offers to return** and run. Results stream into the dataset as each page is read.

### Input

```json
{
    "searchQueries": ["desarrollador"],
    "provinces": ["33"],
    "remoteModes": ["2"],
    "postedWithin": "_7_DAYS",
    "withSalaryOnly": true,
    "sortBy": "PUBLICATION_DATE",
    "maxResults": 200
}
```

Everything is optional and combines with **AND**:

- **searchQueries** - one term per line; each is searched separately and results are merged and de-duplicated.
- **startUrls / urlsFromFile** - paste InfoJobs search URLs (every filter already in the URL is kept) or single offer URLs. You can type them, upload a file, or link a remote `.txt` / `.csv` / Google Sheet.
- **provinces** - 110 pick-list options, ordered by how many offers each currently holds.
- **cities** - exact city names, applied after the search (InfoJobs itself filters by province).
- **categories** (21) - InfoJobs' own job families.
- **contractTypes** (8), **workdayTypes** (10), **educationLevels** (19), **countries** (27) - all pick-lists.
- **remoteModes** - `Presencial` (on-site), `Híbrido` (hybrid), `Solo teletrabajo` (fully remote), `Sin especificar`.
- **executiveOnly** - InfoJobs' Executive segment only: senior and management roles.
- **postedWithin** - `ANY`, last `24 hours`, `7 days` or `15 days`. These are the only windows InfoJobs supports, so they are a dropdown: an invalid value is rejected before the run starts instead of quietly returning nothing.
- **minSalary / maxSalary / withSalaryOnly** - filter on the published range.
- **titleQuery / companyNameQuery / excludeKeywords / maxDaysOld** - tighten a broad search without re-running it.
- **includeDescription** (on by default), **includeDetails** + **maxDetailRecords** - see below.
- **monitorMode / monitorKey**, **maxResults**, **proxyConfiguration**.

> **Tip:** run with no search term and no URL and you get a small 25-offer sample rather than a full-price bulk pull, so an exploratory or agent-issued call is never an expensive surprise.

### Output

Every row is one job offer. The dataset ships four ready-made **views**: **Job offers**, **Offers with salary**, **Remote & hybrid**, and **Newest first**.

```json
{
    "recordType": "jobOffer",
    "offerId": "aed96d7df74a10ace0200f866b2869",
    "title": "Java Senior Developer - IA Generativa (Sector Seguros)",
    "url": "https://www.infojobs.net/barcelona/java-senior-developer-ia-generativa-sector-seguros/of-iaed96d7df74a10ace0200f866b2869",
    "companyName": "Arestes",
    "companyUrl": "https://arestes.ofertas-trabajo.infojobs.net",
    "companyLogo": "https://multimedia-logos.infojobs.net/image/upload/f2/f21ca57b-...",
    "city": "Barcelona",
    "country": "ES",
    "teleworking": "Híbrido",
    "remoteMode": "hybrid",
    "isRemote": false,
    "isHybrid": true,
    "isOnSite": false,
    "contractType": "Contrato indefinido",
    "workday": "Jornada completa",
    "salaryMin": 44000,
    "salaryMax": 50000,
    "salaryPeriod": "year",
    "salaryCurrency": "EUR",
    "salaryType": "gross",
    "salaryText": "44,000 - 50,000 EUR / year (gross)",
    "hasSalary": true,
    "publishedAt": "2026-08-28T12:30:05.000Z",
    "daysSincePublished": 3,
    "isExecutive": false,
    "badges": ["REUSED"],
    "isFeatured": true,
    "description": "…the full offer text…",
    "descriptionLength": 1885,
    "searchKeyword": "desarrollador",
    "resultPage": 1,
    "scrapedAt": "2026-08-31T21:11:05.036Z"
}
```

#### Deep enrichment - `includeDetails`

Turn it on and each matched offer's own page is fetched and merged in, adding up to **19 more fields**:

`province` · `provinceId` · `category` · `subcategory` · `sector` · `jobLevel` · `department` · `minimumEducation` · `minimumExperience` · `minimumRequirements` · `desiredRequirements` · `skills[]` · `requiredLanguages[]` · `socialBenefits[]` · `vacancies` · `directReports` · `contractDuration` · **`applicantsCount`** (how many candidates have already applied - a direct competition signal) · **`companyRating`** and `companyRatingCount` · `normalizedJobTitle`.

It costs one extra request and one extra event per offer, and `maxDetailRecords` caps how many are enriched so a big search cannot become a big bill by accident.

#### What to expect (field coverage)

Measured on a live run (search `desarrollador`, 25 newest offers, 2026-08-31):

| Field group | Coverage |
|---|---|
| Title, URL, company, company page, city, contract, workday, remote mode, published date, description | **100%** |
| Company logo | 96% |
| Salary range (min/max/period/currency/gross-net) | 56-64% |
| Enrichment fields (with `includeDetails`) | present when InfoJobs publishes them; `skills`, `category`, `province`, `applicantsCount` and `companyRating` were on 100% of enriched offers |

A blank salary means the employer chose not to publish one - roughly a quarter of offers - not that scraping failed. Use **withSalaryOnly** to drop them.

### Monitor mode - only newly posted jobs

Turn on **`monitorMode`** and the actor remembers every offer it has already returned (in a named key-value store) and, on the next run, emits **only offers that are new**, each tagged `monitorEvent: "new"`.

This is **orthogonal to [Apify Schedules](https://docs.apify.com/platform/schedules)**: the Schedule *starts* the run on your cadence; monitor mode decides *what is new*. Use a distinct `monitorKey` per saved watch (one per city, per category, per client) so separate monitors never share memory.

### Automate & schedule

Run it on autopilot and pull results into your own stack: [Schedules](https://docs.apify.com/platform/schedules) for cadence, [webhooks](https://docs.apify.com/platform/integrations/webhooks) to push each finished run onward, and one-click [integrations](https://docs.apify.com/platform/integrations) for Google Sheets, Slack, Airtable, Make and Zapier.

### Integrate with any app

Results are available as **JSON, CSV, Excel, XML and RSS** through the [Dataset API](https://docs.apify.com/api/v2#/reference/datasets/item-collection), and the actor can be started from the [API](https://docs.apify.com/api/v2#/reference/actors/run-collection/run-actor) or any [client library](https://docs.apify.com/api/client/js/).

### Use with AI assistants (MCP)

This actor is exposed through the [Apify MCP server](https://docs.apify.com/platform/integrations/mcp), so Claude, ChatGPT, Cursor and any other MCP client can call it directly as a tool - "find me remote Java jobs in Madrid posted this week, with salary" becomes one tool call.

### Agent-ready: autonomous payments (x402 & Skyfire)

This actor is **agent-ready** — AI agents can discover it, run it, and **pay for it autonomously**, with no Apify account and no human in the loop. It uses [pay-per-event](https://docs.apify.com/platform/actors/publishing/monetize/pay-per-event) pricing and [limited permissions](https://docs.apify.com/platform/actors/development/permissions), so it qualifies for Apify's agentic-payment standards:

- **[x402](https://docs.apify.com/platform/integrations/x402)** — an open, HTTP-native payment protocol. Agents pay per run in USDC on the Base network directly through the [Apify MCP server](https://docs.apify.com/platform/integrations/mcp) — no account, no API key.
- **[Skyfire](https://docs.apify.com/platform/integrations/skyfire)** — agent-to-service payments for fully autonomous AI-agent workflows.

Building an AI agent, MCP tool, or autonomous data pipeline? This scraper is ready to plug in and pay as it goes.

### More job & hiring scrapers from scrapesage

- [ZipRecruiter Scraper](https://apify.com/scrapesage) - US job listings with salary and company
- [Arbeitsagentur Scraper](https://apify.com/scrapesage) - the German federal job board
- [Remote OK / We Work Remotely / Himalayas](https://apify.com/scrapesage) - remote-first job boards
- Browse everything at [apify.com/scrapesage](https://apify.com/scrapesage)

### FAQ

**Do I need an InfoJobs account or API key?**
No. This reads only public search and offer pages.

**How many offers can I get?**
InfoJobs holds roughly 57,000 live offers. A single search is paged until `maxResults` is reached; InfoJobs itself limits how deep any one search can be paged, so for very large sweeps split the work by province or category.

**Why is `salary` empty on some rows?**
The employer did not publish one. Roughly three quarters of offers do. Turn on **withSalaryOnly** to keep only those that did.

**Is the description the full text or a snippet?**
The full text. It is included by default at no extra cost.

**Can I get the offers in English?**
InfoJobs is a Spanish-language board, so titles, descriptions, contract types and workdays come back in Spanish exactly as published. `remoteMode`, `salaryPeriod` and `salaryType` are normalised to English values so you can filter on them without knowing Spanish.

**What does `applicantsCount` mean?**
How many candidates have already applied to that offer, published by InfoJobs itself. It appears when `includeDetails` is on and is the single best signal of how contested a role is.

### Need help?

Open an issue on the actor's **Issues** tab with your input and the run ID and it will be looked at.

***

*This actor collects publicly available job postings for research, recruitment and market-analysis purposes. It is not affiliated with, endorsed by, or sponsored by InfoJobs. InfoJobs is a trademark of its respective owner.*

# Actor input Schema

## `searchQueries` (type: `array`):

What to search for on InfoJobs, one term per line - a job title, a skill or a company (for example <code>desarrollador</code>, <code>enfermera</code>, <code>comercial</code>). Each term is searched separately and the results are merged and de-duplicated. Leave empty and set filters only (province, category, contract...) to sweep every matching offer on the board.

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

Paste InfoJobs search URLs straight from your browser and the actor keeps every filter already in them, paging through the results. Single offer URLs (.../of-i...) also work and are scraped one by one. You can type them, upload a file, or link a remote text file - all four input shapes are accepted.

## `urlsFromFile` (type: `string`):

A block of InfoJobs URLs, one per line - or a single link to a .txt / .csv file, a Google Sheet or a Drive file containing them. Blank lines, comments starting with #, quotes and extra CSV columns are all tolerated, and duplicates are removed.

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

Restrict results to one or more Spanish provinces (plus the international regions InfoJobs indexes). Ordered by how many offers each currently holds.

## `cities` (type: `array`):

Keep only offers whose city matches one of these names exactly, applied after the search (InfoJobs itself filters by province, not city). Use the Spanish spelling as InfoJobs writes it, for example <code>Madrid</code>, <code>Barcelona</code>, <code>Palma de Mallorca</code>, <code>Alicante/Alacant</code>. Left empty, every city is kept.

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

InfoJobs' own job families. Ordered by how many offers each currently holds.

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

Filter by contract type as InfoJobs classifies it.

## `workdayTypes` (type: `array`):

Full time, part time, intensive shifts and so on.

## `remoteModes` (type: `array`):

Filter by how the work is done. <code>Solo teletrabajo</code> is fully remote, <code>Hibrido</code> is hybrid, <code>Presencial</code> is on-site.

## `educationLevels` (type: `array`):

Filter by the minimum qualification the employer asks for.

## `countries` (type: `array`):

InfoJobs is a Spanish board but also indexes offers abroad. Leave empty for all; pick <code>Espana (17)</code> to exclude international listings.

## `executiveOnly` (type: `boolean`):

Return only offers in InfoJobs' Executive segment (senior and management roles). A small, high-value slice of the board.

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

How recent an offer must be. InfoJobs supports exactly these windows; anything else is rejected before the run starts rather than silently returning nothing.

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

Newest first is best for monitoring and for building a fresh lead list; relevance is best for a targeted keyword search.

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

Keep only offers whose published salary reaches this figure, compared in the currency and period InfoJobs published (usually EUR per year, gross). Offers with no published salary are dropped when this is set.

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

Keep only offers at or below this figure. Offers with no published salary are dropped when this is set.

## `withSalaryOnly` (type: `boolean`):

About three quarters of InfoJobs offers publish a salary range. Turn this on to drop the rest.

## `companyNameQuery` (type: `string`):

Keep only offers whose hiring company name contains this text (case-insensitive).

## `titleQuery` (type: `string`):

Keep only offers whose title contains this text (case-insensitive). Useful for tightening a broad search term.

## `excludeKeywords` (type: `array`):

Drop any offer whose title, company or description contains one of these terms - the quickest way to strip staffing agencies or irrelevant seniority from a list.

## `maxDaysOld` (type: `integer`):

Drop offers published more than this many days ago, applied to the exact publication timestamp. Finer than the Published-within windows above and can be combined with them.

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

On by default. InfoJobs ships the complete description with every search result, so this costs no extra requests - turn it off only if you want a slim export.

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

Off by default. Fetches every matched offer's own page to add ~19 more fields: province, category and subcategory, sector, required skills, languages, minimum education and experience, job level, vacancies, direct reports, social benefits, the number of people who have already applied, and the hiring company's rating. Costs one extra request and one extra event per offer.

## `maxDetailRecords` (type: `integer`):

A ceiling on how many offer pages are fetched when the option above is on, so a large search cannot become a large bill by accident.

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

Stops the run once this many offers have been collected across all searches. InfoJobs holds roughly 57,000 live offers, so leave room when sweeping a whole category.

## `monitorMode` (type: `boolean`):

Remembers every offer already returned under this monitor key and emits only the ones that are new, each tagged monitorEvent: "new". Pair it with an Apify Schedule for a clean feed of newly posted jobs.

## `monitorKey` (type: `string`):

Names the memory used by monitor mode. Use a different key per saved watch (one per city, per category, per client) so separate monitors never share state.

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

InfoJobs serves fine from Apify's shared datacenter proxy, which is the default. Switch to residential only if you see the run report refused requests.

## Actor input object example

```json
{
  "searchQueries": [
    "desarrollador"
  ],
  "executiveOnly": false,
  "postedWithin": "ANY",
  "sortBy": "PUBLICATION_DATE",
  "withSalaryOnly": false,
  "includeDescription": true,
  "includeDetails": false,
  "maxDetailRecords": 200,
  "maxResults": 100,
  "monitorMode": false,
  "monitorKey": "default",
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `results` (type: `string`):

All scraped InfoJobs offers in the default dataset.

# 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 = {
    "searchQueries": [
        "desarrollador"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapesage/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 = {
    "searchQueries": ["desarrollador"],
    "proxyConfiguration": { "useApifyProxy": True },
}

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

```

## MCP server setup

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