# Foundit jobs across India, Singapore, Malaysia & Indonesia (`memo23/foundit-jobs-scraper`) Actor

Scrape foundit (Monster India) jobs across India, Singapore, Malaysia and Indonesia — title, company, salary band, experience, skills, map coordinates, full description and apply URL. Straight off foundit's JSON API: no HTML parsing, no proxy. Native vs syndicated flagged per row.

- **URL**: https://apify.com/memo23/foundit-jobs-scraper.md
- **Developed by:** [Muhamed Didovic](https://apify.com/memo23) (community)
- **Categories:** Jobs, Automation, AI
- **Stats:** 17 total users, 15 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: 5.00 out of 5 stars

## Pricing

from $5.99 / 1,000 job 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/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

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

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

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

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

# README

## Foundit Jobs Scraper — India, Singapore, Malaysia & Indonesia

Scrape job postings from **foundit** (formerly Monster India) straight off its own JSON API — no HTML parsing, no browser, no proxy. Paste any search URL, SEO landing page, or direct job URL and get back **68 structured fields per job**: title, company, salary band, experience range, skills, industries, map coordinates, the full job description in both HTML and plain text, and the apply URL. Covers **all four regional sites** — `foundit.in`, `foundit.sg`, `foundit.my`, `foundit.id` — each of which runs a **separate job pool**, so you can pull four countries in one run.

![How the Foundit Jobs Scraper works](https://raw.githubusercontent.com/muhamed-didovic/muhamed-didovic.github.io/main/assets/how-it-works-foundit.png)

### Why this actor

foundit's search page is client-rendered and its public job pages are Akamai-blocked (`403 Access Denied`), so HTML scrapers either return empty shells or get shut out. This actor talks to the same JSON endpoints the site's own front-end uses.

- **100% JSON, zero HTML** — `/middleware/jobsearch` for listings, `/middleware/jobdetail/{id}` for descriptions. Nothing to break when foundit reskins its front-end.
- **No proxy needed, none forced on you** — the API answers `200 OK` direct from every region. Proxy support is there if you need a specific egress, but it's **off by default**, so you don't burn residential bandwidth on a site that never asks for it.
- **Four regions, one run** — India, Singapore, Malaysia and Indonesia are genuinely separate inventories (sampling the same query across all four found **1 shared posting in 200**). Pages are pulled **round-robin**, so four start URLs give you four countries rather than 1,000 rows from whichever one you listed first.
- **Native vs syndicated, flagged on every row** — foundit republishes a large share of its listings from other boards. Every row carries `isSyndicated` plus `applyHost`, and you can filter them out at the source with `excludeSyndicated`.
- **Promoted-slot deduplication** — a request for 50 rows comes back with ~62, because foundit injects sponsored listings that repeat across pages. Rows are deduplicated per region before anything is billed.
- **Filters run before fetching** — recency and syndication are evaluated on the listing row, so a filtered-out job never consumes your `maxItems` budget or triggers a detail request.
- **Real map coordinates** — latitude/longitude come from the detail payload, not a geocoding guess.

### Use cases

- **Job-board aggregation** — build an India/SEA jobs feed with `isSyndicated` to avoid re-importing postings you already ingest from LinkedIn or an ATS
- **Salary benchmarking** — structured `salary` objects with annual *and* monthly INR/SGD/MYR/IDR bands, paired with `experienceMin`/`experienceMax` and `functions`
- **Recruitment market research** — `totalApplicants` and `views` per posting show real demand, not just supply
- **Skills demand tracking** — normalized `skills[]` per role, region and industry
- **Competitor hiring intelligence** — filter by `companyName` / `companyId` and watch posting velocity via `postedDate`
- **Geo analysis** — `coordinates` plus a structured `locations[]` array (city, state, country) for multi-location postings
- **LLM pipelines** — `descriptionText` gives clean plain text with bullets and paragraphs preserved, ready to embed without stripping markup yourself

### Input

| Field | Type | Required | Notes |
|---|---|---|---|
| `startUrls` | `string[]` | yes | Any mix of foundit search URLs (`/srp/results?query=…&locations=…`), SEO landing pages (`/search/php-developer-jobs-in-pune`), and direct job URLs (`/job/{slug}-{id}`), across all four regional domains. Build a search on the site, apply filters, copy the address bar — **every filter in the URL is passed through to the API**. |
| `fetchDescriptions` | `boolean` | no | Fetch each job's detail endpoint for the full `description`, `descriptionText` and `coordinates`. Costs one extra API call per job. Turn off for a roughly **2× faster run** when listing fields are enough. Default `true`. |
| `excludeSyndicated` | `boolean` | no | Keep only foundit-native postings, dropping ones republished from other boards. See the syndication note below — this materially changes salary coverage. Default `false`. |
| `postedWithinHours` | `integer` | no | Only jobs posted within the last N hours. `24` = last day, `72` = last 3 days. Empty/`0` = everything. Evaluated on the listing row, so filtered jobs cost you nothing. |
| `enrichEmails` | `boolean` | no | Experimental. Attempts to discover an employer contact email from the company name. Adds `contactEmail` / `contactWebsite` / `emailEnrichment`. Billed only when an email is found. Default `false`. |
| `maxItems` | `integer` | no | Hard cap on rows collected. Default `1000`. Free-tier runs are capped at `100`. |
| `maxConcurrency` | `integer` | no | Parallel detail requests. Default `10`. |
| `proxy` | object | no | **Not needed.** Off by default. Only set it if you require a specific egress IP or country. |

#### Example input

```json
{
  "startUrls": [
    "https://www.foundit.in/srp/results?query=software%20engineer&locations=Bangalore",
    "https://www.foundit.sg/srp/results?query=engineer",
    "https://www.foundit.my/srp/results?query=engineer",
    "https://www.foundit.id/srp/results?query=engineer"
  ],
  "fetchDescriptions": true,
  "excludeSyndicated": false,
  "postedWithinHours": 24,
  "maxItems": 1000
}
```

Four regions, cycled a page at a time, capped at 1,000 rows total and limited to postings from the last day.

### Native vs syndicated — read this before setting `excludeSyndicated`

foundit carries two kinds of listing, and the difference decides what data you actually get:

| | Native (`jobSource: "ORGANIC"`) | Syndicated (`jobSource: "SCRAPPING"`) |
|---|---|---|
| `applyUrl` | The foundit job page | Off-site — LinkedIn, the employer's ATS, another board |
| `applyType` | `"internal"` | `"external"` |
| Salary disclosed | Often | Rarely |
| `totalApplicants` / `views` | Real engagement numbers | Usually `0` |

**Syndication rate varies enormously by query**, and salary coverage moves inversely with it. Measured across four live searches:

| Query | Syndicated | Has salary |
|---|---|---|
| Pune (no keyword) | 18% | **80%** |
| nurse · Delhi | 63% | 33% |
| sales manager · Chennai | 94% | 4% |
| accountant · Mumbai | 96% | **2%** |

So: if you need **salary data**, set `excludeSyndicated: true`. If you need **maximum coverage**, leave it `false` and filter later — the flag is on every row either way.

⚠️ On a heavily syndicated query, `excludeSyndicated: true` can legitimately return **very few or zero rows**. The actor paginates deeper to try to fill your `maxItems` and fails loudly rather than handing you a silent empty dataset.

### Output

One row per job. 68 fields, all flat except `salary`, `coordinates` and `locations`.

#### Sample — syndicated posting

```json
{
  "jobId": "62580094",
  "title": "MuleSoft Support Engineer",
  "jobUrl": "https://www.foundit.in/job/mulesoft-support-engineer-ntt-data-pune-india-62580094",
  "companyName": "NTT Data",
  "companyId": 510094,
  "location": "Pune, Maharashtra, India",
  "coordinates": { "latitude": 18.52043, "longitude": 73.856744 },
  "country": "India",
  "region": "India",
  "sourceSite": "www.foundit.in",
  "salary": null,
  "experienceMin": 4,
  "experienceMax": 6,
  "experienceRaw": "4-6 Years",
  "industries": ["IT/Computers - Software"],
  "functions": ["Information Technology"],
  "employmentTypes": ["Full time"],
  "skills": ["Servicenow", "Sql Basics", "Json", "Rabbitmq", "Xml", "Rest Apis"],
  "postedDate": "2026-08-14T00:01:50.000Z",
  "closingDate": "2026-09-27T13:00:00.000Z",
  "applyType": "external",
  "applyUrl": "https://careers-inc.nttdata.com/job/Pune-MuleSoft-Support-Engineer-MH/1419001700/",
  "applyHost": "careers-inc.nttdata.com",
  "isSyndicated": true,
  "jobSource": "SCRAPPING",
  "totalApplicants": 1,
  "views": 3
}
```

#### Sample — native posting (with salary)

```json
{
  "jobId": "62628690",
  "title": "Sap Fico Consultant",
  "companyName": null,
  "companyConfidential": true,
  "location": "Hyderabad / Secunderabad, Telangana, India | Mumbai, Maharashtra, India | Pune, Maharashtra, India",
  "salary": {
    "currency": "INR",
    "min": 450000,
    "max": 550000,
    "minMonthly": 37500,
    "maxMonthly": 45833,
    "raw": "450,000-550,000"
  },
  "skills": ["Sap Fico", "SAP Integration"],
  "applyType": "internal",
  "applyUrl": "https://www.foundit.in/job/sap-fico-consultant-hyderabad-secunderabad-telangana-mumbai-pune-62628690",
  "applyHost": "foundit.in",
  "isSyndicated": false,
  "jobSource": "ORGANIC",
  "totalApplicants": 115,
  "views": 129
}
```

#### Key output fields

| Field | Type | Notes |
|---|---|---|
| `jobId` | string | Unique **per region only** — the four pools have independent ID spaces. Key on `sourceSite` + `jobId`. |
| `title`, `jobUrl` | string | Always populated. |
| `companyName` | string | null | `null` when the employer posted confidentially; `companyConfidential` tells you which. |
| `salary` | object | null | `{ currency, min, max, minMonthly, maxMonthly, raw }`. **`null` when undisclosed** — foundit writes `0-0` for "not disclosed" and that is deliberately not published as a real ₹0 band. |
| `experienceMin` / `experienceMax` | number | null | Years. `null` when unspecified — again, foundit's `0–0` marker is not published as a real entry-level band. |
| `location` | string | Human-readable, joined across multi-location postings. |
| `locations` | object\[] | `{ city, state, country, countryCode }` per location. |
| `coordinates` | object | null | `{ latitude, longitude }`. Requires `fetchDescriptions: true`. Absent on fully-remote postings with no city. |
| `description` | string | Full job description, original HTML. Requires `fetchDescriptions: true`. |
| `descriptionText` | string | Same content as clean plain text — bullets and paragraph breaks preserved, entities decoded. Ready for LLMs and CSV. |
| `skills` | string\[] | Normalized and de-duplicated. |
| `postedDate` / `closingDate` / `updatedDate` | ISO 8601 | |
| `applyUrl` / `applyType` / `applyHost` | string | `internal` → foundit page; `external` → off-site. `applyHost` is the bare hostname for grouping. |
| `isSyndicated` / `jobSource` / `jobClassification` | boolean / string | Provenance. See the section above. |
| `totalApplicants` / `views` | number | Real engagement, meaningful mostly on native postings. |
| `region` / `sourceSite` / `country` / `countryCode` | string | Which regional site the row came from, and where the job is. |

#### Fields that are always empty for foundit

These belong to the shared job-row schema used across this actor family, and foundit simply doesn't publish them. They are present for schema stability, never populated:

`applyEmail`, `categories`, `contractType`, `externalApplyUrl`, `hours`, `modifiedDate`, `source`

`companyWebsite` and `companyDomain` populate **only** when `enrichEmails` is enabled.

### How it works

1. Each start URL is classified — search page, SEO landing page, or direct job URL — and mapped to its regional host.
2. Listings are paged through `/middleware/jobsearch?start=N&limit=50`, **round-robin across every start URL** so no single region monopolizes the run.
3. Each page is deduplicated (foundit injects repeating promoted slots), then screened against `excludeSyndicated` and `postedWithinHours` **before** anything is fetched or billed.
4. Surviving jobs hit `/middleware/jobdetail/{id}` in parallel for the full description and coordinates.
5. The listing and detail payloads are merged field-by-field — the two endpoints disagree about which fields they populate, and in several cases about a field's *type*, so the merge is shape-aware rather than a blind overwrite.
6. Rows are pushed as they complete; pagination continues while details are still in flight.

### Notes & limitations

- **Region ID collision** — `jobId` is unique per regional pool, not globally. Always key on `sourceSite` + `jobId`.
- **`foundit.ph` is not supported.** The Philippines site sits behind a JavaScript/JWT redirect challenge the other four don't use. It is deliberately excluded rather than worked around.
- **`foundit.ae`, `foundit.com.hk`, `foundit.vn` no longer resolve** — those markets appear to be closed.
- **Deep pagination is real but finite.** foundit serves distinct results well past `start=900`; when a page returns only already-seen IDs, that region stops.
- **Descriptions cost an API call each.** With `fetchDescriptions: false` you keep every listing field and lose only `description`, `descriptionText` and `coordinates`.
- **Empty runs fail loudly.** If nothing is scraped, the run errors with the likely cause rather than silently succeeding with an empty dataset.

### FAQ

**Do I need a proxy?**
No. foundit's API answers directly from every region with no anti-bot challenge. Proxy support exists for callers with egress requirements, but it is off by default.

**Why did `excludeSyndicated: true` return almost nothing?**
Your query is probably in a heavily syndicated segment — some return 96% syndicated. Try a broader location-only search, or leave the filter off and use the `isSyndicated` field afterwards.

**Why is `salary` null on most rows?**
Because foundit doesn't disclose it on most syndicated postings. Salary coverage rises sharply on native ones — see the syndication table above.

**Can I scrape all four countries at once?**
Yes. Put one URL per region in `startUrls`; pages are cycled round-robin so all four are represented.

**Can I filter by salary, experience, or industry?**
Apply those filters on foundit's own search page and paste the resulting URL — every query parameter is forwarded to the API.

**How do I get only today's jobs?**
Set `postedWithinHours: 24`.

**Is the job description HTML or text?**
Both. `description` keeps the original HTML; `descriptionText` is the cleaned plain-text version.

### Support

Found a bug or need a field that isn't here? Open an issue on the actor's **Issues** tab and I'll take a look.

### 🤖 For AI Agents & LLM Apps

This actor is MCP-friendly and returns flat, self-describing rows. For retrieval and embedding pipelines use `descriptionText` rather than `description` — it's already stripped of markup with structure intact. Deduplicate on `sourceSite` + `jobId`, never `jobId` alone. `isSyndicated` is the single most useful filter for building a clean feed: syndicated rows point off-site and usually lack salary.

### ⚠️ Disclaimer

This actor collects only publicly available job-posting data from foundit's public endpoints. It does not access private, authenticated, or personal data, and it does not bypass any authentication or bot-protection mechanism. Users are responsible for ensuring their use complies with foundit's Terms of Service, applicable data-protection law (including the DPDP Act and GDPR where relevant), and any contractual obligations. This actor is not affiliated with, endorsed by, or connected to foundit, Monster, or Quess Corp.

### SEO Keywords

foundit scraper, monster india scraper, foundit.in scraper, foundit jobs api, india job scraper, naukri alternative scraper, indian job board scraper, singapore jobs scraper, malaysia jobs scraper, indonesia jobs scraper, southeast asia jobs scraper, job listings scraper, job posting api, salary data scraper india, recruitment data scraper, hiring data api, job aggregator feed, ATS job feed, job market intelligence, foundit sg scraper, foundit my scraper, foundit id scraper

# Actor input Schema

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

Full foundit URLs to crawl. Build a search on the site, apply your filters, then copy the address bar — every filter in the URL is passed through to the API.

## `fetchDescriptions` (type: `boolean`):

When enabled, each job additionally fetches its detail endpoint to add the full HTML `description` and `coordinates` (latitude/longitude). Turn this off for a roughly 2x faster run when the listing fields are enough.

## `excludeSyndicated` (type: `boolean`):

Foundit republishes a large share of its inventory from other boards. Those rows are marked `isSyndicated: true`, almost never carry a salary, and their `applyUrl` points off-site (LinkedIn, the employer's ATS). Enable this to keep only foundit-native postings — the subset with the best salary coverage and a clean apply funnel. Every row carries the flag either way, so you can also filter after the run.

## `postedWithinHours` (type: `integer`):

Filters on each job's posted date. Set 24 for the last day, 72 for the last 3 days, etc. Leave empty (or 0) to return everything regardless of age.

## `enrichEmails` (type: `boolean`):

If enabled, tries to find a contact email for each employer by discovering the company's website (Clearbit) and reading its contact/about pages. Adds contactEmail + contactWebsite columns plus a detailed emailEnrichment object. Best-effort, billed per contact email found; only charged when an email is returned, never for misses.

## `hiringMarketAnalysis` (type: `boolean`):

If enabled, appends a `rowType: "hiringMarketAnalysis"` row per (role, region) group found in your results (up to 8 groups per run, largest first; groups need at least 3 postings). Every number in the row comes verbatim from the code-side aggregation of this run's rows — the AI only narrates. Never emitted when the signal is too thin or the analysis fails.

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

Hard cap on the number of jobs collected. Foundit carries hundreds of thousands of postings across its four regions; use this cap to limit billing.

## `maxConcurrency` (type: `integer`):

Maximum number of job detail API calls processed in parallel.

## `maxRequestRetries` (type: `integer`):

Number of retries before a failed request is given up.

## `proxy` (type: `object`):

Optional and off by default. Foundit's API answers directly without any anti-bot challenge, so a proxy adds latency and cost for no benefit. Only enable this if you need traffic to leave from a specific IP or country.

## Actor input object example

```json
{
  "startUrls": [
    "https://www.foundit.in/srp/results?query=software%20engineer&locations=Bangalore"
  ],
  "fetchDescriptions": true,
  "excludeSyndicated": false,
  "enrichEmails": false,
  "hiringMarketAnalysis": false,
  "maxItems": 1000,
  "maxConcurrency": 10,
  "maxRequestRetries": 4
}
```

# Actor output Schema

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

One row per job posting with title, company, salary band, experience range, skills, map coordinates, full job description (HTML and plain text), apply URL, and a native-vs-syndicated provenance flag.

# 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 = {
    "startUrls": [
        "https://www.foundit.in/srp/results?query=software%20engineer&locations=Bangalore"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("memo23/foundit-jobs-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 = { "startUrls": ["https://www.foundit.in/srp/results?query=software%20engineer&locations=Bangalore"] }

# Run the Actor and wait for it to finish
run = client.actor("memo23/foundit-jobs-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 '{
  "startUrls": [
    "https://www.foundit.in/srp/results?query=software%20engineer&locations=Bangalore"
  ]
}' |
apify call memo23/foundit-jobs-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,memo23/foundit-jobs-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/2lSUDdrBDEtJkRoRT/builds/58NO2L5hlZDu7Zhf6/openapi.json
