# Internshala Scraper — Internships & Jobs + Employer Emails (`memo23/internshala-scraper`) Actor

Internshala scraper — pull internships and jobs by keyword, category, city and filters (stipend, duration, work-from-home, part-time, PPO). Full details per posting: company, stipend, skills, openings, apply-by date. Optional employer-email enrichment + incremental monitoring. JSON/CSV.

- **URL**: https://apify.com/memo23/internshala-scraper.md
- **Developed by:** [Muhamed Didovic](https://apify.com/memo23) (community)
- **Categories:** Jobs, Lead generation, Automation
- **Features:** Limited permissions
- **Stats:** 46 total users, 9 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.50 / 1,000 internship or job scrapeds

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

## Internshala Scraper

<p align="center">
  <img src="https://raw.githubusercontent.com/muhamed-didovic/muhamed-didovic.github.io/main/assets/internshala-logo.png" alt="Internshala Scraper logo" width="220" />
</p>

Scrape internships and fresher jobs from Internshala at scale — search by keyword, category, or city, pull every posting's full details (stipend, skills, duration, apply-by), and optionally enrich each employer with a contact email. Pure HTTP, no login, no browser.

### Why Use This Scraper?

- **Search the way recruiters do** — filter Internshala by keyword, category, and city, then narrow with work-from-home, part-time, minimum stipend, PPO (pre-placement offer), and women-only toggles. Or just paste any Internshala listing or posting URL.
- **Full posting details, not just titles** — every row carries the complete `JobPosting`: company, website, location, stipend range, duration, number of openings, perks, required skills, responsibilities, who-can-apply, apply-by date, and the direct apply URL.
- **UNIQUE employer-email enrichment** — flip one toggle and the scraper finds a contact email for the hiring company on top of every posting. No other Internshala scraper on the Store offers this — it turns a job feed into a ready-to-use B2B lead list.
- **Applicants-per-opening + hiring history** — every detail row carries the public applicant count (or the early-applicant badge), the ratio against openings, and Internshala's "Activity on Internshala" block: hiring-since, opportunities posted, candidates hired.
- **Change-only monitoring** — schedule the actor and later runs emit `new` and `updated` (title, company, location, or applicant count). `applicantCountDelta` rides on the row. Unchanged listings are skipped and never billed unless you turn on `includeUnchanged`.
- **Pure HTTP — no login, no browser** — the scraper reads Internshala's own embedded structured data over plain HTTP (Cheerio/impit). No headless Chrome, no account, no captcha-solving service. That makes runs fast, cheap, and stable.
- **Clean, export-ready output** — one flat row per posting, ready to download as JSON, CSV, or Excel, or pipe straight into your ATS, CRM, or spreadsheet.

### Overview

[Internshala](https://internshala.com/) is India's largest internships and fresher-jobs platform, listing hundreds of thousands of internships and entry-level jobs across every category — from software development and data science to marketing, design, finance, and content writing.

This actor turns that public catalog into structured data. Point it at a search (keyword + category + city + filters), a listing page, or individual postings, and it walks the results, opens each internship or job, and reads the `JobPosting` structured data that Internshala embeds in every posting page. You get a consistent, fully-typed record per posting — the same fields whether you scraped one URL or fifty thousand.

| You asked for | getascraper | This actor |
|---|---|---|
| Applicant count / applicants-per-opening | Yes | Same public `applications_message` field |
| Employer hiring history | Yes | Same "Activity on Internshala" block |
| Actively hiring / early applicant / PPO badges | `specialFilters` | Same keys |
| Change-only feed | `new_and_changed` | `monitoringMode` + `updated` when title/company/location/applicants move |
| Employer email | No | `enrichEmails` — billed only when an email is found |
| Job event | $0.98–$1.30 / 1k | $0.50 / 1k (price left as-is this month) |

Typical uses: building internship and fresher-job aggregators, powering campus-hiring and career-platform dashboards, market research on stipends and demand by category or city, and B2B lead generation using the enriched employer emails.

### Supported Inputs

You can drive the scraper three ways — mix and match them freely in a single run:

**1. Keyword / category / city search (with filters)**

Set any combination of `searchQuery`, `category`, and `location`, then add filters (`workFromHome`, `partTime`, `withJobOffer`, `forWomen`, `minStipend`, `maxDurationMonths`). Choose `listingType` = `internship`, `job`, or `both`. The scraper builds the correct Internshala search, pages through all results, and scrapes each posting.

**2. Listing / search URLs (`startUrls`)**

Already have an Internshala URL from your browser? Paste it in and the scraper pages through it and scrapes every posting it finds. Examples:

- `https://internshala.com/internships/computer-science-internship-in-mumbai/`
- `https://internshala.com/internships/graphic-design-internship/`
- `https://internshala.com/internships/work-from-home-marketing-internship/`
- `https://internshala.com/jobs/`
- `https://internshala.com/jobs/data-science-jobs-in-bangalore/`

**3. Individual posting URLs (`startUrls`)**

Drop the URL of a single internship or job and the scraper extracts just that posting — useful for enriching a hand-picked shortlist. Example:

- `https://internshala.com/internship/detail/graphic-design-internship-in-jaipur-at-emiac-technologies`

Detail pages are auto-detected, so listing URLs and posting URLs can sit in the same `startUrls` list.

### Use Cases

| Who | What they use it for |
| --- | --- |
| **Recruiters & staffing agencies** | Pull fresh internships/fresher roles by skill, category, and city into an ATS; track which companies are hiring which skills, at what stipend. |
| **Ed-tech & career platforms** | Power an internship/job board or a "recommended for you" feed with live, structured Internshala data — skills, stipend, duration, apply-by. |
| **Student job-boards & communities** | Aggregate WFH, part-time, and PPO opportunities for a campus, cohort, or Telegram/WhatsApp community, refreshed on a schedule. |
| **B2B lead generation** | Turn "who's hiring on Internshala" into a contactable list — the employer-email enrichment attaches a company email to every posting. |
| **Hiring-market research** | Analyse stipend ranges, in-demand skills, WFH share, and category/city hiring volume over time for reports, dashboards, or investor decks. |

### How It Works

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

1. **Input** — give the scraper a keyword/category/city search with filters, paste listing URLs, or drop individual posting URLs. Pick `listingType` = internships, jobs, or both.
2. **Fetch & parse** — over plain HTTP (no browser, no login), the scraper walks the listing page by page (`/page-2`, `/page-3`, …), opens each internship or job, and reads the `JobPosting` structured data embedded in the page. If `enrichEmails` is on, it then finds the employer's contact email. If `monitoringMode` is on, it skips postings seen in previous runs.
3. **Emit** — each posting becomes one flat row with all fields below, streamed to the dataset for download as JSON, CSV, or Excel, or via the API.

### Input Configuration

| Field | Type | Description |
| --- | --- | --- |
| `listingType` | enum | `internship`, `job`, or `both` — which catalog(s) to scrape. |
| `searchQuery` | string | Keyword to search, e.g. `graphic design`, `data science`, `content writing`. |
| `category` | string | Internshala category to filter by, e.g. `Marketing`, `Web Development`, `Finance`. |
| `locations` | array | Several cities — one search walk each. |
| `location` | string | City to filter by, e.g. `Mumbai`, `Bangalore`, `Delhi`. |
| `workFromHome` | boolean | Only return work-from-home (remote) opportunities. |
| `partTime` | boolean | Only return part-time opportunities. |
| `forWomen` | boolean | Only return opportunities in the "internships for women" segment. |
| `withJobOffer` | boolean | Only return internships that come with a pre-placement job offer (PPO). |
| `specialFilters` | array | `actively_hiring`, `early_applicant`, `job_offer`. Dropped rows are never billed. |
| `maxDurationMonths` | integer | Cap internship duration at this many months. |
| `minStipend` | integer | Minimum monthly stipend in INR. |
| `startUrls` | array | Internshala listing/search URLs or individual posting URLs to scrape directly. Detail pages are auto-detected. |
| `includeDetails` | boolean | Open each posting to extract the full `JobPosting` (default). Turn off for a faster, shallower listing-only pass. |
| `enrichEmails` | boolean | Opt-in — find a contact email for the hiring company on each posting. |
| `monitoringMode` | boolean | Change-only mode — `new` / `updated`; unchanged skipped unless `includeUnchanged`. |
| `resetMonitoringState` | boolean | Forget previously-seen postings — the next monitoring run returns everything again. |
| `includeUnchanged` | boolean | Also re-emit already-seen listings (billed). |
| `monitorStateName` | string | Isolate several scheduled watches. |
| `maxItems` | integer | Hard cap on the number of postings to return (across all inputs). |
| `minConcurrency` / `maxConcurrency` | integer | Advanced — tune parallelism for very large runs. |
| `maxRequestRetries` | integer | Advanced — how many times a failed page is retried before being skipped. |
| `proxy` | object | Apify proxy configuration. Residential proxies recommended for large runs. |

### Output Overview

Every posting is emitted as one flat JSON object. All fields come straight from Internshala's own `JobPosting` structured data (plus the scraper's own `id`, `url`, `scrapedAt`, and — when enabled — the enriched employer email). `id` is the numeric posting id taken from the posting URL, so it is present and identical on every row regardless of which extraction path produced it. Fields that a given posting doesn't publish come back empty rather than missing, so the schema stays consistent across the whole dataset. Download the result as JSON, CSV, or Excel, or pull it via the Apify API.

### Output Sample

A trimmed internship row (some text fields shortened for readability):

```json
{
  "type": "internship",
  "id": "1784801025",
  "internshalaJobId": "20263215537",
  "url": "https://internshala.com/internship/detail/graphic-design-internship-in-jaipur-at-emiac-technologies1784801025",
  "title": "Graphic Design - Internship",
  "companyName": "EMIAC Technologies",
  "companyWebsite": "https://emiactech.com/",
  "aboutCompany": "EMIAC Technologies is a digital marketing agency specialising in SEO, content, and outreach...",
  "location": "Jaipur, Rajasthan",
  "addressLocality": "Jaipur",
  "addressRegion": "Rajasthan",
  "country": "IN",
  "workFromHome": false,
  "partTime": false,
  "employmentType": "INTERN",
  "stipend": { "currency": "INR", "min": 5000, "max": 7000, "unit": "MONTH", "text": "₹5,000 - 7,000 /month" },
  "durationText": "6 months",
  "startDateText": "Immediately",
  "numberOfOpenings": 2,
  "applicantCount": 46,
  "applicantsPerOpening": 23,
  "isEarlyApplicant": false,
  "isActivelyHiring": true,
  "employerHiringSince": "November 2023",
  "employerOpportunitiesPosted": 65,
  "employerCandidatesHired": 9,
  "withJobOffer": false,
  "perks": ["Certificate", "Letter of recommendation", "Flexible work hours"],
  "postedDate": "2026-06-25",
  "applyByDate": "2026-07-25",
  "category": ["Marketing"],
  "skills": ["Adobe Photoshop", "CorelDRAW", "Adobe Illustrator", "Graphic Design"],
  "responsibilities": "Design social media creatives, banners, and marketing collateral...",
  "whoCanApply": "Available for a full-time (in-office) internship, can start between the given dates...",
  "otherRequirements": "Portfolio of prior design work preferred.",
  "description": "Selected intern's day-to-day responsibilities include designing...",
  "applyUrl": "https://internshala.com/internship/detail/graphic-design-internship-in-jaipur-at-emiac-technologies1784801025",
  "employerEmail": "careers@emiactech.com",
  "scrapedAt": "2026-07-03T09:14:22.000Z"
}
```

> `employerEmail` appears only when `enrichEmails` is enabled.

### Key Output Fields

**Identity & links**

- `type` — `internship` or `job`
- `id` — the posting's numeric id, taken from the posting URL. Stable across runs and unique per posting, so it is the field to deduplicate and join on. This is also the key `monitoringMode` uses to recognise postings you have already received.
- `internshalaJobId` — Internshala's own internal job identifier from the posting's structured data. A different number from `id`, and `null` on the ~12% of multi-location postings that publish no structured data — so join on `id`, not this.
- `url` — canonical posting URL
- `applyUrl` — direct apply link
- `scrapedAt` — ISO timestamp of extraction

**Role**

- `title`, `employmentType`
- `category[]` — Internshala categories the posting belongs to
- `skills[]` — required skills
- `responsibilities`, `whoCanApply`, `otherRequirements`, `description`

**Company**

- `companyName`, `companyWebsite`, `aboutCompany`
- `employerEmail` — enriched contact email (only with `enrichEmails`)

**Location & mode**

- `location`, `addressLocality`, `addressRegion`, `country`
- `workFromHome`, `partTime`

**Compensation & terms**

- `stipend` — `{ currency, min, max, unit, text }`
- `durationText`, `startDateText`, `numberOfOpenings`
- `applicantCount` — public count, or `null` when Internshala only shows "Be an early applicant"
- `applicantsPerOpening` — `applicantCount / numberOfOpenings` when both exist
- `applicantCountDelta` — vs the previous monitor run (`null` on first sight)
- `isEarlyApplicant`, `isActivelyHiring`
- `employerHiringSince`, `employerOpportunitiesPosted`, `employerCandidatesHired`
- `changeType` — `new` / `updated` / `unchanged` in monitoring mode
- `withJobOffer` — comes with a PPO
- `perks[]` — certificate, LoR, flexible hours, etc.

**Dates**

- `postedDate`, `applyByDate`

### FAQ

**Do I need an Internshala account or login?**
No. The scraper reads Internshala's public pages over plain HTTP and parses the structured data every posting already publishes. There is no login, no cookie, and no headless browser involved.

**Can it scrape both internships and jobs?**
Yes. Set `listingType` to `internship`, `job`, or `both`. Internshala lists fresher/entry-level jobs alongside internships, and each row is tagged with a `type` field so you always know which is which.

**How do the filters work — and can I just paste a URL instead?**
Both. You can set `searchQuery`, `category`, `location`, and the boolean/numeric filters (`workFromHome`, `partTime`, `withJobOffer`, `forWomen`, `minStipend`, `maxDurationMonths`) and the scraper builds the matching Internshala search. Or, if you already have an Internshala URL from your browser — a listing, a filtered search, or a single posting — paste it into `startUrls`. Listing and posting URLs can be mixed in the same list; detail pages are detected automatically.

**How does the employer-email enrichment work, and what's the coverage?**
It's an opt-in extra (`enrichEmails`). When enabled, the scraper takes the hiring company on each posting — using the company website Internshala publishes where available — and looks up a contact email for it, attaching `employerEmail` to the row. Coverage depends on whether the company has a discoverable public email; postings where none can be found simply come back without the field. This is the one capability no competing Internshala scraper offers, and it's what turns a job feed into a lead list.

**Can I run this on a schedule and only get new postings?**
Yes. Turn on `monitoringMode` and schedule the actor. Later runs emit `new` listings and `updated` ones (title, company, location, or applicant count moved). Unchanged listings are skipped and never billed. `applicantCountDelta` is the applicant-count change vs the last run.

**Where does the applicant count come from?**
The public detail-page line Internshala already shows (`46 applicants` or `Be an early applicant`). Hiring history is the "Activity on Internshala" block. We do not invent a count when the page only shows the early-applicant prompt.

**What format is the stipend in?**
`stipend` is a structured object: `{ currency, min, max, unit, text }` — e.g. `{ "INR", 5000, 7000, "MONTH", "₹5,000 - 7,000 /month" }`. The `min`/`max`/`unit` fields are numeric and machine-friendly for filtering and analysis, while `text` preserves Internshala's original human-readable string. Fixed (non-range) stipends come through with `min` equal to `max`; unpaid internships come through as zero.

**What export formats are supported?**
Every run writes to an Apify dataset you can download as JSON, CSV, Excel, XML, or JSONL, or fetch via the Apify API. It plugs into scheduling, webhooks, and integrations (Google Sheets, Zapier, Make, etc.) like any Apify actor.

**How fast is it, and how many postings can it handle?**
Because it's pure HTTP with no browser, throughput is high and cost per result is low. It scales from a single posting to tens of thousands across many searches; use `maxItems` to cap volume and Apify residential proxies for the largest runs. Enabling `enrichEmails` adds a lookup step per posting, so email-enriched runs take a little longer.

### Changelog

**2026-08-20 — one consistent `id`, and a monitoring-mode fix**

`id` is now the numeric id from the posting URL on **every** row. It previously came from whichever extraction path handled the posting: postings with structured data got Internshala's internal job identifier (e.g. `20263215537`), while multi-location postings got the URL id (e.g. `1784801025`) — two different numbers for the same posting, depending on the page.

This fixes `monitoringMode`. Incremental runs match postings by their URL id, so postings whose `id` had come from the structured data never matched and were re-delivered on every run. If you scheduled the actor with `monitoringMode` on, you were being re-charged for postings you had already received. Monitoring now skips them.

Internshala's internal identifier has not been dropped — it moved to its own `internshalaJobId` field.

**If you store results, this changes the `id` value on new rows.** Rows already in your dataset or database keep their old values. To reconcile, match on `url` (unchanged) or on the trailing digits of the URL, which are what `id` now holds. If your pipeline keyed off the old structured-data identifier, read `internshalaJobId` instead. Existing `monitoringMode` state carries over safely: previously-stored URL ids keep matching, and any stale structured-data ids sit unused in the store — use `resetMonitoringState` once if you want a clean baseline.

### Support

- **Found a bug or need a new field?** Open a ticket on the **Issues** tab of this actor — it's the fastest way to reach me and I actively maintain this scraper.
- **Email:** <muhamed.didovic@gmail.com>
- **Website:** [muhamed-didovic.github.io](https://muhamed-didovic.github.io/)

### Additional Services

Need something beyond the standard output? I build and maintain custom scrapers and data pipelines. Happy to help with:

- Custom fields, filters, or export formats tailored to your workflow
- Scheduled, incremental feeds wired into your ATS, CRM, or data warehouse
- Enrichment beyond employer email (company firmographics, social profiles, etc.)
- Private or dedicated actors for high-volume or compliance-sensitive use

Reach out via the Issues tab or email and describe what you need.

### Explore More Scrapers

Browse my other Apify actors — job boards, business directories, review sites, social platforms, and more — on my profile: [memo23 on Apify](https://apify.com/memo23).

### 🤖 For AI Agents & LLM Apps

Compact reference for AI agents calling this actor via the [Apify MCP server](https://mcp.apify.com) or the Apify API (actor: `memo23/internshala-scraper`).

**Purpose:** Scrape internships and fresher jobs from Internshala — full `JobPosting` details per listing — driven by keyword/category/city filters or ready-made Internshala URLs, with optional employer-email enrichment.

**Minimal input:**

```json
{ "startUrls": ["https://internshala.com/internships/computer-science-internship-in-mumbai/"], "maxItems": 20 }
```

Or build a search from fields instead of a URL: `{ "listingType": "internship", "searchQuery": "graphic design", "location": "mumbai", "maxItems": 20 }`.

**Output:** one row per posting — `type`, `id`, `url`, `title`, `companyName`, `companyWebsite`, `aboutCompany`, `location`, `addressLocality`, `addressRegion`, `country`, `workFromHome`, `partTime`, `employmentType`, `stipend {currency, min, max, unit, text}`, `durationText`, `startDateText`, `numberOfOpenings`, `withJobOffer`, `perks`, `postedDate`, `applyByDate`, `category`, `skills`, `responsibilities`, `whoCanApply`, `otherRequirements`, `description`, `applyUrl`, `employerEmail` (only with enrichment), `scrapedAt`.

**Behaviors an agent should know:**

- `startUrls` override the filter fields; when empty the search is built from `listingType`, `searchQuery`, `category`, `location` plus the boolean/numeric filters.
- Always set `maxItems` — Internshala pages return ~40-50 cards each; default cap is 1000.
- `monitoringMode` returns only postings not seen on prior runs (per-user dedup state); `resetMonitoringState` rebuilds the baseline.
- `enrichEmails` is opt-in (default `false`) and adds an email lookup per posting; `includeDetails` (default `true`) fetches each detail page.
- Billed per result (pay-per-event); email enrichment is charged only when an email is found — see the Pricing tab on the actor page.

### ⚠️ Disclaimer

Internshala is a trademark of Scholiverse Educare Pvt. Ltd. (Internshala). This actor is an independent tool and is **not** affiliated with, authorized by, endorsed by, or in any way officially connected to Internshala or Scholiverse Educare.

This scraper accesses only **publicly available** data — the same information any visitor can see in their browser without logging in. It does not bypass authentication, access private or gated data, or collect personal data beyond what companies voluntarily publish on public postings.

You are responsible for how you use the data you collect. Use it in compliance with Internshala's Terms of Service, applicable laws (including the GDPR, India's DPDP Act, and other data-protection and anti-spam regulations), and any contractual obligations you may have. In particular, when using enriched employer emails, ensure your outreach complies with applicable consent and marketing laws. Do not use scraped data to spam, harass, or violate anyone's privacy.

### SEO Keywords

internshala scraper, internshala api, internship scraper, internshala jobs scraper, india internships data, stipend data scraper, campus hiring data, fresher jobs scraper, internshala data extraction, employer email finder, internship data api, internshala internships crawler, work from home internships scraper, india jobs scraper, internship listings scraper, recruiter lead generation, hiring market research, job posting scraper, internshala email enrichment, student internships data, indian startups hiring data

# Actor input Schema

## `listingType` (type: `string`):

Both (default) reads Internshala's internship listings, which cover both kinds.

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

Optional, e.g. python or content writing.

## `category` (type: `string`):

Optional, e.g. computer science, marketing, graphic design or human resources.

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

Optional, one per line, e.g. mumbai and pune; each city is its own search. Combined with the single city below. Work-from-home listings do not count as any city.

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

Optional, e.g. mumbai, delhi or bangalore. Not used for work-from-home-only searches. Combined with the cities above.

## `workFromHome` (type: `boolean`):

On: keep only listings you can do from home.

## `partTime` (type: `boolean`):

On: keep only part-time listings.

## `forWomen` (type: `boolean`):

On: keep only listings in Internshala's Internships for women section.

## `withJobOffer` (type: `boolean`):

On: keep only listings that promise a job or a pre-placement offer (PPO) afterwards.

## `specialFilters` (type: `array`):

Keep only listings that show every badge picked: Actively hiring, Be an early applicant (no applicants counted yet) or the job-offer perk. Listings dropped here are not charged.

## `maxDurationMonths` (type: `integer`):

1 to 6; other values round to a supported one. Only applied when no field, city or keyword is set, because Internshala cannot combine them.

## `minStipend` (type: `integer`):

Rounded to Internshala's brackets: 2000, 4000, 6000, 8000 or 10000. Only applied when no field, city or keyword is set.

## `minSalary` (type: `integer`):

Keep listings whose lowest stipend or salary reaches this. Listings that hide their pay are left out when this is set. Checked before saving, so dropped listings are not charged.

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

Keep listings posted inside this window; listings with no readable date are left out when a window is picked. Checked before saving, so dropped listings are not charged.

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

Search and single-listing links can be mixed.

## `includeDetails` (type: `boolean`):

On (default): each listing's own page is read for the full description, stipend, skills, company website and dates. Off: fewer requests, list-card fields only.

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

Looks for a contact email for each company on its website (the one on the listing, or one found from the company name) and on its contact and about pages. Adds contactEmail, contactWebsite and an emailEnrichment object. Charged only when an email is found, never for a miss.

## `fromDate` (type: `string`):

Optional date, e.g. 2026-08-01. Older postings are skipped.

## `toDate` (type: `string`):

Optional date. A plain date such as 2026-08-31 covers that whole day (UTC).

## `maxAgeMinutes` (type: `integer`):

Optional. Keep only jobs posted this many minutes ago or less, e.g. 1440 for the last day. Empty or 0: any age.

## `includeKeywords` (type: `array`):

Optional words. A job is kept when its title, employer or description contains at least one of them. Letter case does not matter, and part of a word counts (java also finds javascript).

## `excludeKeywords` (type: `array`):

Optional words. A job whose title, employer or description contains any of them is skipped, even when it also has a word it must mention.

## `excludeEmptyFields` (type: `boolean`):

On: fields with no value (null, empty text, an empty list or an empty object) are removed from each row.

## `compact` (type: `boolean`):

On: each row keeps only the main job fields (such as ID, title, employer, location, pay, date and links), plus changeType and firstSeenAt when change tracking is on. Handy when the rows go to an AI tool.

## `descriptionMaxLength` (type: `integer`):

Longest description kept, in characters. Longer ones are cut and end with …. Empty or 0: the whole text.

## `incrementalMode` (type: `boolean`):

Off (default). On: each job is compared with earlier runs of the same search and saved only when it is NEW, UPDATED or REAPPEARED, with changeType and firstSeenAt on the row. The first run saves everything and becomes the baseline. Jobs that are not saved are not charged.

## `skipReposts` (type: `boolean`):

With change tracking on: skip a job that comes back after it disappeared, whether it is the same ad again or the same content under a new ID.

## `emitExpired` (type: `boolean`):

With change tracking on: at the end of the run, add a small row with changeType EXPIRED for each tracked job this run did not find again. A run that fails or finds nothing adds none.

## `emitUnchanged` (type: `boolean`):

With change tracking on: jobs with no change are saved too, with changeType UNCHANGED, and charged like any row.

## `stateKey` (type: `string`):

Optional, e.g. nurse-toronto. Empty: a name is made from your search settings, so the same search always finds the same memory. Searches given the same name share one memory.

## `resetIncrementalState` (type: `boolean`):

Clears this search's memory at the start of the run, so every job counts as NEW again. Switch it off afterwards, or every run starts over.

## `notifyOnlyChanges` (type: `boolean`):

Matters when change tracking and Also save unchanged jobs are both on: alerts and the webhook then list only NEW, UPDATED and REAPPEARED jobs, not the unchanged ones.

## `notificationLimit` (type: `integer`):

How many jobs each chat alert lists, 1 to 20 (default 5). The first line of the alert gives the run's totals.

## `webhookUrl` (type: `string`):

Optional. After each completed run, gets one JSON POST with run details under metadata and up to 500 of the saved jobs under items. Works with n8n, Make, Zapier or your own backend.

## `webhookHeaders` (type: `object`):

Optional. Headers to send with the webhook POST, as a JSON object, e.g. an Authorization header carrying your token.

## `slackWebhookUrl` (type: `string`):

Optional. Alerts are posted to the Slack channel this link belongs to.

## `discordWebhookUrl` (type: `string`):

Optional. Create one in the channel's integration settings; alerts are posted there.

## `telegramChatId` (type: `string`):

Optional. ID of the chat or channel the bot posts to, e.g. -1001234567890. The bot must be allowed to post there.

## `telegramBotToken` (type: `string`):

Optional. From @BotFather. Telegram alerts need this and the Telegram chat.

## `whatsappTo` (type: `string`):

Optional. Number with country code and no +, e.g. 14165551234. WhatsApp delivers only if this person wrote to your business number in the last 24 hours.

## `whatsappPhoneNumberId` (type: `string`):

Optional. ID of the business number the alerts come from. Meta shows it as Phone number ID on the API Setup page of your WhatsApp app.

## `whatsappAccessToken` (type: `string`):

Optional. A system-user token from Meta Business for your WhatsApp app. WhatsApp alerts need this, the sender number ID and the number to message.

## `monitoringMode` (type: `boolean`):

Each listing is compared with the last run: new the first time, updated when its title, company, place or applicant count changed, otherwise skipped unless unchanged listings are asked for below. Applicant-count changes come with the updated rows. The first run returns everything and becomes the baseline.

## `resetMonitoringState` (type: `boolean`):

One-off: clear the record of delivered listings at the start of this run, e.g. after changing the search.

## `includeUnchanged` (type: `boolean`):

With the listing monitor on. Off (default): only new and changed listings. On: listings already delivered come back too, marked unchanged, and are charged.

## `monitorStateName` (type: `string`):

Optional, e.g. mumbai-cs or remote-marketing, to keep several separate monitors in one account. Empty: one default monitor.

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

The run stops after this many listings, default 1000. Internshala shows about 40 to 50 per page. On the free Apify plan a run stops at 100.

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

Default 10.

## `minConcurrency` (type: `integer`):

Default 1.

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

How many times a failed page is tried again, default 5.

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

Leave empty: the actor's own connection is included at no extra cost. Set this only to use your own proxies.

## Actor input object example

```json
{
  "listingType": "both",
  "searchQuery": "python",
  "category": "computer science",
  "locations": [
    "mumbai",
    "pune"
  ],
  "location": "mumbai",
  "workFromHome": false,
  "partTime": false,
  "forWomen": false,
  "withJobOffer": false,
  "maxDurationMonths": 3,
  "minStipend": 10000,
  "datePosted": "all",
  "startUrls": [
    "https://internshala.com/internships/computer-science-internship-in-mumbai/"
  ],
  "includeDetails": true,
  "enrichEmails": false,
  "excludeEmptyFields": false,
  "compact": false,
  "incrementalMode": false,
  "skipReposts": false,
  "emitExpired": false,
  "emitUnchanged": false,
  "resetIncrementalState": false,
  "notifyOnlyChanges": false,
  "notificationLimit": 5,
  "monitoringMode": false,
  "resetMonitoringState": false,
  "includeUnchanged": false,
  "maxItems": 1000,
  "maxConcurrency": 10,
  "minConcurrency": 1,
  "maxRequestRetries": 5
}
```

# Actor output Schema

## `listings` (type: `string`):

One row per internship or job with title, company, location, stipend, duration, perks, skills, who-can-apply text and the apply URL.

# 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://internshala.com/internships/computer-science-internship-in-mumbai/"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("memo23/internshala-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://internshala.com/internships/computer-science-internship-in-mumbai/"] }

# Run the Actor and wait for it to finish
run = client.actor("memo23/internshala-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://internshala.com/internships/computer-science-internship-in-mumbai/"
  ]
}' |
apify call memo23/internshala-scraper --silent --output-dataset

```

## MCP server setup

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