# Apna Jobs Scraper — Salary, Shift & Contact (`memo23/apna-jobs-scraper`) Actor

Scrape apna.co jobs — title, employer, salary, shift, department, education, English level, applicant counts. Category and city search. No login.

- **URL**: https://apify.com/memo23/apna-jobs-scraper.md
- **Developed by:** [Muhamed Didovic](https://apify.com/memo23) (community)
- **Categories:** Jobs, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.80 / 1,000 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?

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

## Apna Jobs Scraper — India Jobs, Salary, Shift & Employer Contact

Scrape job postings from **apna.co**, India's largest entry-level and blue/grey-collar job board — no login, no cookies, no proxy, no browser. Pick categories and cities, or paste listing / job URLs, and get back **35 structured fields per job**: employer, full postal address with pincode, salary band, shift, department and role category, and the screening requirements apna asks for that most job feeds throw away — **education, English level, experience and stated gender preference** — plus how many people have already applied. Schedule it and only new jobs are billed.

### Why Use This Scraper?

- **Screening fields, not just listings.** `education`, `englishLevel`, `experienceRequired` and `shift` are what actually decide whether a candidate can take the job. They live in apna's own panels, not in the schema.org block, and they are read here by anchoring on the visible labels.
- **Full postal address, including pincode.** `address`, `location`, `region` and `postalCode` come straight from apna's structured data — enough to map or route on.
- **Live competition signal.** `applicantCount` shows how many people have already applied to that exact posting.
- **Real coverage.** 58 categories × 42 verified cities. apna claims 95,000+ live postings.
- **The city names people actually type.** `Bangalore`, `Delhi` and `Gurgaon` all fail on apna's own URLs; this actor maps them to the slugs that work.
- **No proxy needed.** apna answers a direct request, so runs are fast and you are not burning proxy budget.
- **Only-new monitoring.** Flip `monitorMode` on a schedule and already-delivered job IDs are skipped — not fetched, not billed. Name each watch with `monitorStateName` if you run more than one.

### Overview

apna.co is where most of India's non-white-collar hiring happens: delivery, retail, back office, field sales, telecalling, security, beauty, driving, plus entry-level roles filtered by 10th/12th pass.

This actor walks apna's category landing pages (`/jobs/{category}-jobs`, optionally narrowed to a city), harvests the posting URLs, then reads each posting's own page — where apna publishes a complete schema.org `JobPosting` block alongside the panels that carry the screening requirements.

Listing cards are deliberately not parsed: apna's markup is emotion-generated and its class names change on every deploy, while the detail page's structured data is a published contract.

### Supported Inputs

| Input type | Pattern | Example |
|---|---|---|
| **Category** | apna's category slug | `back-office`, `delivery-person`, `telecalling-bpo` |
| **City** | common name or apna slug | `Mumbai`, `Bangalore`, `Delhi`, `bengaluru` |
| Listing URL | `https://apna.co/jobs/{category}-jobs[-in-{city}]` | `https://apna.co/jobs/back-office-jobs-in-mumbai` |
| Direct job URL | `https://apna.co/job/{city}/{slug}-{id}` | `https://apna.co/job/thane/back-office-coordinator-525613186` |

### Use Cases

- **Blue/grey-collar recruitment intelligence** — who is hiring, for what shift, at what pay, in which pincode.
- **Wage benchmarking** — salary bands by category and city across thousands of live postings.
- **Staffing and gig-platform sourcing** — find employers hiring at volume in a specific area.
- **Labour-market research** — education and English-level requirements by role and region.
- **Job aggregation** — feed a candidate-facing app with fresh postings by category and city.

### How It Works

<p align="center">
  <img src="https://raw.githubusercontent.com/muhamed-didovic/muhamed-didovic.github.io/main/assets/how-it-works-apna.png" alt="How the Apna jobs scraper works" width="900" />
</p>

1. **Input** — pick categories and cities, paste listing or job URLs, or leave filters empty for apna's whole board
2. **Resolve** — city names are mapped to the slugs apna serves; unknown values are reported rather than silently ignored
3. **Walk listings** — pages `?page=N` at 25 postings per page, cycling between your category/city pairs a page at a time so no single one uses up the item budget
4. **Read each posting** — the detail page's `JobPosting` structured data plus the label-anchored requirement panels
5. **Filter / monitor** — optional keyword and posted-within windows; already-seen IDs are skipped when monitoring is on
6. **Output** — one row per posting, as JSON or CSV

### Input Configuration

| Field | Type | Required | Notes |
|---|---|---|---|
| `categories` | `string[]` | no | apna category slugs. **Empty = all 58 categories.** |
| `cities` | `string[]` | no | **Empty = all-India.** Common names are mapped (`Bangalore` → `bengaluru`, `Delhi` → `new-delhi`, `Gurgaon` → `gurgaon-gurugram`). |
| `startUrls` | `string[]` | no | Job URLs and/or listing URLs (`/jobs/{category}-jobs[-in-{city}]`). URLs alone do not walk the rest of the board. |
| `keyword` | `string` | no | Keep rows whose title or company contains this text. apna has no keyword search, so this is applied after the detail page is read. |
| `dateWithinDays` | `integer` | no | Keep postings whose `postedDate` is within this many days. `0` = off. Rows with no date are dropped when set. |
| `enrichEmails` | `boolean` | no | Experimental. Discovers an employer website from the company name. Lower hit rate here than on white-collar boards — see the FAQ. Default `false`. |
| `monitorMode` | `boolean` | no | Only return (and bill) jobs not already delivered to this account. Default `false`. |
| `monitorStateName` | `string` | no | Name a watch so several schedules keep separate seen-ID lists. |
| `emitBaseline` | `boolean` | no | First monitoring run: `true` (default) emits current matches; `false` seeds silently. |
| `includeUnchanged` | `boolean` | no | Monitoring: also re-emit already-seen jobs, tagged `changeType`. Billed. Default `false`. |
| `resetMonitoringState` | `boolean` | no | One-shot: clear the seen-ID list at the start of this run. |
| `maxItems` | `integer` | no | Hard cap on **emitted** rows. Skipped monitor IDs and filter misses do not consume it. Default `1000`. Free-tier runs are capped at `100`. |
| `maxConcurrency` | `integer` | no | Parallel detail fetches. Default `10`. |
| `proxy` | object | no | **Not needed.** Off by default. |

#### Example input

```json
{
  "categories": ["back-office", "delivery-person"],
  "cities": ["Mumbai", "Bangalore"],
  "maxItems": 1000
}
```

### Output Overview

One row per posting. 35 fields, all flat except `skills`, `benefits` and `highlights`.

#### Output Sample

```json
{
  "type": "job",
  "source": "apna",
  "jobId": "525613186",
  "jobUrl": "https://apna.co/job/thane/back-office-coordinator-525613186",
  "title": "Back Office Coordinator",
  "companyName": "Radiance",
  "location": "Thane",
  "region": "Maharashtra",
  "address": "RADIANCE GROUP, shil, opp. Bharat gear company, Dawle Village, Kausa, Mumbra, Thane, Maharashtra, India",
  "postalCode": "400076",
  "country": "IN",
  "salaryMin": 120000,
  "salaryMax": 149000,
  "salaryCurrency": "INR",
  "salaryPeriod": "MONTH",
  "employmentType": "FULL_TIME",
  "department": "Admin / Back Office / Computer Operator",
  "roleCategory": "Back Office",
  "shift": "Day Shift",
  "experienceRequired": "Any experience",
  "education": "Graduate",
  "englishLevel": "Basic English",
  "workLocationType": "Work from Office",
  "genderPreference": "Female",
  "skills": [],
  "applicantCount": 93,
  "benefits": ["Flexible Working Hours"],
  "highlights": [],
  "postedDate": "2026-09-02T12:00:38.539Z",
  "validThrough": "2026-09-17",
  "directApply": true,
  "description": "Key Responsibilities: • Vendor identification & development • Procurement of civil/construction materials …",
  "scrapedAt": "2026-09-08T21:12:44.011Z"
}
```

### Key Output Fields

| Field | Type | Notes |
|---|---|---|
| `jobId` | string | apna's own numeric id. Unique site-wide — safe as a dedup key. |
| `salaryMin` / `salaryMax` / `salaryPeriod` | number / string | **Reported exactly as apna publishes them.** `salaryPeriod` is almost always `MONTH`. See the FAQ before treating it as verified. |
| `education` / `englishLevel` / `experienceRequired` | string | null | apna's stated screening bar. Read from the posting's own panels. |
| `genderPreference` | string | null | Some apna employers state one. Reported as published — see the Disclaimer. |
| `shift` | string | null | `Day Shift` / `Night Shift`. |
| `workLocationType` | string | null | `Work from Office`, `Work from Home`, `Field Job` or `Hybrid`. |
| `applicantCount` | number | null | How many have applied. `null` on postings too new to have any. |
| `postalCode` / `address` | string | null | Full street address and pincode from apna's structured data. |
| `validThrough` | string | null | When apna expires the posting. |
| `benefits` / `highlights` | string\[] | apna's own badges, e.g. `Flexible Working Hours`, `Urgently hiring`. |

### FAQ

**Do I need a login, cookie or proxy?**
No. apna serves every listing and posting page server-rendered to a plain request. The proxy input exists only for callers who need a specific egress IP.

**The salary figures look too high for the role — is the scraper wrong?**
No. apna labels these `monthly` in both its own interface and its structured data, and this actor passes them through unchanged. Some employers clearly enter an annual figure into a monthly field — one Thane back-office posting reads "₹1,20,000 - ₹1,49,000 monthly" on apna itself. Treat `salaryPeriod` as apna's label rather than a verified fact, and sanity-check outliers against the role. Re-basing the numbers here would invent a figure apna never published.

**Why is `applicantCount` sometimes null?**
apna only shows it once a posting has applicants, so brand-new postings have none.

**Why did asking for two cities return only one?**
Because `maxItems` was smaller than one listing page. apna serves 25 postings per page, so a cap below 25 fills from the first category/city pair before the next is reached. Above that, the crawl cycles between pairs a page at a time.

**Why do `Bangalore` and `Gurgaon` need mapping?**
apna 404s on `bangalore`, `gurgaon` and `gurugram`, and a bare `delhi` returns a page with no postings. The working slugs are `bengaluru`, `gurgaon-gurugram` and `new-delhi`. You can type either form.

**How good is the email enrichment here?**
Lower than on white-collar boards, and deliberately opt-in. apna never publishes an employer website, so a domain has to be discovered from the company name — and many apna employers are small local businesses with no web presence at all. You are only billed for emails actually found.

**Does monitoring charge for jobs I already have?**
No. In `monitorMode` an ID already in your baseline is skipped before the detail fetch, so it is not billed. `maxItems` counts only rows that land in the dataset. The first run after you turn monitoring on returns the current matches and builds that baseline (set `emitBaseline` to `false` if you want the first run silent).

**Can I run two watches from one account?**
Yes — give each schedule its own `monitorStateName` (for example `mumbai-office` and `pune-delivery`) so their seen-ID lists stay separate.

### Pricing

| Event | When | Price |
|---|---|---|
| Actor start | Once per run | $0.005 |
| Job row | Each posting written to the dataset | $0.0008 |
| Employer email | Only when enrichment finds an address | $0.05 per found email |

Skipped monitor IDs, keyword misses and date-filter misses are not billed.

### What this has that the other Apna actors do not

| | This actor | getascraper / themineworks / shahidirfan |
|---|---|---|
| Education, English, shift, gender, applicant count, pincode | Always, from the posting page | Partial / card-level / optional flags |
| Only-new monitor + named watches | Yes — skipped IDs not billed | getascraper / themineworks yes; shahidirfan no |
| Listing URLs in `startUrls` | Yes | getascraper yes; others mixed |
| Keyword filter | Title or company (apna has no search API) | themineworks title-only; getascraper generates searches |
| Employer email | Opt-in, billed only on a hit | No |
| Price per job | $0.0008 | $0.0009–$0.00137 |

We still fetch every posting page. That is slower than a card-only dump and is why the screening fields are on the row.

### Support

Found a bug or need a field that isn't here? Open a ticket on the [Issues](https://apify.com/memo23/apna-jobs-scraper/issues) tab.

### Additional Services

Need this data pushed somewhere specific, or another India job board covered? Get in touch through the Apify Console.

### Explore More Scrapers

Other India job boards in this family: [Naukri](https://apify.com/memo23/naukri-scraper), [Instahyre](https://apify.com/memo23/instahyre-jobs-scraper), [Internshala](https://apify.com/memo23/internshala-scraper), [CutShort](https://apify.com/memo23/cutshort-jobs-scraper), [Foundit](https://apify.com/memo23/foundit-jobs-scraper), [Shine](https://apify.com/memo23/shine-jobs-scraper).

### 🤖 For AI Agents & LLM Apps

This actor returns flat, self-describing rows. Deduplicate on `jobId`, which is unique site-wide. For matching a candidate to a role, the highest-signal fields are `education`, `englishLevel`, `experienceRequired` and `shift` — apna's postings are screened on those far more than on free-text skills, and `skills` is frequently empty. Treat `salaryPeriod` as apna's own label rather than a verified unit before doing arithmetic on `salaryMin`/`salaryMax`; outliers where an employer entered an annual figure into a monthly field do occur. `applicantCount` is the best available proxy for how contested a posting is.

### ⚠️ Disclaimer

This actor collects only publicly available job-posting data from apna.co. It does not log in, bypass authentication, or access any private or personal candidate data. Some apna employers publish a stated gender preference; that field is reported exactly as the employer published it and is included so researchers can study the practice — using it to filter candidates may be unlawful in your jurisdiction, and that is your responsibility, not apna's or this actor's. Use the output in line with apna's terms of service and applicable data-protection and employment law.

### SEO Keywords

apna scraper, apna.co scraper, apna jobs api, apna jobs scraper, apna listing url, apna monitor, india jobs scraper, blue collar jobs scraper india, entry level jobs scraper, delivery jobs scraper india, back office jobs scraper, india job board scraper, mumbai jobs scraper, bengaluru jobs scraper, telecalling jobs data, field sales jobs india, wage data india scraper, blue collar recruitment data, job posting api india, gig economy jobs india, hiring data india, 10th pass jobs scraper, 12th pass jobs scraper

# Actor input Schema

## `categories` (type: `array`):

apna's own category slugs. Leave empty for every category.

## `cities` (type: `array`):

Leave empty for all-India. You can type the common name — `Bangalore`, `Delhi` and `Gurgaon` are mapped to the slugs apna actually serves, each of which returns nothing under its obvious spelling.

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

Job URLs (`https://apna.co/job/{city}/{slug}-{id}`) and listing URLs (`https://apna.co/jobs/{category}-jobs` or `https://apna.co/jobs/{category}-jobs-in-{city}`). Example: `https://apna.co/jobs/back-office-jobs-in-mumbai`. Default: empty.

## `keyword` (type: `string`):

Keep only postings whose title or company name contains this text. apna has no keyword search of its own, so this is applied after the detail page is read. Example: `coordinator`. Leave empty for no filter. Default: empty.

## `dateWithinDays` (type: `integer`):

Keep only postings whose published `postedDate` is within this many days. `0` (default) applies no date filter. A positive value drops rows that have no parseable date. Example: `7`.

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

apna never publishes an employer website, so this discovers one from the company name and reads its contact pages. Best-effort — many apna employers are small local businesses with no web presence, so hit rates are lower than on white-collar boards. Billed per contact email found; never charged for misses.

## `monitorMode` (type: `boolean`):

When on, job IDs already delivered to this account are skipped — not fetched, not billed. The first run returns current matches and builds the baseline (unless Emit baseline is off). Use Apify Schedules for a 'posted since last run' feed. Default: false.

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

Clear the saved seen-job list at the start of this run. Use after changing the search or to recover a missed delivery. No effect when monitoring is off. Default: false.

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

Name this watch so one account can run several only-new schedules without mixing their history — e.g. `mumbai-office` vs `pune-delivery`. Leave empty for a single default baseline. Only used when monitoring is on.

## `emitBaseline` (type: `boolean`):

Only used with monitoring. ON (default): the first run returns every current match and remembers them. OFF: the first run records what exists now and emits nothing, so later runs return only jobs that appeared after it. Default: true.

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

Only used with monitoring. OFF (default): return only new postings. ON: also re-emit already-seen jobs, each tagged `changeType` = `new` or `unchanged`. Re-emitted rows are billed. Default: false.

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

Hard cap on postings collected. Note that apna serves 25 postings per listing page, so a cap below 25 will be filled from the first category before the next one is reached.

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

Maximum number of job detail pages fetched in parallel.

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

Number of retries before a failed request is given up.

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

Optional and off by default. apna's pages answer 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
{
  "categories": [
    "back-office"
  ],
  "cities": [],
  "dateWithinDays": 0,
  "enrichEmails": false,
  "monitorMode": false,
  "resetMonitoringState": false,
  "emitBaseline": true,
  "includeUnchanged": false,
  "maxItems": 1000,
  "maxConcurrency": 10,
  "maxRequestRetries": 4
}
```

# Actor output Schema

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

One row per posting with employer, full postal address, salary band, shift, department, education and English-level requirements, work mode, applicant count and the full job description.

# 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 = {
    "categories": [
        "back-office"
    ],
    "cities": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("memo23/apna-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 = {
    "categories": ["back-office"],
    "cities": [],
}

# Run the Actor and wait for it to finish
run = client.actor("memo23/apna-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 '{
  "categories": [
    "back-office"
  ],
  "cities": []
}' |
apify call memo23/apna-jobs-scraper --silent --output-dataset

```

## MCP server setup

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