# Indeed Hiring Signals Snapshot (`zinin/indeed-hiring-signals-snapshot`) Actor

Bounded hiring-intelligence snapshots from Indeed search pages with deduplicated job cards, stable keys, free company summaries, and honest terminal sentinels. No detail-page requests or invented URLs.

- **URL**: https://apify.com/zinin/indeed-hiring-signals-snapshot.md
- **Developed by:** [Tim Zinin](https://apify.com/zinin) (community)
- **Categories:** Jobs, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.25 / 1,000 job signal delivereds

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

## Indeed Hiring Signals Snapshot

Bounded search-page data for hiring intelligence, market research, recruiting workflows, and machine-to-machine agents.

### 1. What it does

Enter a job or skill query and a location. The Actor makes a best-effort attempt to open up to three Indeed search pages, reads the cards shown on those pages, removes duplicates, and returns job-card signals plus free company summaries. Indeed may gate pages after the first behind source authentication, so `maxPages` is an upper bound, not a completeness guarantee.

### 2. What you upload

```json
{
  "query": "data analyst",
  "location": "Austin, TX",
  "maxPages": 2,
  "maxItems": 15,
  "proxyConfiguration": {"useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"], "apifyProxyCountry": "US"}
}
```

`query` and `location` are strings. `maxPages` is 1–3 and defaults to 1. `maxItems` is 1–50 and defaults to 15. Only Apify Proxy `RESIDENTIAL` with `apifyProxyCountry: "US"` is accepted; missing or non-US country, lowercase country, arbitrary groups, custom endpoints, and datacenter proxy are rejected before crawling. Proxy bandwidth is billed separately by Apify.

### 3. What you receive

Job rows contain `rowType: "job"`, stable `jobKey`, title, company, location, salary and posted snippets when visible, page, position, source search URL, and a direct `jobUrl` only when the search card itself supplied one. Missing values remain `null`.

Company rows contain `rowType: "company_summary"`, company, delivered job count, locations, and up to five representative titles. They are free.

Each terminal outcome produces exactly one free `rowType: "sentinel"` row. `maxItems`, `maxPages`, `no-progress`, `budget-exhausted`, and source authentication use `status: "limited"` with a matching `action` and `partialReason`. Explicit Indeed no-results uses `status: "empty"` and `action: "stop-no-results"`; an HTTP-200 page with neither cards nor an explicit no-results message is `status: "error"` with `action: "stop-unrecognized-page"`. Blocked, error, and empty paths are not duplicated.

### 4. Simple examples

- A recruiter asks for `"registered nurse"` in `Denver, CO` and receives deduplicated cards from the selected pages.
- A market analyst asks for `"warehouse automation"` in `Dallas, TX`, groups the free company summaries, and feeds stable `jobKey` values into a later workflow.
- An agent sets `maxItems: 5` for a predictable small run.

### 5. Billing

Pricing is pay per event. On the FREE subscription tier, `actor-start` costs $0.005 per allocated GB and each successfully delivered `rowType: "job"` emits `result-found` at $0.005. The default 2 GB run therefore starts at $0.010. BRONZE/SILVER/GOLD/PLATINUM/DIAMOND per-GB start and per-job prices are $0.00475/$0.0045/$0.00425/$0.0041/$0.004. Company summaries, decoys, blocked/empty/error outcomes, and terminal sentinels have no dataset-item event charge. Residential proxy usage is a separate Apify charge. The Actor fails closed if pricing or the run budget cannot be verified.

### 6. Pagination and bounds

Pages use Indeed search offsets 0, 10, and 20. The Actor never requests more than three pages or 50 paid job rows. Pagination is best effort: pages after the first may redirect to Indeed's `secure.indeed.com/auth?...branding=page-two-signin` gate. When the gate's sign-in/create-account copy confirms that more than one page requires login, the Actor emits one free `limited` sentinel with `partialReason: "authentication-required"`, preserves the requested `/jobs?...&start=10` as `sourceSearchUrl`, and stops without incrementing `failedCount`. Reaching `maxItems` prevents another page even when the cap is reached by the final delivered row. A platform money/event limit is reported separately as `budget-exhausted`. Repeated real keys on page two or later produce `no-progress`; a page containing only decoys/unusable cards is `empty`; an unrecognized zero-card HTTP-200 page is an error. A page is not retried automatically.

### 7. Honest data boundary

This is a search-card snapshot, not a full job scraper. Its browser route guard fails closed: only the exact `/jobs` search pages (offsets 0, 10, and 20), approved static resources, and challenge resources are allowed; Indeed detail navigation and detail subrequests (`/viewjob`, `/rc/clk`, mobile/basecamp routes, detail APIs, and `/jobs` URLs carrying a detail selector such as `vjk`, `jk`, `jobId`/`job_id`, `jobKey`/`job_key`, `postingId`, or an equivalent `job`/`posting`/`listing`/`position` identifier) are aborted before they are sent. It never fetches job detail pages, descriptions, applications, contact information, or salary estimates. A URL is never invented from a title. Salary and posted values are snippets, not normalized facts.

### 8. Anti-bot behavior

Indeed challenge pages and HTTP 401/403/429/503 responses produce one free `blocked` sentinel. The exact positive page-two authentication gate produces `stop-auth-required`, not `blocked` or `empty`. Known honeypot/decoy cards are excluded from progress and billing; they do not create per-decoy dataset rows. The Actor does not solve challenges or click through them.

### 9. Output contract

The stable fields are `rowType`, `jobKey`, `title`, `company`, `jobLocation`, `salary`, `posted`, `jobUrl`, `page`, `position`, `sourceSearchUrl`, `status`, `partial`, and `partialReason`. Company summary rows also include `companyJobCount` and `representativeTitles`. The OUTPUT key-value record contains page count, paid/free counts, decoy count, `blockedDetailAttempts`, stop reason, replay safety, and fatal-error state. Authentication-gated pagination sets `stopReason: "authentication-required"`, `fatalError: false`, and leaves `failedCount` unchanged.

### 10. Machine-to-machine use

The flat JSON rows are designed for n8n, Make, MCP tools, scheduled enrichment, and agent pipelines. Use `rowType` as the router, `jobKey` as the idempotency key, `status` as the health signal, and `partialReason` before acting on incomplete data.

### 11. Recommended workflow

Use this Actor to discover current hiring signals, then pass selected companies or stable keys to your own verification/enrichment workflow. Do not treat a search card as proof that a vacancy is still open without a separate permitted verification step.

### 12. Related zinin Actors

Pair with `company-hiring-radar` for broader company hiring signals, `hiring-trend-index` for trend aggregation, `job-postings-aggregator` for multi-board comparison, and `job-alert` for recurring monitoring. These are complementary, not hidden dependencies; each must be run separately.

### 13. Privacy and safety

Do not submit personal data or secrets as query/location text. Inputs are encoded as search parameters. Logs contain counts and states, never proxy credentials or API keys.

### 14. Limitations and support

Indeed markup, availability, ranking, and anti-bot behavior can change. Results reflect only what was visible on the requested search pages at run time. For reproducible automation, save the input, OUTPUT summary, timestamp, and dataset export together.

### 15. Public task

The included public task searches `data analyst` in Austin with at most one page and ten paid rows. At the FREE subscription tier and this Actor's 2 GB memory setting, its maximum event charge is $0.06: up to two $0.005 start units (Apify counts one per GB) plus at most ten $0.005 delivered jobs. Residential proxy usage is billed separately; summaries and sentinels do not incur dataset-item charges.

# Actor input Schema

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

Job title, skill, or hiring keyword for Indeed's search box.

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

City, state, country, or remote location. Leave empty for a broad search.

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

Caps how many billed job rows this run delivers -- not the array-length runtime guardrail (this Actor has no array input to cap; it was legitimately exempted from that at first ship), a buyer-facing row cap for a predictable bill on a source whose real card count (15-17 typically) is otherwise fixed by the page, not by you. Default 15 is close to a full page's real yield, so most runs return everything the page has; raise it for headroom on a wider query, lower it for a stricter ceiling. Enforced on the charge itself: once this many rows are billed, further real cards on the page are read but never charged for or delivered, and the run's OUTPUT/summary row says so plainly (status 'limited') rather than reporting the page as empty.

## `maxPages` (type: `integer`):

Best-effort attempt to fetch up to three bounded Indeed search pages using start offsets. Indeed may require source sign-in after page one; the Actor then returns a free authentication-required sentinel instead of bypassing login. Stops earlier on no results, an unrecognized page, or repeated cards.

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

US residential Apify Proxy is required for reliable Indeed search access. Other countries, groups, custom endpoints, and datacenter proxies are rejected. Proxy bandwidth is billed separately by Apify.

## Actor input object example

```json
{
  "query": "software engineer",
  "location": "Austin, TX",
  "maxItems": 15,
  "maxPages": 1,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}
```

# Actor output Schema

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

API URL for the default dataset items.

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

Pages, requested and delivered counts, paid/free counts, stop reason, decoy and blocked-detail-request counts, replay safety, and fatal error state.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "query": "software engineer",
    "location": "Austin, TX",
    "maxItems": 15,
    "maxPages": 1,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "US"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("zinin/indeed-hiring-signals-snapshot").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "query": "software engineer",
    "location": "Austin, TX",
    "maxItems": 15,
    "maxPages": 1,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "US",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("zinin/indeed-hiring-signals-snapshot").call(run_input=run_input)

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

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

```

## CLI example

```bash
echo '{
  "query": "software engineer",
  "location": "Austin, TX",
  "maxItems": 15,
  "maxPages": 1,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}' |
apify call zinin/indeed-hiring-signals-snapshot --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,zinin/indeed-hiring-signals-snapshot"
        }
    }
}

```

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/FCZauN8HVoAgw7Jm8/builds/r07eqW9418SVWYrtM/openapi.json
