Compare Salary Bands in Lakhs by City and Experience on Foundit
Build a salary table for one role across Indian cities and experience bands from Foundit.in listings that publish pay, and drop the placeholder figures.
The actor referenced in this article. Pay only for results delivered.
An HR team setting pay for a new Hyderabad office, a recruiter answering “what does this role pay in Pune”, and a candidate weighing an offer all want the same table: for one role, what employers in each city say they pay, split by experience. Foundit.in, the job board formerly called Monster India, carries salary bands in lakhs on the listings that publish them. The trouble is finding those listings. Most Foundit jobs hide pay, and a few show figures that are clearly not real.
Try it live: Foundit Monster India Jobs Scraper. Pay per job delivered. Empty searches and summary rows are never charged.
The Foundit Jobs Scraper returns salary as two numbers, salary_min_lakhs and salary_max_lakhs, next to the experience range as two more. With its salary filter set, Foundit returns only listings that publish pay, which turns a sea of hidden salaries into a dataset you can actually summarize.
What you get and who it is for
| Field | What it holds |
|---|---|
salary_min_lakhs, salary_max_lakhs | Annual CTC band in lakhs of rupees, present only when the listing shows pay |
salary_confidential | Foundit’s own flag, which does not reliably track whether figures are shown |
experience_min_years, experience_max_years | Experience range in years (0 and 0 means not stated) |
location | One or more cities as Foundit lists them |
title, company, company_id | The role and the employer |
posted_days_ago, total_applicants | How fresh the listing is and how many have applied |
This is for compensation and HR teams benchmarking an offer, recruiters who need a defensible number for a client, founders deciding where to open a team, and job seekers checking whether a band is fair for their years of experience.
The input
{
"searchKeywords": ["python developer"],
"location": "Bangalore",
"employmentType": "Full Time",
"salaryMinLakhs": 1,
"salaryMaxLakhs": 100,
"postedWithinDays": "30",
"maxJobs": 300
}
salaryMinLakhsandsalaryMaxLakhsare integers from 0 to 100, in lakhs per year. Setting either one tells Foundit to return only listings that carry a salary. In the actor’s own test, a 5 to 15 lakh search for sales managers returned 20 rows and all 20 had figures. We keep the range wide, 1 to 100, so the filter works as a “publishes pay” switch and does not cut off the very spread you want to measure. A role paying above 100 lakhs would fall outside it.locationis one city per run here. A listing can name several cities, such as “Bengaluru, Chennai, Pune”, so running one search per city and labeling rows with the city you searched is cleaner than splitting thelocationtext afterwards. The Python script below does that.postedWithinDaysis a string:"1","3","7","15"or"30". Thirty days gives you more listings with pay; a shorter window gives you a more current picture.maxJobscaps the jobs per run, up to 1,000. The default is 5, so set it.- You can also split by experience at the source with
experienceMinYearsandexperienceMaxYears(integers, 0 to 30). Foundit returns jobs whose range overlaps yours, so a 2 to 6 year search also returns a 5 to 7 year role. Grouping byexperience_min_yearsafterwards, as the script does, keeps each job in one band.
What the output looks like
[
{
"job_id": "67308586",
"title": "Python Developer",
"company": "Astika Software Technologies Private Limited",
"location": "Hyderabad",
"experience_min_years": 4,
"experience_max_years": 8,
"salary_confidential": false,
"salary_min_lakhs": 19,
"salary_max_lakhs": 36.5,
"employment_type": "Full time",
"posted_days_ago": 0,
"total_applicants": 16
},
{
"job_id": "68443648",
"title": "Python Automation Framework Developer",
"company": "Hr Remedy India",
"location": "Bengaluru",
"experience_min_years": 5,
"experience_max_years": 7,
"salary_confidential": false,
"salary_min_lakhs": 20,
"salary_max_lakhs": 35,
"employment_type": "Full time",
"posted_days_ago": 6,
"total_applicants": 143
},
{
"job_id": "66558540",
"title": "Python Developer",
"company": "Artech Infosystems Private Limited",
"location": "Bengaluru, Chennai, Pune",
"experience_min_years": 5,
"experience_max_years": 10,
"salary_confidential": false,
"salary_min_lakhs": 8,
"salary_max_lakhs": 18,
"employment_type": "Full time",
"posted_days_ago": 0,
"total_applicants": 55
},
{
"job_id": "66571485",
"company": "Tetra Computing LLC",
"location": "Remote",
"experience_min_years": 10,
"experience_max_years": 14,
"salary_confidential": true,
"salary_min_lakhs": 0.5,
"salary_max_lakhs": 0.5
}
]
These rows are real but do not come from the salary-filtered input above, which we have not run. They come from searches without a salary filter: the Hr Remedy row from our September 30, 2026 run for "python developer" in Bangalore, full time, posted within 7 days; the other three from our daily all-India "python developer" check runs on September 12 and 16, 2026. They are trimmed to the fields that matter here.
The last row shows why the clean-up step exists. Half a lakh a year for a role asking 10 to 14 years of experience is a placeholder, not a salary. Across all our runs of this actor (179 distinct jobs), 23 jobs carried salary figures and 7 of those were flagged salary_confidential: true. All 7 had figures like this one: 0.5 to 0.5, 1 to 1, 1 to 2 lakhs for 8 to 13 years, or 2 to 3 lakhs for 9 to 17 years. The script drops every row flagged confidential and anything with a top figure under 1 lakh.
The same runs show the other reason a filter matters. Our Bangalore run of September 30 returned 49 jobs, and only 2 had figures, one of them a placeholder. One usable salary out of 49 jobs is not a benchmark.
Run it in Apify Console
- Open https://apify.com/themineworks/foundit-jobs-scraper and click Try for free.
- In Search keywords, enter the role, one keyword per line. Use the title as employers write it, such as
python developerorsales manager. - Set Location to one city.
- Set Minimum salary to 1 and Maximum salary to 100.
- Set Posted within to Last 30 days and Max jobs to 300, then click Start.
- Repeat for each city, or save it as a task and change only the Location between runs.
- Export each run as CSV or Excel, filter out rows where
salary_confidentialis true, and pivot onexperience_min_years.
Run it from Python
pip install apify-client
import csv
from collections import defaultdict
from statistics import median
from apify_client import ApifyClient
client = ApifyClient("YOUR_APIFY_TOKEN")
ROLE = "python developer"
CITIES = ["Bangalore", "Hyderabad", "Pune"]
def band(job):
lo, hi = job["experience_min_years"], job["experience_max_years"]
if lo == 0 and hi == 0:
return "not stated"
if lo <= 2:
return "0 to 2 yrs"
if lo <= 5:
return "3 to 5 yrs"
if lo <= 9:
return "6 to 9 yrs"
return "10+ yrs"
rows, seen = [], set()
for city in CITIES:
run = client.actor("themineworks/foundit-jobs-scraper").call(run_input={
"searchKeywords": [ROLE],
"location": city,
"employmentType": "Full Time",
"salaryMinLakhs": 1,
"salaryMaxLakhs": 100,
"postedWithinDays": "30",
"maxJobs": 300,
})
for job in client.dataset(run["defaultDatasetId"]).iterate_items():
if "_type" in job or "salary_min_lakhs" not in job:
continue # summary rows and jobs without pay
if job.get("salary_confidential"):
continue # figures on these looked like placeholders in our runs
lo, hi = job["salary_min_lakhs"], job["salary_max_lakhs"]
if hi < lo or hi < 1:
continue
if (city, job["job_id"]) in seen:
continue
seen.add((city, job["job_id"]))
rows.append({"city": city, "band": band(job), "company": job["company"],
"title": job["title"], "min_lakhs": lo, "max_lakhs": hi,
"mid_lakhs": (lo + hi) / 2, "url": job["apply_url"]})
groups = defaultdict(list)
for r in rows:
groups[(r["city"], r["band"])].append(r)
summary = []
for (city, b), g in sorted(groups.items()):
summary.append({
"city": city, "experience": b, "listings": len(g),
"companies": len({r["company"] for r in g}),
"median_min_lakhs": median(r["min_lakhs"] for r in g),
"median_max_lakhs": median(r["max_lakhs"] for r in g),
"median_mid_lakhs": median(r["mid_lakhs"] for r in g),
})
for name, data in (("salary_listings.csv", rows), ("salary_by_city_band.csv", summary)):
if data:
with open(name, "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=list(data[0].keys()))
writer.writeheader()
writer.writerows(data)
for s in summary:
flag = "" if s["companies"] >= 5 else " (thin)"
print(f"{s['city']:<10} {s['experience']:<11} {s['listings']:>3} jobs "
f"median {s['median_min_lakhs']} to {s['median_max_lakhs']} lakhs{flag}")
You get two files. salary_listings.csv keeps every usable listing with its city, band and link, so you can check any number against its source. salary_by_city_band.csv holds one row per city and experience band, with the median low end, high end and midpoint of the published bands, plus how many listings and how many distinct companies sit behind each median. Cells backed by fewer than five companies are marked thin in the printout.
Two choices in the script are worth knowing. It counts companies as well as listings because one employer can post the same role many times: across all our runs, 8 of the 16 usable salary rows were internship listings from a single company, Maxgen Technologies, each covering a different set of Maharashtra and Gujarat towns. And it keeps rows where experience is “not stated” in their own band rather than guessing.
Refresh it every month
Pay bands move slowly, so a monthly refresh is enough. Save one task per city in Apify Console, open Schedules, create a schedule with a cron expression such as 0 6 1 * * (06:00 on the first of each month) and add all the tasks to it. With postedWithinDays at "30", each month’s runs cover the month just ended.
Send each run to a Google Sheet with Apify’s Google Sheets integration and keep a tab per month, or let a webhook trigger the script above when the last run finishes. Comparing this month’s medians with last month’s shows you which city is moving, as long as you read the company counts next to them.
What it costs
You pay per job saved to your dataset: $2.00 per 1,000 jobs on the Bronze plan, $1.75 on Silver, and $1.50 on Gold and above. There is no start fee and no separate compute charge.
The script runs three cities with a cap of 300 jobs each, so at most 900 jobs: $1.80 on Bronze, $1.58 on Silver or $1.35 on Gold. A salary-filtered search usually returns fewer jobs than the cap, since only listings with pay qualify, and you pay only for jobs delivered. Five cities refreshed monthly at the full cap is 1,500 jobs, $3.00 a month on Bronze.
Compare that with searching without the filter. At the rate in the actor’s 1,000-row test, 9 jobs with pay per 1,000, you would pay for about 110 jobs per usable salary. The filter is what makes this affordable.
Limits worth knowing
- Published pay is a minority, and not a random one. Only employers who choose to show salary appear in this dataset. Across all our runs, none of the 43 Infosys and Tata Consultancy Services listings showed pay, so the table describes employers who publish, not the whole market. Running the same roles through Naukri’s job data in Python widens the sample where Foundit is thin.
- Watch for placeholder figures. Drop rows flagged
salary_confidentialand anything under 1 lakh, as the script does, and glance at the listing file for bands that do not fit the experience asked. - One employer can dominate a cell. Staffing firms and training companies repost the same role across many towns. Read the company count before the median.
- Multi-city listings. A job listed for “Bengaluru, Chennai, Pune” can turn up in more than one city’s search. The script keeps it once per city searched, which is right for a per-city table but means the same job can count toward three cities.
- Experience 0 to 0 means not stated. In a 1,000-row test, 660 rows had
0for both experience fields. Those land in the “not stated” band. - Up to 1,000 jobs per run. Split a large search by city or experience band rather than raising one cap.
Related
Explore the scraper referenced in this article: inputs, outputs, and pricing, then run it on Apify.
Frequently asked questions
Why do most Foundit jobs have no salary? +
Most employers on Foundit do not publish pay. In a 1,000-row all-India test without a salary filter, 9 rows had figures. Setting salaryMinLakhs or salaryMaxLakhs makes Foundit return only listings that show a salary.
What unit are the salary figures in? +
Lakhs of rupees per year, as annual CTC. A row with salary_min_lakhs 8 and salary_max_lakhs 18 means 8 to 18 lakhs a year.
Can I trust salary_confidential? +
Not as a has-salary flag. Check whether salary_min_lakhs is present instead. In our own runs, every row flagged confidential that still carried figures had ones that looked like placeholders, such as 0.5 to 0.5 lakhs for 10 to 14 years of experience, so the guide drops them.
How many listings do I need for a fair comparison? +
There is no fixed number, but a median from two or three listings tells you about those employers, not the market. The script reports the listing and company count next to every median so you can grey out thin cells.
Find the Skills Employers Ask for Most in a Role on Foundit
Count the skill tags on Foundit.in (Monster India) jobs for one role and city, merge spelling variants, and rank skills by how many employers want them.
Track New Foundit Job Postings Every Day With Monitor Mode
Get only the Foundit.in (Monster India) jobs you have not seen before, every morning, and pay only for the new rows. Input, Python code and real costs.
Compare Pay for a Role by Experience Level on Shine
Run one Shine.com search per experience band, keep the listings that show pay, and turn them into salary ranges in lakhs for each band.
Post a Daily Feed of New Tech Jobs to Slack or Telegram
Send each morning's new Hirist.tech jobs for your stack and cities to a Slack or Telegram channel, with repeats filtered out by job id, using Python.