# Foundit Scraper (`mlg14/foundit-scraper`) Actor

Search public Foundit jobs by keyword, city, experience, salary and freshness. Export employer, skills, pay, dates, applicant counts and job links.

- **URL**: https://apify.com/mlg14/foundit-scraper.md
- **Developed by:** [MLG Data](https://apify.com/mlg14) (community)
- **Categories:** Jobs, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$1.00 / 1,000 results

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

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

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Foundit Scraper

Scrape Foundit job listings by keyword, city, experience, salary, employment type, and posting date. Export Foundit jobs as structured JSON, CSV, or spreadsheet data with employer, skills, compensation, applicant count, and application links. This Foundit search data source is useful when a public job-search feed is needed without collecting candidate profiles.

The scraper reads public search results and produces one row per unique job. A verified run returned 50 jobs for a Bangalore search with experience and freshness filters. You can search broadly to map an employment market or narrowly to watch a role in one location.

### What data can you extract from Foundit?

Each dataset row represents one job ID. The fields are flat so they can be sorted, filtered, or joined to another table without unpacking nested objects. Values that the listing does not disclose are `null`; label arrays can be empty. The values shown below are examples of formats, not promises that every listing supplies every field.

| Field | Description | Example |
| --- | --- | --- |
| `jobId` | Stable identifier used for deduplication | `68781817` |
| `title` | Job title shown in search results | `Senior Software Engineer` |
| `companyName` | Listed employer or recruiter name | Varies by listing |
| `companyId` | Site identifier for the listed company | `1249584` |
| `companyUrl` | Public company search page, if provided | Foundit search URL |
| `location` | Location text on the listing | `Bengaluru, India` |
| `experienceMinYears` | Lower experience bound | `6` |
| `experienceMaxYears` | Upper experience bound | `8` |
| `salaryConfidential` | Whether the listing explicitly marks salary confidential | `false` |
| `salaryDisclosed` | Whether a numeric salary bound exists | `false` |
| `salaryMinLakhs` | Lower annual pay in INR lakhs, if disclosed | `9.5` |
| `salaryMaxLakhs` | Upper annual pay in INR lakhs, if disclosed | `14` |
| `salaryCurrency` | Salary currency code | `INR` |
| `employmentType` | First employment label on the listing | `Full time` |
| `industries` | Industry labels | `[]` |
| `functions` | Functional area labels | `Data Science` |
| `skills` | Listed skills, split into an array | `[]` |
| `postedDateText` | Site's relative posting-age label | `a day ago` |
| `postedDaysAgo` | Approximate whole-day age | `1` |
| `postedAt` | Source posting timestamp in UTC | `2026-09-24T18:09:39Z` |
| `expiresAt` | Source closing timestamp in UTC, if supplied | `2026-11-07T18:30:00Z` |
| `totalApplicants` | Applicant count reported by the site | `1` |
| `isUrgentlyHiring` | Urgent-hiring flag | `false` |
| `applyUrl` | Public Foundit job URL | Foundit job URL |
| `externalApplyUrl` | External application URL, when supplied | `null` |
| `sourceKeyword` | First input keyword that returned the ID | `python` |
| `scrapedAt` | UTC capture time for this row | `2026-09-26T11:51:52Z` |

Salary has two separate indicators for a reason. Some search rows say `salaryConfidential: false` but still contain zero for both numeric salary bounds. Those rows have `salaryDisclosed: false` and null lakh amounts. A zero salary is never presented as a genuine pay offer. The experience bounds likewise reflect the source listing; `0` can be a placeholder rather than an explicit entry-level requirement. These distinctions matter when calculating pay or experience distributions.

The job URL always points to the public listing when the source supplies a path. The external application URL is a separate field because some employers direct applicants to a different destination. Keeping both fields lets a workflow show the listing for context while routing an interested applicant to the supplied application page.

### How to scrape Foundit jobs

1. Enter one or more search terms in `searchKeywords`. A job title, a skill, or a short function name works best.
2. Add a city or region in `location`, or leave it empty for a broader search. Set only the experience, pay, type, and age filters needed for your question.
3. Set `maxJobs` to the number of unique listings you need. The default is 100 and the per-run maximum is 1,000.
4. Start the run and inspect the Jobs dataset. Download its rows as JSON, CSV, or a spreadsheet file, or read the dataset through the platform API.

A search with several keywords processes them in the order entered. Repeated IDs are included once, with `sourceKeyword` set to the first term that found them. When the cap is reached, later keywords are not searched. Put the most important term first, or use separate runs when equal coverage per keyword matters.

### Input

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `searchKeywords` | String list | Required | Terms searched separately, in order |
| `location` | String | Empty | City, region, or comma-separated places; empty searches all locations |
| `experienceMinYears` | Integer | Unset | Lower bound of the site's experience facet |
| `experienceMaxYears` | Integer | Unset | Upper bound of the site's experience facet |
| `salaryMinLakhs` | Number | Unset | Lower annual salary bound in INR lakhs |
| `salaryMaxLakhs` | Number | Unset | Upper annual salary bound in INR lakhs |
| `employmentType` | Choice | Any | Full time, part time, or both |
| `postedWithinDays` | Choice | Any date | Listings posted within 1, 3, 7, 15, or 30 days |
| `maxJobs` | Integer | 100 | Maximum unique rows, up to 1,000 |
| `proxyConfiguration` | Proxy setting | Enabled | Starts on a datacenter route and escalates after blocks |

For example, this input looks for recent listings in Bangalore with an experience range:

```json
{
  "searchKeywords": ["python"],
  "location": "Bangalore",
  "experienceMinYears": 2,
  "experienceMaxYears": 6,
  "postedWithinDays": "7",
  "maxJobs": 50
}
```

This exact input produced 50 unique rows in a verified run. The experience range is passed to Foundit's search facet. It is a match range used by the site, so a returned listing may show a wider minimum-to-maximum band that overlaps your range. Use the output bounds if your downstream rule requires strict containment.

Salary filters are also site-side facets. Enter lakhs, not absolute rupees: `5` means ₹500,000 annually. A tight pay range can dramatically shrink a result set because many listings do not provide numeric compensation. For a general jobs feed, leave both salary inputs empty and check `salaryDisclosed` in the output.

### Output example

The following fields come from one job row in the verified 50-row run. Other keys were omitted here for readability; the field table above lists the complete output shape.

```json
{
  "jobId": "68781817",
  "title": "Senior Software Engineer",
  "location": "Bengaluru, India",
  "experienceMinYears": 5,
  "experienceMaxYears": 7,
  "salaryConfidential": false,
  "salaryDisclosed": false,
  "salaryMinLakhs": null,
  "salaryMaxLakhs": null,
  "employmentType": "Full time",
  "postedDateText": "18 hours ago",
  "postedDaysAgo": 0,
  "postedAt": "2026-09-25T17:37:10Z",
  "totalApplicants": 18,
  "isUrgentlyHiring": false,
  "sourceKeyword": "python",
  "scrapedAt": "2026-09-26T11:51:55Z"
}
```

Every job has its own dataset row; advertising and banner entries mixed into the site's search payload are ignored. A repeated job found under two terms is not billed twice in the same run. The dataset contains jobs only, with no trailing summary row to remove before analysis.

### Use cases

- **Recruitment coverage:** Build a list of currently visible roles for a function and city, then review employer, skills, experience, and direct job links in one table.
- **Salary research:** Filter to rows with `salaryDisclosed: true` and compare the disclosed lakh range against required experience. Retain null salaries as missing data, rather than treating them as zero.
- **Hiring trend tracking:** Run the same search on a schedule, compare `jobId` values across datasets, and count newly seen openings by location or function.
- **Skills analysis:** Count listed skill tags across a role family. Use `functions` and `industries` to split a broad search into meaningful segments.
- **Job alerts:** Feed recent listings into a digest or internal review queue, using `postedAt` and `applyUrl` to prioritize current opportunities.
- **Application routing:** Show the public listing as context and keep `externalApplyUrl` available when the employer supplied a separate application destination.

### How much does it cost to scrape Foundit?

The current result-event price is **$1.00 per 1,000 delivered jobs**, or $0.001 for each dataset row. A 50-job run has $0.05 in result events; 500 jobs have $0.50; and the 1,000-job per-run cap has $1.00. The price shown for this actor at run time is authoritative if pricing changes later.

The `maxJobs` setting controls the largest possible result-event charge for one run. A query that finds fewer jobs produces fewer events. Banner rows and duplicate job IDs are skipped before insertion, so they do not create result events. A search blocked before any row is delivered has no result events. Normal platform usage for the actor is included in the result price; additional costs for an external system that consumes your exported data are outside this actor.

Start with a small cap while checking which filters yield useful results. A narrow job title in one city may return far fewer rows than a skill term over the same period. The verified 50-job example used a broad term plus a city and two facets; a more restrictive title returned only 13 jobs under the same facets. That difference reflects the live search inventory, not a fixed quota.

### Tips for best results

Use a short, meaningful keyword before adding filters. If the search is too broad, first choose a location or freshness period. Add experience and salary only if those dimensions are essential. Because the site decides which listings match each facet, compare the returned bounds to your exact reporting rule before publishing a metric.

For multiple places, a comma-separated location can be sent to the site's location facet. If you need separate counts by city, use separate runs: the `location` field in a row is the listing's own text, and a combined search can include jobs matching more than one place. Likewise, separate keyword runs are better when each term needs its own independent maximum.

For larger harvests, start with `maxJobs` between 100 and 500 and inspect the freshness and employer mix. The scraper requests up to 100 jobs per page, then follows the site's next cursor. It stops when it reaches your cap, runs out of pages, sees repeated pages without new IDs, or reaches the observed search-offset boundary. Narrowing a broad query by city, posting period, or skill creates smaller slices with less overlap.

Keep both `postedDateText` and `postedAt` when building time-based reports. The text is what the site displayed at collection time; the timestamp is better for sorting and joining to other dated data. `postedDaysAgo` is an approximate whole-day interpretation and can differ from a calendar-day calculation near midnight.

### Limits

Full job descriptions are not included. The public search response supplies structured metadata but no description body. The job-detail pages rejected anonymous requests during validation, and the tested detail JSON paths did not supply data. The actor therefore reports the search fields it can verify without claiming to enrich a listing with a full description.

The site can change its search response or block a route. During validation, ordinary search and detail pages returned 403. The structured search endpoint worked after the connection moved to a residential route. Each actor run begins with the lower-cost datacenter route, rotates after repeated blocks, and escalates only when required. If the structured endpoint itself becomes unavailable, the run fails visibly instead of returning a misleading empty dataset.

Foundit's response mixes real jobs with non-job display entries. Only rows with both an ID and a title are emitted. The site also reports a large total count on broad queries, but deep offsets showed a boundary near 9,500; this actor stops at that observed offset. The maximum output is 1,000 unique jobs per run. For wider coverage, create narrower queries and deduplicate their datasets by `jobId`.

Company, skills, salary, applicant count, dates, and external links depend on what each listing supplies. Some listings show an experience placeholder of zero, some have no pay numbers, and external application pages can expire or redirect. The scraper does not verify an employer's external destination or infer missing details.

### Automated searches

A scheduled run can refresh the same job search daily or weekly. To identify newly visible roles, compare `jobId` from the new dataset against your earlier saved dataset. This actor deduplicates within each run; it does not maintain a persistent seen-ID list across runs. Keeping the previous ID set in your own workflow gives you full control over retention and alert rules.

Plain-language requests for an automated workflow might be: “Collect the latest 100 analyst openings in Bangalore, then show the roles with disclosed pay,” or “Find 50 sales openings posted in the last seven days and group them by function.” These requests map directly to the input fields and output columns above. Check the final dataset for null salaries before calculating averages.

### FAQ

**Does this require a Foundit account?** No. The actor reads public job-search data. It does not request a login or access candidate profiles.

**Do I need to configure proxies?** The default setting handles the route used in validation. It tries a datacenter connection first and moves to a residential connection after repeated blocks. Turning proxy use off can make a blocked search fail.

**How fast is a run?** The verified 50-job run completed in about 16 seconds after startup. More pages, proxy retries, and site response times can increase that duration; no fixed completion time is guaranteed.

**Can I schedule this?** Yes. Save the input as a recurring task through the platform scheduler. Each scheduled run produces a fresh dataset. Compare IDs between runs if you want only new openings.

**Can I export the results to a spreadsheet?** Yes. Download the dataset in CSV or spreadsheet format, or retrieve JSON for further processing. Arrays such as `skills` remain structured in JSON and may appear as serialized values in flat exports.

**Why are salary fields empty?** The source often supplies no numeric pay range. `salaryDisclosed` is the reliable indicator for whether at least one bound was returned; a false `salaryConfidential` flag alone does not mean the pay numbers are available.

**Why do experience results extend beyond my range?** The site's experience facet can include overlapping listing bands. The output preserves the listing's stated minimum and maximum, allowing you to apply a stricter rule afterward if needed.

**Is collecting these listings legal?** The actor only accesses publicly visible job-search records. Review the site's terms and applicable privacy and data rules for your use case, particularly before redistributing listings or combining them with personal information.

### Integrations

The default dataset can be read through the platform API or sent to a webhook after a run. It can also be exported to a spreadsheet or loaded into an internal database. Use `jobId` as the stable key when merging repeated searches, `scrapedAt` to track when a row was captured, and `sourceKeyword` to explain which search first surfaced it. A scheduled task and a downstream ID comparison form a simple repeatable monitoring workflow.

### Support

Open an issue on the Issues tab with the input that produced an unexpected result and the affected job ID. Include which field or filter you expected to behave differently. We reply within 24 hours and add fields on request when the public source provides them.

# Actor input Schema

## `searchKeywords` (type: `array`):

One or more job titles or skills. Searches run in order and duplicate job IDs are removed.

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

City or comma-separated cities. Leave empty to search all locations.

## `experienceMinYears` (type: `integer`):

Lower end of the experience range in years.

## `experienceMaxYears` (type: `integer`):

Upper end of the experience range in years.

## `salaryMinLakhs` (type: `number`):

Lower end of the salary range in INR lakhs. Listings with undisclosed pay may be excluded.

## `salaryMaxLakhs` (type: `number`):

Upper end of the salary range in INR lakhs.

## `employmentType` (type: `string`):

Return full-time or part-time listings. Leave empty for both.

## `postedWithinDays` (type: `string`):

Show listings posted in the selected number of days. Leave empty for any date.

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

Maximum unique jobs in the dataset across all keywords.

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

Use the default datacenter route first. Blocked requests retry with a rotated session and residential route.

## Actor input object example

```json
{
  "searchKeywords": [
    "python developer"
  ],
  "location": "Bangalore",
  "employmentType": "",
  "postedWithinDays": "",
  "maxJobs": 100,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `jobs` (type: `string`):

Unique public job listings 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 = {
    "searchKeywords": [
        "python developer"
    ],
    "location": "Bangalore"
};

// Run the Actor and wait for it to finish
const run = await client.actor("mlg14/foundit-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 = {
    "searchKeywords": ["python developer"],
    "location": "Bangalore",
}

# Run the Actor and wait for it to finish
run = client.actor("mlg14/foundit-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 '{
  "searchKeywords": [
    "python developer"
  ],
  "location": "Bangalore"
}' |
apify call mlg14/foundit-scraper --silent --output-dataset

```

## MCP server setup

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