# Indeed Salary Matrix — Levels.fyi Alternative + AI (`harvestlab/indeed-scraper`) Actor

Indeed salaries across 8 countries at $0.003/job — 99%+ run success. RAG-ready jobs + salary matrix for VC diligence, talent-acquisition + comp-benchmarking agents. Geo arbitrage, emerging-skill gaps, AI briefs. Replaces Levels.fyi, Payscale, LinkedIn Talent Insights. MCP-ready, x402-ready.

- **URL**: https://apify.com/harvestlab/indeed-scraper.md
- **Developed by:** [Nick](https://apify.com/harvestlab) (community)
- **Categories:** Jobs, AI, MCP servers
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, NaN bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

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

## What's an Apify Actor?

Actors are a software tools running on the Apify platform, for all kinds of web data extraction and automation use cases.
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.

In JavaScript/TypeScript projects, use official [JavaScript/TypeScript client](https://docs.apify.com/api/client/js.md):

```bash
npm install apify-client
```

In Python projects, use official [Python client library](https://docs.apify.com/api/client/python.md):

```bash
pip install apify-client
```

In shell scripts, use [Apify CLI](https://docs.apify.com/cli/docs.md):

````bash
# MacOS / Linux
curl -fsSL https://apify.com/install-cli.sh | bash
# Windows
irm https://apify.com/install-cli.ps1 | iex
```bash

In AI frameworks, you might use the [Apify MCP server](https://docs.apify.com/platform/integrations/mcp.md).

If your project is in a different language, use 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

## Indeed Scraper - Jobs, Salaries & Multi-Query AI Compare

Scrape job listings from Indeed, the world's largest job board, across 8 countries. Extract job titles, company names, salaries, descriptions, skills, benefits, and hiring signals. This Indeed scraper parses salary ranges automatically, detects 70+ technical and professional skills from job descriptions, and includes two AI-powered intelligence modes: **single-query market analysis** and **multi-query x multi-location compare mode** (new in v1.6) that builds a cross-role salary matrix, geo premium, and emerging-skill-gap report in a single run.

Whether you are a compensation team building multi-geo salary bands, a recruiter benchmarking 3 competing roles across 5 metros, or a career-switcher deciding between role pivots, this actor delivers structured Indeed data and AI-driven market intelligence without requiring you to stitch multiple scrapes together.

### What it does

Indeed Scraper pulls structured job listings from Indeed across 8 countries (US, UK, Canada, Australia, Germany, France, Netherlands, India) and turns raw postings into decision-ready intelligence.

Every job is parsed into clean fields: normalized salary ranges (hourly/yearly/K-notation all resolved to `min`/`max`/`period`), 70+ detected technical and professional skills extracted from the job description, employer rating, benefits list, and job type. At $0.003 per deduplicated listing, it undercuts every competing Indeed scraper on Apify while shipping richer per-job structure.

The standout feature is **multi-query compare mode** (v1.6). Provide an array of job titles and an array of locations and the actor runs the full N×M grid in parallel — up to 16 cells — then sends all results to an LLM in one shot. The output is a single cross-role comparison report: a median salary matrix, geographic pay premium ranking, hottest skills across all roles, emerging skill gaps between titles, and per-audience recommendations. It also exports a CSV salary matrix and a Markdown executive briefing to the run's key-value store, ready to drop into a Slack workflow or weekly briefing.

For single-query runs, AI market analysis produces salary percentile bands (25th/50th/75th/90th), top-skill frequency ranking, company hiring-volume leaders, and remote/on-site ratio — all from a single `$0.05` event charge.

Compensation teams, recruiters, HR analysts, career researchers, and workforce development organizations use it to replace hours of manual Indeed browsing with a scheduled, structured data pipeline.

### Features

- **8-country support** -- search Indeed across US, UK, Canada, Australia, Germany, France, Netherlands, and India using localized Indeed domains
- **Multi-query compare mode (NEW in v1.6)** -- supply `queries[]` + `locations[]` and the actor runs the full N x M grid in parallel (max 8 concurrent, capped at 16 cells), then an LLM produces a single cross-query / cross-location comparison report: median salary matrix, geo premium, hottest skills overall, emerging-skill gaps between roles, and per-audience recommendations
- **Advanced job filters** -- filter by date posted, job type (full-time, part-time, contract, internship), experience level (entry, mid, senior), salary minimum, and remote/hybrid/on-site
- **Automatic salary parsing** -- extracts and normalizes salary ranges from free-text salary strings, handling hourly rates, yearly salaries, K notation, and range formats into structured min/max/period fields
- **70+ skill detection** -- scans title + job descriptions to identify mentioned technical skills (Python, AWS, Docker, React, SQL, Snowflake, dbt, LangChain, LLM, RAG, etc.) and professional skills, building a structured skills profile per job
- **Benefits extraction** -- captures listed benefits like health insurance, 401(k), remote work, PTO, and stock options
- **Company ratings** -- extracts employer rating scores from Indeed's company review system
- **AI market intelligence (single-query)** -- optional LLM-powered report with salary benchmarking (25th/50th/75th/90th percentiles), top skills by frequency, company hiring volume, remote vs on-site ratios, and actionable recommendations
- **Compare-mode export artifacts (NEW in v1.7)** -- render a CSV salary-matrix spreadsheet and/or a Markdown executive briefing to the run's key-value store, with a webhook for autonomous Slack/Notion delivery
- **Multi-LLM support** -- choose OpenRouter (recommended — 300+ models), Anthropic (Claude), Google AI (Gemini), OpenAI (GPT), or Ollama (self-hosted) for AI analysis
- **Pay-per-event pricing** -- only pay for what you scrape, no monthly fees or commitment

### Use Cases

- **Recruiters and staffing agencies (talent intelligence)** -- benchmark competing offers across 3-8 metros in one run. The compare-mode salary matrix shows where the market is paying a premium for the role you're filling and which companies are hiring aggressively, so you can sharpen your pitch and pricing in a single weekly Slack briefing.
- **HR and compensation teams (compensation benchmarking, alternative to Levels.fyi / Payscale)** -- build data-driven compensation bands without a five-figure SaaS subscription. Levels.fyi and Payscale pull from self-reported employee data with a quarterly lag; this actor pulls live employer-posted salary ranges, normalizes hourly/yearly/K-notation, and emits 25/50/75/90 percentile bands per role and geography for ~$1 a run.
- **Job seekers (geo arbitrage and role pivots)** -- run the same query across 4-6 cities + Remote and let the geo-premium ranking show where your role pays 20-40% more for the same skill stack. Pair role pivot research (`["data engineer", "ML engineer", "AI engineer"]`) with the emerging-skill-gap report to see which adjacent role rewards your existing skills best.
- **VCs and PE firms (hiring signals as portfolio diligence)** -- monitor portfolio companies' hiring volume, role mix, and salary bands as a leading indicator of growth, pivots, or burn. Schedule weekly compare-mode runs across portfolio cohorts and feed the Markdown briefing straight into your investment-team Slack — cheaper and faster than LinkedIn Talent Insights ($1k+/month enterprise tier).
- **Market researchers and equity analysts** -- track hiring trends as a leading economic indicator. Run weekly `["software engineer", "sales", "operations"]` comparisons across the FAANG cohort or any sector and track shifts in volume, salary growth, and remote ratio in a longitudinal dataset.
- **Workforce-development organizations and bootcamps** -- inform curriculum design with current skill demand. The 70+ skill detection library shows which programming languages, frameworks, and AI/ML tools are accelerating in your local market — informing course updates without buying a Burning Glass / Lightcast license.
- **Talent acquisition leaders (alternative to LinkedIn Talent Insights / Glassdoor for Employers)** -- LinkedIn Talent Insights starts at ~$1k/seat/month; Glassdoor for Employers bundles reviews with limited salary intelligence. This actor delivers structured cross-role/cross-geo salary + skill data on demand at $0.003/job, ready for direct ingestion into your BI stack or feeding Slack/Notion briefings via the included webhook.
- **Salary researchers, academic economists, and consultants** -- aggregate Indeed postings across roles, locations, experience levels, and time. Build longitudinal compensation databases for wage research, remote-work adoption studies, or client-facing market intelligence reports — at a fraction of the cost of API access from Indeed Hiring Lab or Burning Glass.

### Input

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `searchQuery` | string | -- (required in single-query mode) | Job title or keywords (e.g., "software engineer", "marketing manager") |
| `queries` | string[] | -- | **Compare mode**: list of job titles to run in parallel. Requires `locations` also set. |
| `locations` | string[] | -- | **Compare mode**: list of locations to run in parallel. Requires `queries` also set. |
| `location` | string | -- | City, state, zip code, or "remote" (single-query mode only) |
| `country` | select | `us` | Indeed country domain: `us`, `uk`, `ca`, `au`, `de`, `fr`, `nl`, `in` |
| `maxJobs` | integer | 25 | Maximum jobs to scrape per cell (1-100). In compare mode this applies per (query, location) cell. |
| `datePosted` | select | `7` | Posting recency filter: `1` (24h), `3` (3 days), `7` (7 days), `14` (14 days), `any` |
| `jobType` | select | `any` | Job type: `any`, `fulltime`, `parttime`, `contract`, `internship`, `temporary` |
| `experienceLevel` | select | `any` | Experience: `any`, `entry`, `mid`, `senior` |
| `salaryMin` | integer | -- | Minimum salary filter in local currency |
| `remoteFilter` | select | `any` | Work arrangement: `any`, `remote`, `hybrid`, `in_person` |
| `sortBy` | select | `relevance` | Sort by: `relevance` or `date` |
| `fetchDetails` | boolean | false | Backfill salary from job detail pages when the list card is missing it (common on sponsored/pagead cards like nursing roles). Adds ~1 extra request per missing-salary job. Off by default to keep runs fast. |
| `exportFormat` | select | `none` | **Compare mode only**: render a briefing artifact. Options: `none`, `csv`, `markdown`, `both`. Artifact stored as `compare-{run_id}.csv` / `.md` in the run's key-value store. Charged $0.01 per artifact. |
| `artifactWebhookUrl` | string | -- | **Compare mode only**: optional HTTPS webhook to POST artifact URLs after generation. Wire to Slack / Zapier / Make for autonomous weekly compensation briefings. Non-fatal on failure. |
| `enableAiAnalysis` | boolean | false | Generate AI market intelligence report (single-query) or comparison report (compare mode) |
| `llmProvider` | select | `openrouter` | AI provider: `openrouter`, `anthropic`, `google`, `openai`, or `ollama` |
| `llmModel` | string | -- | Override default LLM model (leave empty for recommended default) |
| `openrouterApiKey` | string | -- | OpenRouter API key (required if using OpenRouter) |
| `anthropicApiKey` | string | -- | Anthropic API key (required if using Anthropic) |
| `googleApiKey` | string | -- | Google AI API key (required if using Google AI) |
| `openaiApiKey` | string | -- | OpenAI API key (required if using OpenAI) |
| `ollamaBaseUrl` | string | `http://localhost:11434` | Ollama base URL (required if using Ollama) |
| `proxyConfiguration` | proxy | RESIDENTIAL | Proxy settings. Indeed aggressively blocks datacenter IPs — RESIDENTIAL is strongly recommended. |

#### Full Example Input

```json
{
    "searchQuery": "software engineer",
    "location": "San Francisco, CA",
    "country": "us",
    "maxJobs": 25,
    "datePosted": "7",
    "jobType": "fulltime",
    "experienceLevel": "mid",
    "remoteFilter": "any",
    "sortBy": "relevance",
    "enableAiAnalysis": true,
    "llmProvider": "openrouter",
    "openrouterApiKey": "sk-or-..."
}
````

### Output

Each job is pushed as a separate dataset item:

```json
{
    "job_id": "abc123def456",
    "title": "Senior Software Engineer",
    "company_name": "TechCorp Inc.",
    "company_rating": 4.2,
    "location": "San Francisco, CA",
    "is_remote": false,
    "salary_text": "$150,000 - $200,000 a year",
    "salary_min": 150000,
    "salary_max": 200000,
    "salary_period": "yearly",
    "salary_currency": "USD",
    "currency": "USD",
    "job_type": "full-time",
    "description_snippet": "We are looking for a Senior Software Engineer to join...",
    "posted_date": "2026-04-08",
    "job_url": "https://www.indeed.com/viewjob?jk=abc123def456",
    "url": "https://www.indeed.com/viewjob?jk=abc123def456",
    "benefits": ["Health insurance", "401(k)"],
    "skills_mentioned": ["Python", "AWS", "Docker", "Kubernetes", "React"],
    "search_query": "software engineer",
    "search_location": "San Francisco, CA",
    "search_country": "us",
    "scraped_at": "2026-04-10T12:00:00+00:00"
}
```

#### Output field reference

| Field | Type | Description |
|-------|------|-------------|
| `job_id` | string | 16-char Indeed job key (e.g. `d55eb1a8c9e4bbe8`) |
| `title` | string | Job title as posted |
| `company_name` | string | Employer name |
| `company_rating` | float | Indeed employer rating (1.0-5.0) when available |
| `location` | string | City, state / region as posted |
| `is_remote` | boolean | True if the listing is tagged remote |
| `salary_text` | string | Raw salary string from the listing |
| `salary_min` / `salary_max` | number | Parsed numeric salary range |
| `salary_period` | string | `hourly`, `daily`, `weekly`, `monthly`, or `yearly` |
| `salary_currency` | string | ISO-ish currency code (USD, GBP, EUR, etc.) — legacy field |
| `currency` | string | Portfolio-standard ISO 4217 currency code — prefer this |
| `job_type` | string | `full-time`, `part-time`, `contract`, `internship`, `temporary` |
| `description_snippet` | string | First ~500 chars of the job description |
| `posted_date` | string | ISO 8601 date derived from Indeed's "X days ago" label |
| `job_url` | string | Direct `viewjob?jk=...` link — legacy field |
| `url` | string | Portfolio-standard URL field — prefer this |
| `benefits` | string\[] | Extracted perk labels (health, 401k, RSU, etc.) |
| `skills_mentioned` | string\[] | 70+ tech + business skills detected in title + description |
| `search_query` / `search_location` / `search_country` | string | Echo of the original request |
| `scraped_at` | string | ISO 8601 UTC timestamp |

**Field migration note**: `salary_currency` and `job_url` are retained for backward compatibility, but both dual-emit the portfolio-standard `currency` and `url` alongside. New integrations should read `currency` and `url`; legacy names will be dropped in a future release.

#### AI Market Intelligence Output

When `enableAiAnalysis` is enabled, an additional dataset item contains a comprehensive market report including:

- **Salary benchmarking** -- 25th, 50th, 75th, and 90th percentile salary ranges for the searched role
- **Top skills by frequency** -- ranked list of the most mentioned technical and professional skills
- **Company hiring volume** -- which companies are posting the most jobs and at what salary levels
- **Remote vs on-site ratios** -- breakdown of work arrangement preferences in the market
- **Top-paying listings** -- highest compensation packages with company and requirements details
- **Market temperature** -- hiring velocity, competition level, and demand indicators
- **Actionable recommendations** -- tailored advice for job seekers, recruiters, and hiring managers

#### Cost and Pricing

This actor uses Apify's pay-per-event pricing model. You only pay for what you scrape — no monthly fees, no commitments.

| Event | Price | Description |
|-------|-------|-------------|
| `job-scraped` | $0.003 | Charged per deduplicated job listing extracted |
| `ai-analysis-completed` | $0.05 | Charged when a single-query AI market intelligence report is generated |
| `market-comparison-report` | $0.10 | Charged when a multi-query compare-mode report is generated |
| `export-csv-generated` | $0.01 | Charged per compare-mode CSV salary-matrix artifact |
| `export-markdown-generated` | $0.01 | Charged per compare-mode Markdown briefing artifact |

| Scenario | Jobs | AI Event | Export | Total Cost |
|----------|------|----------|--------|-----------:|
| Quick salary check | 10 | none | none | $0.03 |
| Role market analysis (single query) | 25 | $0.05 ai-analysis | none | $0.125 |
| Full market intelligence (single query) | 50 | $0.05 ai-analysis | none | $0.20 |
| Large-scale data collection | 100 | $0.05 ai-analysis | none | $0.35 |
| Compare 3 roles x 4 cities at 25/cell | 300 | $0.10 comparison | none | $1.00 |
| Weekly Slack briefing: 3 roles x 4 cities | 300 | $0.10 comparison | CSV+MD ($0.02) | $1.02 |
| Daily Slack tracker: 2 roles x 3 cities | 150 | $0.10 comparison | CSV+MD ($0.02) | $0.57 |

#### How we compare to other Indeed scrapers on Apify

| Scraper | Price | Model | AI Analysis |
|---------|-------|-------|-------------|
| **This actor** | **$3/1K jobs + $0.05/AI report** | Pay-per-event, no base fee | Yes (5 providers) |
| `misceres/indeed-scraper` | $3/1K | Pay-per-result | No |
| `borderline/indeed-scraper` | $5/1K | Pay-per-result | No |
| `memo23/apify-indeed-cheerio` | $29/month + usage | Rental + usage | No |
| `curious_coder/indeed-scraper` | $20/month + usage | Rental + usage | No |

### Quick Start

The simplest way to get started -- search for a single job title:

```json
{
    "searchQuery": "data scientist",
    "location": "New York, NY",
    "maxJobs": 10
}
```

This scrapes the top 10 data scientist job listings in New York sorted by relevance.

#### Weekly Comp-Briefing Template (one-click clone)

Saves a recurring task that runs every Monday 8am, comparing 3 roles x 3 locations, exports to CSV + Markdown, posts the briefing to your Slack via webhook.

```json
{
  "queries": ["data engineer", "ML engineer", "AI engineer"],
  "locations": ["New York, NY", "San Francisco, CA", "Remote"],
  "country": "us",
  "datePosted": 7,
  "compareMode": true,
  "exportFormat": "both",
  "enableAiAnalysis": true,
  "aiProvider": "openrouter",
  "artifactWebhookUrl": "https://hooks.slack.com/services/YOUR/WEBHOOK/HERE",
  "maxJobs": 50
}
```

1. Open the actor in Apify Console.
2. Click "Save & Build".
3. Paste the JSON above into Input.
4. Set Schedule -> Cron `0 8 * * MON`.
5. Replace the webhook URL with your Slack incoming webhook.

**Cost callout:** Each weekly run: ~$1.02 (50 jobs x $0.003 + 3 compare reports x $0.10 + CSV+MD exports + AI). Annual cost: ~$53 vs ~$1,200/yr for Levels.fyi Insights team license.

#### Multi-Query Compare Mode (v1.6+)

Supply `queries` and `locations` as arrays to run the full N x M grid in one job and get a single cross-role / cross-location AI comparison report:

```json
{
    "queries": ["data scientist", "ML engineer", "AI researcher"],
    "locations": ["New York, NY", "San Francisco, CA", "Austin, TX", "Remote"],
    "country": "us",
    "maxJobs": 25,
    "enableAiAnalysis": true,
    "llmProvider": "openrouter",
    "openrouterApiKey": "sk-or-..."
}
```

The actor runs 3 x 4 = 12 parallel searches (max 8 concurrent), writes every job to the dataset tagged with its `search_query` and `search_location`, and pushes a `market_comparison_report` item containing the median-salary matrix, highest-paying combinations, geo premium analysis, hottest skills overall, and emerging-skill gaps between roles. Grid is capped at 16 cells to protect proxy budget.

Compare mode charges a single `$0.10` `market-comparison-report` event in place of the `$0.05` `ai-analysis-completed` event.

#### Compare-Mode Export Artifacts (v1.7+)

Autonomous weekly compensation briefings. Set `exportFormat` to `"csv"`, `"markdown"`, or `"both"` and the actor renders a ready-to-share artifact on top of the raw dataset:

- `"csv"` -- RFC-4180 salary-matrix spreadsheet. Loads cleanly into Google Sheets, Tableau, Power BI, Excel.
- `"markdown"` -- README-style executive brief designed for direct paste into Slack / Notion / email.
- `"both"` -- emit both. Charged $0.01 per artifact.

Each artifact is stored in the run's default key-value store as `compare-{run_id}.csv` / `.md` and the public URL is included as an `export_artifact` dataset item.

Pair with the optional `artifactWebhookUrl` input and Apify Scheduler for an autonomous weekly comp-briefing pipeline:

```json
{
    "queries": ["data scientist", "ML engineer"],
    "locations": ["New York, NY", "San Francisco, CA", "Remote"],
    "country": "us",
    "maxJobs": 25,
    "enableAiAnalysis": true,
    "llmProvider": "openrouter",
    "openrouterApiKey": "sk-or-...",
    "exportFormat": "both",
    "artifactWebhookUrl": "https://hooks.slack.com/services/T00/B00/XXXX"
}
```

Every Monday at 8 AM, the actor scrapes 2 roles x 3 cities, writes a Markdown brief + CSV matrix to key-value storage, and POSTs the URLs to your Slack / Zapier / Make webhook. Weekly $1.02; daily $0.57.

#### More Examples

**Basic job search** — scrape software engineer jobs in San Francisco:

```json
{
    "searchQuery": "software engineer",
    "location": "San Francisco, CA",
    "maxJobs": 25,
    "country": "us"
}
```

**Multi-query market research** — compare salaries across roles in one city:

```json
{
    "queries": ["data scientist", "machine learning engineer", "AI researcher"],
    "locations": ["New York, NY"],
    "maxJobs": 10,
    "country": "us"
}
```

**Remote jobs with AI analysis** — find remote Python developer jobs and get market intelligence:

```json
{
    "searchQuery": "Python developer remote",
    "maxJobs": 50,
    "enableAiAnalysis": true,
    "llmProvider": "openrouter",
    "openrouterApiKey": "YOUR_KEY"
}
```

### Troubleshooting

**Getting blocked or 0 results returned**
Indeed uses aggressive bot detection (CAPTCHA, rate limits). Always set `proxyConfiguration` with `RESIDENTIAL` proxy group. Run with a low `maxJobs` (10-20) first to verify the proxy is working. US proxies work best for indeed.com; match country to your target Indeed domain.

**Salary data missing for most jobs**
Salary is employer-provided and often not disclosed. Indeed reports that only ~30-40% of job postings include salary data. The actor captures salary when present; missing salary results in `null` fields, not an error. Enable `fetchDetails` to backfill salary from job detail pages for sponsored listings that omit salary on the list card.

**Jobs appear duplicated across result pages**
Indeed sometimes serves the same promoted job listing on multiple search pages. The actor deduplicates by `job_id` within a single run. If you run multiple queries, cross-run dedup is your responsibility (use `job_id` as the unique key).

**AI comparison fails for multi-query runs**
AI cross-role salary matrix requires multiple entries in `queries` to be set. With only one query, AI analysis runs in single-role mode (no salary matrix). Set at least 2 different job titles in `queries` to activate cross-role comparison.

**CSV/Markdown export not appearing in dataset**
CSV and Markdown exports are pushed as special `_type: "export"` items to the dataset. They won't appear in the standard dataset view's item count — check the raw dataset JSON for items where `_type === "export"`. Download via the Apify API: `GET /v2/datasets/{id}/items?format=json`.

**Indeed bot detection — persistent failures**
Indeed uses sophisticated bot detection including behavioral analysis, CAPTCHA challenges, and IP reputation scoring. If you experience persistent 403 errors: (1) ensure you are using RESIDENTIAL proxies, not datacenter; (2) try a lower `maxJobs` (10-15 per cell); (3) try again later as proxy IP pools rotate; (4) try a different country domain. There is no guaranteed workaround — residential proxies achieve ~80-90% success rates on most queries.

**Frequently Asked Questions**

*How accurate is the salary data?* The actor extracts salary information directly from Indeed job postings. Salary data is only available for jobs that include salary in their listings -- typically 30-60% of postings depending on the role and market. The automatic parser normalizes all formats (hourly, yearly, K notation, ranges) into consistent structured fields.

*Which countries are supported?* The actor supports 8 Indeed country domains: United States (us), United Kingdom (uk), Canada (ca), Australia (au), Germany (de), France (fr), Netherlands (nl), and India (in). Each country uses the localized Indeed domain for accurate local results.

*How does skill detection work?* The actor scans job titles + description snippets against a library of 70+ technical and professional skills including programming languages (Python, Java, JavaScript, .NET), cloud platforms (AWS, Azure, GCP), data/ML tooling (Snowflake, Databricks, Airflow, dbt, TensorFlow, PyTorch), frameworks (React, Next.js, Django, Spring), infra (Docker, Kubernetes, Terraform, Ansible), modern AI stack (OpenAI, LangChain, LLM, RAG), and tools (Jira, Figma, Salesforce, SAP).

*How does compare mode work?* Set `queries` (e.g. `["data scientist", "ML engineer", "AI researcher"]`) and `locations` (e.g. `["New York, NY", "San Francisco, CA", "Remote"]`) instead of the single `searchQuery` / `location` fields. Arrays take precedence. The actor runs the full N x M grid of searches in parallel (8 concurrent, capped at 16 cells), tags every job with its `search_query` and `search_location`, and — if `enableAiAnalysis` is true — produces a single `market_comparison_report` with a median-salary matrix, highest-paying combinations, geo premium analysis, hottest skills, and emerging skill gap callouts.

*Can I track hiring trends over time?* Yes. Schedule regular runs on Apify (weekly or monthly) for the same search queries and locations. Over time, you build a longitudinal dataset showing changes in job volume, salary ranges, skill demand, and remote work adoption.

### Legal and Compliance

This actor scrapes publicly available data. By using this actor, you agree to the following:

- **Your responsibility**: You are solely responsible for ensuring your use complies with all applicable laws, regulations, and the target website's terms of service. This includes but is not limited to GDPR (EU), CCPA (California), and other data protection laws in your jurisdiction.
- **No legal advice**: This actor does not constitute legal advice. Consult a qualified attorney if you have questions about the legality of your specific use case.
- **Intended use**: This actor is designed for legitimate business purposes such as market research, competitive analysis, and academic research using publicly accessible data.
- **Data handling**: You are responsible for how you store, process, and share any data collected. Ensure you have a lawful basis for processing any personal data under applicable privacy laws.
- **Rate limiting**: This actor implements polite crawling practices including request delays and retry backoff to minimize impact on target servers.
- **No warranty**: This actor is provided "as is" without warranty. Data accuracy depends on the target website's content and structure.
- **Job data**: Indeed's terms of service restrict automated data collection. Job listings are provided for informational and research purposes. Do not use scraped data to create competing job board services.
- **Personal data notice**: Job listings may contain names of hiring managers or company representatives. Under GDPR and similar regulations, even publicly available personal data may be subject to data protection requirements. Ensure you have a lawful basis (such as legitimate interest) for collecting and processing this data. Implement appropriate data retention policies.

#### Limitations

- **Indeed aggressively blocks automated access.** Residential proxies are required and even then, some requests may be blocked. The actor rotates proxy IPs between retries with exponential backoff. If you experience persistent 403 errors, try running again (different proxy IPs may succeed) or try a different country domain.
- Salary data is only available for jobs that include salary information in their listings (not all do).
- Skills detection is based on pattern matching from job description snippets, not full job descriptions -- some skills may be missed.
- AI analysis quality depends on the number of jobs scraped (25+ recommended) and the LLM model chosen.
- Indeed's HTML structure may change; the actor uses multiple parsing strategies for resilience but temporary disruptions are possible.
- Maximum 100 jobs per run to maintain reasonable resource usage.
- Job posting recency depends on Indeed's internal date tracking, which may not always be precise.

#### Tips for Reliable Runs

- **Use specific job titles.** "Senior Data Engineer" returns more targeted results than generic terms like "data" or "engineer."
- **Filter by date posted.** Set `datePosted` to `7` or `3` to focus on active, current openings rather than stale listings.
- **Enable AI analysis for salary benchmarking.** The AI report synthesizes salary data across all scraped jobs into actionable percentile ranges.
- **Scrape 25+ jobs for meaningful insights.** Both skill detection patterns and AI analysis improve significantly with more data points.
- **Compare across locations.** Run the same search query in multiple cities to understand geographic salary differences.
- **Use residential proxies for large runs.** For scraping 50+ jobs, residential proxies improve reliability.

#### Implementation Notes

- Uses HTTP requests with rotating User-Agents (no browser required for basic scraping)
- Parses Indeed's server-rendered HTML with multiple extraction strategies for resilience
- Automatic salary parser handles formats like "$150K-$200K", "$45/hr", "$150,000 - $200,000 a year"
- Skills detection uses a curated library of 70+ patterns matching common technical, data/ML, and professional skills
- AI analysis sends aggregated job statistics to the LLM -- individual job descriptions are not sent in full
- Supports Apify proxy with automatic proxy URL detection from environment variables

#### Related Actors

Pair this actor with others from the same portfolio for richer pipelines:

- **[Contact Extractor](https://apify.com/harvestlab/contact-extractor)** — Enrich Indeed company data with hiring-manager contacts. Feed each listing's company website through Contact Extractor to get SMTP-verified emails, direct phones, and tech-stack signals — turning a job board into a recruiter sourcing pipeline.
- **[News Monitor](https://apify.com/harvestlab/news-monitor)** — Track funding rounds, expansions, and layoff news for the companies hiring on Indeed. Combine recent press with active job postings to spot growth-stage employers and time applications (or recruiter outreach) around real-world hiring momentum.
- **[Reddit Scraper](https://apify.com/harvestlab/reddit-scraper)** — Pull employer reputation signals from subreddits like r/cscareerquestions, r/recruitinghell, and company-specific threads. Cross-reference Indeed listings with employee sentiment to filter out toxic workplaces before you apply or shortlist candidates.

# Actor input Schema

## `searchQuery` (type: `string`):

Job title or keywords (e.g. "software engineer", "data scientist", "marketing manager")

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

CLI alias for searchQuery. Hidden from Console form.

## `q` (type: `string`):

CLI alias for searchQuery. Hidden from Console form.

## `search` (type: `string`):

CLI alias for searchQuery. Hidden from Console form.

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

CLI alias for searchQuery. Hidden from Console form.

## `searchTerm` (type: `string`):

CLI alias for searchQuery. Hidden from Console form.

## `jobTitle` (type: `string`):

CLI alias for searchQuery. Hidden from Console form.

## `queries` (type: `array`):

Optional list of job titles to search in parallel, e.g. \["data scientist", "ML engineer", "AI researcher"]. When provided alongside 'locations', the actor runs N×M searches and generates a single cross-query/cross-location comparison report (charged at $0.10 instead of $0.05). Leave empty to run a single searchQuery.

## `locations` (type: `array`):

Optional list of locations to search in parallel, e.g. \["New York, NY", "San Francisco, CA", "Austin, TX", "Remote"]. Combined with 'queries' to run a full N×M grid. Leave empty to use the single 'location' field.

## `location` (type: `string`):

City, state, or "remote" (e.g. "New York, NY", "San Francisco, CA", "Remote")

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

CLI alias for location. Hidden from Console form.

## `where` (type: `string`):

CLI alias for location. Hidden from Console form.

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

Indeed country domain to search (e.g. United States uses indeed.com, United Kingdom uses uk.indeed.com, Germany uses de.indeed.com).

## `maxJobs` (type: `integer`):

Maximum number of job listings to scrape (1-100). Indeed shows ~15 results per page. Start with 25 to test before running larger jobs.

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

CLI alias for maxJobs. Hidden from Console form.

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

CLI alias for maxJobs. Hidden from Console form.

## `datePosted` (type: `string`):

Filter jobs by how recently they were posted. Useful for monitoring new openings daily/weekly.

## `jobType` (type: `string`):

Filter by employment type (e.g. Full-time for salaried W-2/permanent roles, Contract for 1099/freelance work, Internship for student positions).

## `experienceLevel` (type: `string`):

Filter by required experience: Entry level (0-2 years / new grad), Mid level (3-5 years), Senior level (6+ years / lead roles).

## `salaryMin` (type: `integer`):

Minimum annual salary in the local currency for the selected Country (USD for US, GBP for UK, EUR for DE/FR/NL, CAD for Canada, AUD for Australia, INR for India). Example: 80000 for $80k/year in the US.

## `remoteFilter` (type: `string`):

Filter by work arrangement: Remote (fully WFH), Hybrid (mix of office/home), In-person (on-site only).

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

Result ordering. 'Relevance' (default) ranks by Indeed's match score against your keywords — best for broad keyword searches. 'Date posted' shows newest first — best for daily/weekly monitoring of new openings and avoiding stale listings.

## `fetchDetails` (type: `boolean`):

When enabled, for any job whose list-card didn't render a salary (common for sponsored /pagead ads like nursing roles), fetch the job detail page and try to extract salary from JSON-LD or the page body. Adds ~1 extra request per missing-salary job. Default off to keep runs fast.

## `exportFormat` (type: `string`):

When compare mode is active (queries + locations both set), render a ready-to-share salary matrix artifact in addition to the dataset. 'csv' = RFC-4180 salary-matrix spreadsheet (Google Sheets / Tableau / Excel friendly, one row per cell). 'markdown' = README-style executive brief for direct paste into Slack / Notion / email. 'both' = emit both. Each artifact is stored in the run's key-value store as `compare-{run_id}.csv` / `.md` with a public URL included in the dataset report. Charged $0.01 per artifact.

## `artifactWebhookUrl` (type: `string`):

Optional HTTPS webhook to POST the artifact URLs and run metadata after generation — wire it to Slack Incoming Webhook, Zapier, Make, or your own endpoint for autonomous weekly compensation briefings via Apify Scheduler. Webhook receives a JSON body: `{run_id, queries, locations, country, total_jobs, csv_url, markdown_url, message}`. Leave empty to skip. Failure to POST is non-fatal.

## `enableAiAnalysis` (type: `boolean`):

Generate an AI-powered job market analysis report with salary benchmarking, skill trends, and hiring insights

## `llmProvider` (type: `string`):

Which AI provider to use for job market analysis. OpenRouter is the cheapest option.

## `llmModel` (type: `string`):

Specific model to use. Leave empty for the provider default (google/gemini-2.0-flash-001 for OpenRouter, claude-sonnet-4-20250514 for Anthropic, gemini-2.0-flash for Google AI, gpt-4o-mini for OpenAI, llama3.1 for Ollama).

## `openrouterApiKey` (type: `string`):

API key from openrouter.ai (required if using OpenRouter provider)

## `anthropicApiKey` (type: `string`):

API key from console.anthropic.com (required if using Anthropic provider)

## `googleApiKey` (type: `string`):

API key for Google AI (Gemini). Get one at aistudio.google.com/app/apikey

## `openaiApiKey` (type: `string`):

API key from platform.openai.com (required if using OpenAI provider)

## `ollamaBaseUrl` (type: `string`):

Base URL for Ollama API. Default: http://localhost:11434

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

Proxy settings. Indeed aggressively blocks datacenter IPs with 403/CAPTCHA walls — RESIDENTIAL (default) is strongly recommended. Leave Apify Proxy enabled unless you're supplying your own custom proxy list.

## Actor input object example

```json
{
  "searchQuery": "software engineer",
  "location": "New York, NY",
  "country": "us",
  "maxJobs": 25,
  "datePosted": "7",
  "jobType": "any",
  "experienceLevel": "any",
  "remoteFilter": "any",
  "sortBy": "relevance",
  "fetchDetails": false,
  "exportFormat": "none",
  "enableAiAnalysis": false,
  "llmProvider": "openrouter",
  "ollamaBaseUrl": "http://localhost:11434",
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

## `datasetOutput` (type: `string`):

Dataset containing scraped Indeed job postings, multi-query/multi-location compare-mode market comparison reports, AI analysis records, and CSV/Markdown export references.

# 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 = {
    "searchQuery": "software engineer",
    "location": "New York, NY",
    "ollamaBaseUrl": "http://localhost:11434",
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("harvestlab/indeed-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 = {
    "searchQuery": "software engineer",
    "location": "New York, NY",
    "ollamaBaseUrl": "http://localhost:11434",
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("harvestlab/indeed-scraper").call(run_input=run_input)

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

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

```

## CLI example

```bash
echo '{
  "searchQuery": "software engineer",
  "location": "New York, NY",
  "ollamaBaseUrl": "http://localhost:11434",
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call harvestlab/indeed-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=harvestlab/indeed-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "Indeed Salary Matrix — Levels.fyi Alternative + AI",
        "description": "Indeed salaries across 8 countries at $0.003/job — 99%+ run success. RAG-ready jobs + salary matrix for VC diligence, talent-acquisition + comp-benchmarking agents. Geo arbitrage, emerging-skill gaps, AI briefs. Replaces Levels.fyi, Payscale, LinkedIn Talent Insights. MCP-ready, x402-ready.",
        "version": "1.8",
        "x-build-id": "ycrT7WZWtbCcPOfEC"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/harvestlab~indeed-scraper/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-harvestlab-indeed-scraper",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        },
        "/acts/harvestlab~indeed-scraper/runs": {
            "post": {
                "operationId": "runs-sync-harvestlab-indeed-scraper",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor and returns information about the initiated run in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/runsResponseSchema"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/acts/harvestlab~indeed-scraper/run-sync": {
            "post": {
                "operationId": "run-sync-harvestlab-indeed-scraper",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "inputSchema": {
                "type": "object",
                "properties": {
                    "searchQuery": {
                        "title": "Search Query",
                        "type": "string",
                        "description": "Job title or keywords (e.g. \"software engineer\", \"data scientist\", \"marketing manager\")"
                    },
                    "query": {
                        "title": "Search Query (CLI alias)",
                        "type": "string",
                        "description": "CLI alias for searchQuery. Hidden from Console form."
                    },
                    "q": {
                        "title": "Search Query (CLI alias)",
                        "type": "string",
                        "description": "CLI alias for searchQuery. Hidden from Console form."
                    },
                    "search": {
                        "title": "Search Query (CLI alias)",
                        "type": "string",
                        "description": "CLI alias for searchQuery. Hidden from Console form."
                    },
                    "keyword": {
                        "title": "Search Query (CLI alias)",
                        "type": "string",
                        "description": "CLI alias for searchQuery. Hidden from Console form."
                    },
                    "searchTerm": {
                        "title": "Search Query (CLI alias)",
                        "type": "string",
                        "description": "CLI alias for searchQuery. Hidden from Console form."
                    },
                    "jobTitle": {
                        "title": "Search Query (CLI alias)",
                        "type": "string",
                        "description": "CLI alias for searchQuery. Hidden from Console form."
                    },
                    "queries": {
                        "title": "Multi-Query (Compare Mode)",
                        "type": "array",
                        "description": "Optional list of job titles to search in parallel, e.g. [\"data scientist\", \"ML engineer\", \"AI researcher\"]. When provided alongside 'locations', the actor runs N×M searches and generates a single cross-query/cross-location comparison report (charged at $0.10 instead of $0.05). Leave empty to run a single searchQuery.",
                        "items": {
                            "type": "string"
                        }
                    },
                    "locations": {
                        "title": "Multi-Location (Compare Mode)",
                        "type": "array",
                        "description": "Optional list of locations to search in parallel, e.g. [\"New York, NY\", \"San Francisco, CA\", \"Austin, TX\", \"Remote\"]. Combined with 'queries' to run a full N×M grid. Leave empty to use the single 'location' field.",
                        "items": {
                            "type": "string"
                        }
                    },
                    "location": {
                        "title": "Location",
                        "type": "string",
                        "description": "City, state, or \"remote\" (e.g. \"New York, NY\", \"San Francisco, CA\", \"Remote\")"
                    },
                    "city": {
                        "title": "Location (CLI alias)",
                        "type": "string",
                        "description": "CLI alias for location. Hidden from Console form."
                    },
                    "where": {
                        "title": "Location (CLI alias)",
                        "type": "string",
                        "description": "CLI alias for location. Hidden from Console form."
                    },
                    "country": {
                        "title": "Country",
                        "enum": [
                            "us",
                            "uk",
                            "ca",
                            "au",
                            "de",
                            "fr",
                            "nl",
                            "in"
                        ],
                        "type": "string",
                        "description": "Indeed country domain to search (e.g. United States uses indeed.com, United Kingdom uses uk.indeed.com, Germany uses de.indeed.com).",
                        "default": "us"
                    },
                    "maxJobs": {
                        "title": "Max Jobs",
                        "minimum": 1,
                        "maximum": 100,
                        "type": "integer",
                        "description": "Maximum number of job listings to scrape (1-100). Indeed shows ~15 results per page. Start with 25 to test before running larger jobs.",
                        "default": 25
                    },
                    "maxItems": {
                        "title": "Max Items (CLI alias)",
                        "minimum": 1,
                        "maximum": 100,
                        "type": "integer",
                        "description": "CLI alias for maxJobs. Hidden from Console form."
                    },
                    "maxResults": {
                        "title": "Max Results (CLI alias)",
                        "minimum": 1,
                        "maximum": 100,
                        "type": "integer",
                        "description": "CLI alias for maxJobs. Hidden from Console form."
                    },
                    "datePosted": {
                        "title": "Date Posted",
                        "enum": [
                            "any",
                            "1",
                            "3",
                            "7",
                            "14"
                        ],
                        "type": "string",
                        "description": "Filter jobs by how recently they were posted. Useful for monitoring new openings daily/weekly.",
                        "default": "7"
                    },
                    "jobType": {
                        "title": "Job Type",
                        "enum": [
                            "any",
                            "fulltime",
                            "parttime",
                            "contract",
                            "internship",
                            "temporary"
                        ],
                        "type": "string",
                        "description": "Filter by employment type (e.g. Full-time for salaried W-2/permanent roles, Contract for 1099/freelance work, Internship for student positions).",
                        "default": "any"
                    },
                    "experienceLevel": {
                        "title": "Experience Level",
                        "enum": [
                            "any",
                            "entry",
                            "mid",
                            "senior"
                        ],
                        "type": "string",
                        "description": "Filter by required experience: Entry level (0-2 years / new grad), Mid level (3-5 years), Senior level (6+ years / lead roles).",
                        "default": "any"
                    },
                    "salaryMin": {
                        "title": "Minimum Annual Salary",
                        "type": "integer",
                        "description": "Minimum annual salary in the local currency for the selected Country (USD for US, GBP for UK, EUR for DE/FR/NL, CAD for Canada, AUD for Australia, INR for India). Example: 80000 for $80k/year in the US."
                    },
                    "remoteFilter": {
                        "title": "Remote Filter",
                        "enum": [
                            "any",
                            "remote",
                            "hybrid",
                            "in_person"
                        ],
                        "type": "string",
                        "description": "Filter by work arrangement: Remote (fully WFH), Hybrid (mix of office/home), In-person (on-site only).",
                        "default": "any"
                    },
                    "sortBy": {
                        "title": "Sort By",
                        "enum": [
                            "relevance",
                            "date"
                        ],
                        "type": "string",
                        "description": "Result ordering. 'Relevance' (default) ranks by Indeed's match score against your keywords — best for broad keyword searches. 'Date posted' shows newest first — best for daily/weekly monitoring of new openings and avoiding stale listings.",
                        "default": "relevance"
                    },
                    "fetchDetails": {
                        "title": "Backfill Salary From Detail Pages",
                        "type": "boolean",
                        "description": "When enabled, for any job whose list-card didn't render a salary (common for sponsored /pagead ads like nursing roles), fetch the job detail page and try to extract salary from JSON-LD or the page body. Adds ~1 extra request per missing-salary job. Default off to keep runs fast.",
                        "default": false
                    },
                    "exportFormat": {
                        "title": "Compare-Mode Export Artifact",
                        "enum": [
                            "none",
                            "csv",
                            "markdown",
                            "both"
                        ],
                        "type": "string",
                        "description": "When compare mode is active (queries + locations both set), render a ready-to-share salary matrix artifact in addition to the dataset. 'csv' = RFC-4180 salary-matrix spreadsheet (Google Sheets / Tableau / Excel friendly, one row per cell). 'markdown' = README-style executive brief for direct paste into Slack / Notion / email. 'both' = emit both. Each artifact is stored in the run's key-value store as `compare-{run_id}.csv` / `.md` with a public URL included in the dataset report. Charged $0.01 per artifact.",
                        "default": "none"
                    },
                    "artifactWebhookUrl": {
                        "title": "Artifact Webhook URL",
                        "type": "string",
                        "description": "Optional HTTPS webhook to POST the artifact URLs and run metadata after generation — wire it to Slack Incoming Webhook, Zapier, Make, or your own endpoint for autonomous weekly compensation briefings via Apify Scheduler. Webhook receives a JSON body: `{run_id, queries, locations, country, total_jobs, csv_url, markdown_url, message}`. Leave empty to skip. Failure to POST is non-fatal."
                    },
                    "enableAiAnalysis": {
                        "title": "Enable AI Analysis",
                        "type": "boolean",
                        "description": "Generate an AI-powered job market analysis report with salary benchmarking, skill trends, and hiring insights",
                        "default": false
                    },
                    "llmProvider": {
                        "title": "LLM Provider",
                        "enum": [
                            "openrouter",
                            "anthropic",
                            "google",
                            "openai",
                            "ollama"
                        ],
                        "type": "string",
                        "description": "Which AI provider to use for job market analysis. OpenRouter is the cheapest option.",
                        "default": "openrouter"
                    },
                    "llmModel": {
                        "title": "LLM Model",
                        "type": "string",
                        "description": "Specific model to use. Leave empty for the provider default (google/gemini-2.0-flash-001 for OpenRouter, claude-sonnet-4-20250514 for Anthropic, gemini-2.0-flash for Google AI, gpt-4o-mini for OpenAI, llama3.1 for Ollama)."
                    },
                    "openrouterApiKey": {
                        "title": "OpenRouter API Key",
                        "type": "string",
                        "description": "API key from openrouter.ai (required if using OpenRouter provider)"
                    },
                    "anthropicApiKey": {
                        "title": "Anthropic API Key",
                        "type": "string",
                        "description": "API key from console.anthropic.com (required if using Anthropic provider)"
                    },
                    "googleApiKey": {
                        "title": "Google AI API Key",
                        "type": "string",
                        "description": "API key for Google AI (Gemini). Get one at aistudio.google.com/app/apikey"
                    },
                    "openaiApiKey": {
                        "title": "OpenAI API Key",
                        "type": "string",
                        "description": "API key from platform.openai.com (required if using OpenAI provider)"
                    },
                    "ollamaBaseUrl": {
                        "title": "Ollama Base URL",
                        "type": "string",
                        "description": "Base URL for Ollama API. Default: http://localhost:11434"
                    },
                    "proxyConfiguration": {
                        "title": "Proxy Configuration",
                        "type": "object",
                        "description": "Proxy settings. Indeed aggressively blocks datacenter IPs with 403/CAPTCHA walls — RESIDENTIAL (default) is strongly recommended. Leave Apify Proxy enabled unless you're supplying your own custom proxy list."
                    }
                }
            },
            "runsResponseSchema": {
                "type": "object",
                "properties": {
                    "data": {
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "string"
                            },
                            "actId": {
                                "type": "string"
                            },
                            "userId": {
                                "type": "string"
                            },
                            "startedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "finishedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "status": {
                                "type": "string",
                                "example": "READY"
                            },
                            "meta": {
                                "type": "object",
                                "properties": {
                                    "origin": {
                                        "type": "string",
                                        "example": "API"
                                    },
                                    "userAgent": {
                                        "type": "string"
                                    }
                                }
                            },
                            "stats": {
                                "type": "object",
                                "properties": {
                                    "inputBodyLen": {
                                        "type": "integer",
                                        "example": 2000
                                    },
                                    "rebootCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "restartCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "resurrectCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "computeUnits": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "options": {
                                "type": "object",
                                "properties": {
                                    "build": {
                                        "type": "string",
                                        "example": "latest"
                                    },
                                    "timeoutSecs": {
                                        "type": "integer",
                                        "example": 300
                                    },
                                    "memoryMbytes": {
                                        "type": "integer",
                                        "example": 1024
                                    },
                                    "diskMbytes": {
                                        "type": "integer",
                                        "example": 2048
                                    }
                                }
                            },
                            "buildId": {
                                "type": "string"
                            },
                            "defaultKeyValueStoreId": {
                                "type": "string"
                            },
                            "defaultDatasetId": {
                                "type": "string"
                            },
                            "defaultRequestQueueId": {
                                "type": "string"
                            },
                            "buildNumber": {
                                "type": "string",
                                "example": "1.0.0"
                            },
                            "containerUrl": {
                                "type": "string"
                            },
                            "usage": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "integer",
                                        "example": 1
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "usageTotalUsd": {
                                "type": "number",
                                "example": 0.00005
                            },
                            "usageUsd": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "number",
                                        "example": 0.00005
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
