# JustDial Scraper \[From $2💰] | India Business Leads (`ahmed_jasarevic/justdial-scraper`) Actor

Extract Indian business leads from JustDial: name, category, phone, address, locality, pincode, rating, review count, verified badge, coordinates and profile URL. Uses JustDial's own JSON APIs - no browser needed. Built for lead generation, local SEO and India market research.

- **URL**: https://apify.com/ahmed_jasarevic/justdial-scraper.md
- **Developed by:** [Ahmed Jasarevic](https://apify.com/ahmed_jasarevic) (community)
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.65 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## JustDial Scraper — Scrape Indian Business Listings And Phone Numbers

The **JustDial Scraper** extracts Indian business leads from **JustDial (justdial.com)** — India's largest local business directory — as lead-ready JSON with 19 fields per business: phone, address, locality, pincode, rating, review count, verified badge, coordinates and canonical profile URL. It reads JustDial's own internal JSON listing APIs over plain HTTP (Crawlee's `CheerioCrawler`) — no browser, no DOM scraping, no JavaScript rendering.

### Main use cases

- **B2B lead generation India** — call-ready rows (name, phone, address, pincode), deduped across runs with `docId`.
- **India company contact database** — rebuild a category × city list on a schedule instead of buying a static one.
- **Local business leads India** — plumbers, dentists or any JustDial category, one city or twenty.
- **Local SEO India data** — NAP-consistent name/address/phone rows plus `profileUrl` for citations.
- **India market research data** — category density, `rating`, `reviewCount`, `verified` per market.
- **SMB prospecting data India** — rank prospects by rating, reviews and verified badge.

### Build Indian Business Leads Without Official API Access

JustDial serves listings through **its own internal JSON APIs** — the endpoints the justdial.com frontend calls when you scroll. The Actor walks four endpoint/`wap` combos in order with a mobile User-Agent, pins the first surface that answers for the rest of the search, sanity-checks every row and writes one dataset item per business. Plain HTTP + Cheerio only: 256 MB RAM, ~30 businesses per request, no per-business requests.

### Input Parameters

| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
| `searchUrls` | `string[]` | No | – | JustDial category/search URLs — **most reliable mode**; category id goes straight to the internal API, e.g. `https://www.justdial.com/Pune/Stationery-Shops/nct-10453443` |
| `queries` | `string[]` | No | – | Free-text searches used with `city`, e.g. `["plumbers","dentists"]` |
| `city` | `string` | No | `Pune` | Indian city for `queries` (Pune, Mumbai, Bangalore, Delhi, Hyderabad, …) |
| `area` | `string` | No | `""` | Optional locality filter, e.g. `Koregaon Park`; empty = whole city |
| `nationalCatId` | `string` | No | `""` | Optional JustDial national category id (`nct-…`) for `queries` |
| `maxItemsPerSearch` | `integer` | No | `50` | Businesses per search (1–500). **The only limit you need** — page count derives at 30 rows/page; the Actor stops on its own |
| `proxy` | `object` | No | `{"useApifyProxy": true, "apifyProxyCountry": "IN", "apifyProxyGroups": ["RESIDENTIAL"]}` | Indian residential is the reliable setting; swap in `proxyUrls` if the pool is flagged |

Nothing is required, but rows appear only with a `searchUrls` entry or `queries` string. Everything else is internal and tuned: **25 parallel requests, 400 per run, 6 fresh proxy IPs per page, ≤25 pages per search**. Empty run? Almost always the proxy pool — [If every IP is refused](#if-every-ip-is-refused).

### Output Fields

One item per business in the run's default dataset — 19 fields:

| Field | Type | Description |
|---|---|---|
| `name` | string | Business name as listed |
| `category` | string | JustDial category; arrives as `type` on `write-wap*` surfaces, normalised here |
| `phone` | string | Contact number incl. `+91`; absent if none on file |
| `address` | string | Full street address with locality, city, pincode as rendered |
| `locality` | string | Neighbourhood/area without city and pincode |
| `city` | string | Upstream casing varies (`Pune`/`pune`) — compare case-insensitively |
| `pincode` | string | Postal code as string (leading zeroes survive) |
| `rating` | number | Average rating 0–5; `null` when unrated |
| `reviewCount` | number | Total reviews behind `rating`; `null` when none |
| `verified` | boolean | JustDial verified badge; unknown/unverified is `false` |
| `latitude`, `longitude` | number | WGS84 pin coordinates; `null` when not geocoded |
| `thumbnail` | string | Catalogue image URL on JustDial's image CDN; `null` if no photo |
| `profileUrl` | string | Canonical justdial.com profile URL from city + `docId`; `null` only without `docId` |
| `docId` | string | JustDial's stable business id — **dedupe key across runs** |
| `searchTerm`, `searchUrl` | string | The query/category and the search URL that produced the row |
| `apiSurface` | string | Endpoint that answered: `read-wap1`, `read-wap2`, `write-wap1`, `write-wap2` |
| `scrapedAt` | string | UTC ISO 8601 collection timestamp |

> **`hours`, `website` and review text are not available** — no non-JS endpoint serves them. See [Why there are no profile pages](#why-there-are-no-profile-pages).

**Views.** The dataset schema ships with the Actor, so the Output tab renders a table instead of raw JSON:

| View | Shows | Use it for |
|---|---|---|
| **Overview** (default) | photo, name, category, rating, reviews, verified, city, locality, profile link | skimming results |
| **Contact** | name, phone, address, locality, city, pincode, profile link | lead generation |
| **Map data** | name, city, locality, pincode, latitude, longitude | mapping and territory analysis |

Views change Console display only — JSON, CSV and Excel exports always contain all 19 fields. Each field carries `title`/`description`/`example` in the schema, which is what the API returns in the run's `output` property and what AI agents read when chaining this Actor.

### Example Input And Output

#### Category URLs (most reliable mode)

```json
{
  "searchUrls": [
    "https://www.justdial.com/Pune/Stationery-Shops/nct-10453443",
    "https://www.justdial.com/Mumbai/General-Physician-Doctors/nct-10892680"
  ],
  "maxItemsPerSearch": 50,
  "proxy": { "useApifyProxy": true, "apifyProxyCountry": "IN", "apifyProxyGroups": ["RESIDENTIAL"] }
}
```

#### Free-text queries

```json
{ "queries": ["dentists", "plumbers"], "city": "Pune", "maxItemsPerSearch": 50 }
```

#### One dataset item

```json
{
  "docId": "022PXX22.XX22.191204000717.B8L5",
  "name": "Dr Salaah Qureshi",
  "category": "General Physician Doctors",
  "phone": "07947159457",
  "address": "Bon Bon Lane, Juhu Versova Link Road, Andheri West, Mumbai",
  "locality": "Juhu",
  "city": "Mumbai",
  "pincode": "400049",
  "rating": 4.5,
  "reviewCount": 210,
  "verified": true,
  "profileUrl": "https://www.justdial.com/Mumbai/Dr-Salaah-Qureshi/022PXX22-XX22-191204000717-B8L5",
  "searchTerm": "General Physician Doctors",
  "apiSurface": "read-wap1",
  "scrapedAt": "2026-10-02T10:30:00.000Z"
}
```

### Scrape JustDial Business Listings And Phone Numbers Without A Browser

#### The four-surface fallback chain

Four endpoint/`wap` combos are tried **in order**; on a blocked, non-JSON or empty response the Actor falls through, and the first surface that answers is **pinned** for the rest of that search.

| # | `id` | Endpoint | Response shape |
|---|---|---|---|
| 1 | `read-wap1` | `/api/india_api_read/26june2015/searchziva.php?wap=1` | `results[]` — objects with named keys (`docId`, `name`, `VNumber`, `address`, `NewAddress`, `city`, `compRating`, `totalReviews`, `verified`, …) |
| 2 | `read-wap2` | same endpoint, `wap=2` | same as #1 |
| 3 | `write-wap1` | `/api/india_api_write/20march2020/searchziva.php?wap=1` | `results: { columns[], data[][] }` — **columnar** |
| 4 | `write-wap2` | same endpoint, `wap=2` | same as #3 |

All four use the **mobile** `User-Agent`; surface 1 answers first in practice, 2–4 are fallbacks, 3/4 differ in payload shape.

#### The edge keys off User-Agent, not the wap flag

A live probe of all eight combinations (read/write × `wap=1|2` × mobile/desktop) proved it:

| Client | Result |
|---|---|
| mobile UA | 200 JSON on read and write; `wap=1` and `wap=2` byte-identical |
| desktop UA | the 14-byte `<HTML>\n</HTML>` Akamai stub — **4 out of 4** |

Desktop variants were guaranteed failures — and expensive: each dead surface burns a full `maxIpAttempts` round of fresh IPs, so three of them meant **18 of every 30 requests went to endpoints that could never answer**. With only live surfaces, the same IP budget buys roughly 2× real attempts. `/api/resultsPageListing` is dead too (its `sw8` + `search_id`/`trkid`/`jdlt` signature is not reproduced): POST → stub, GET → `{"error":1,"message":"Invalid request"}`, `searchListJson` → 404.

#### Columnar parsing, decoy arrays and the sanity check

Surfaces 3/4 return the same fields columnar (`results.columns` / `results.data`), zipped into named rows — `type`, not `category`. Decoy arrays (`AlsoListedIn`, `nearmeDetails`, `keywordResults`) are business-shaped enough to pass for results, so the Actor reads `results` first. A row is dropped unless it has a real `docId`, a name that is not a copy of the locality, and a rating in 0–5; a whole page failing that check moves the Actor to the next surface instead of reporting "0 matches".

#### Why there are no profile pages (no hours, no website)

Profile-fetching was removed — it cannot work and was harmful:

- The page is a client-rendered React shell (HTTP 200, `window.__INITIAL_STATE__` is `{}`, no ld+json, ~218 KB app bundle, business name absent).
- It redirects to the homepage, overwriting the correct `profileUrl`.
- `detailsFetched: true` was a lie (set even on empty shells).
- Cost ~1 request per business (26 of 29 requests in the diagnostic run) and returned nothing.
- The listing API has **75 fields**, none of them hours or website.

Hours/website/review text need JavaScript — out of scope; the Actor stays Cheerio-only (256 MB, no Chrome).

#### Fingerprint handling: IP reputation, not the fingerprint

`got-scraping`'s default **desktop Chrome 147** spoof (TLS profile + generated headers) contradicts a mobile UA + `wap=1`, so it is disabled in `preNavigationHooks` — plain HTTP/1.1, Node's default TLS, mobile headers only. **IP reputation is what gets refused, not the fingerprint**: a plain residential `fetch` returned 30 listings/page (`totalNumberofResults: 149`) five times out of five until a request burst flipped it to the stub; a browser-grade TLS/HTTP2 fingerprint (`curl-cffi-node`) changed nothing and was dropped rather than carried as a 35 MB native dependency. The probe ran from a **non-Indian** IP (`BA`, Sarajevo) and got full Pune data — not geo-fenced. Only IP rotation works; `maxIpAttempts` sets how hard it tries.

### JustDial Scraper Limits, Anti-Bot Defence And Troubleshooting

JustDial sits behind **Akamai (AkamaiGHost / Bot Manager)**, serving a stub to datacenter and to flagged residential IPs. Measured decision order:

1. **Desktop `User-Agent` → stub immediately** — 4 of 4 requests, even on a perfect IP.
2. **Flagged IP reputation → stub**, regardless of country (an unflagged non-Indian residential IP got full data — not geo-fenced).
3. **`/api/resultsPageListing` → stub on GET and POST** (signature not reproduced).

The Actor detects blocks (403/429/451, `Access Denied`, `errors.edgesuite.net`, or the ~14-byte stub) and logs them; **runs every request on a fresh proxy IP**, retrying up to `maxIpAttempts` times; and falls through to the next surface.

**Blocked runs: set `proxy` to `apifyProxyCountry: "IN"` + `apifyProxyGroups: ["RESIDENTIAL"]`** — the most effective setting, because Indian traffic reads as normal local traffic.

#### Why one IP per request, and no cookie warm-up

The block is **IP-level across the whole domain** and a flagged IP stays flagged, so a sticky session would lock the run onto it. The Actor retires its Crawlee session after every request so Apify hands out a **different IP next time** (the pattern working scrapers here use: fresh session per page, up to 6 IPs). No **cookie warm-up** either — Akamai only issues a usable `_abck`/`bm_sz` after JavaScript runs, and replaying a cookie from a previous IP is an IP/cookie mismatch, a stronger bot signal than none.

#### Platform measurement: datacenter fails, RESIDENTIAL is the setting

From Apify's datacenter IPs **every surface returns HTTP 403 and 0 items**; `apifyProxyCountry: "IN"` helps but those are still datacenter IPs, blocked intermittently. `"apifyProxyGroups": ["RESIDENTIAL"]` is the reliable setting while the pool serves unflagged IPs, which varies. `6 fresh IPs refused` everywhere means the pool is flagged now — use `proxy.proxyUrls` or re-run later. `robots.txt` disallows `/api/`, `/api/india_api_read`, `/_next/` — hit deliberately as the site's own data endpoints.

#### Pricing and run limits

- **CheerioCrawler only** — 256 MB default memory (512 MB max), no browser → low compute cost.
- **`result` $0.0018/business (FREE), $0.00165 at GOLD+**, plus **`Actor Start` $0.00005/run**.
- **400 requests/run** cap + `maxItemsPerSearch` = no runaway; concurrency fixed at **25**.
- **~30 businesses/request** → run cost ≈ `searches × pages`; no per-business requests.
- Free tier: **10-business preview**; paying users: unlimited.
- Residential proxy traffic counts against the account's allowance (a response is tens of KB).

#### Troubleshooting

| Symptom | Cause | Fix |
|---|---|---|
| `🔁 … retrying on a fresh IP (2/6)` | Normal — previous IP refused | Nothing; up to 6 IPs/page |
| `… 6 fresh IPs refused` | Pool handed out blocked IPs | Pool flagged: use `proxy.proxyUrls` or re-run later |
| `🚫 blocked (HTTP …)` | Akamai refused your IP | `proxy` with `apifyProxyCountry: "IN"` + RESIDENTIAL, re-run |
| `🚫 soft-blocked (HTTP 200, empty)` | 200 with an empty envelope — same block, quieter | Same fix; commonest failure, looks like "0 matches" |
| `no usable rows (mis-mapped)` | Rows failed the sanity check | Harmless; next surface. Report if persistent everywhere |
| Empty dataset, `~0 matches` | Query too narrow vs category URL | Use a category `searchUrls` URL, not free-text `queries` |
| `hours` / `website` missing | No non-JS endpoint serves them | Not fixable — see [Why there are no profile pages](#why-there-are-no-profile-pages) |
| Free user sees 10 rows | Preview cap | Expected — upgrade |

#### If every IP is refused

The edge refuses many ranges and the share varies. Measured Oct 2026: one run had **50 consecutive refusals on 50 different IPs** while two egress probes through the same sessions returned HTTP 200 — the proxy worked, JustDial rejected the exit IPs; minutes later, same settings, other runs got clean Indian egress and **10–26 real listings with zero blocks**. The pool is unreliable, not dead: `maxIpAttempts` helps when IPs are flagged, but raising it after repeated refusals only costs time. Part of the refusals was self-inflicted — older builds cycled desktop-UA surfaces that can only return the stub, which is why only JSON-verified surfaces remain.

Each run probes egress via `ipwho.is` (`🌐 Egress IP for this request: … (IN)`). `apifyProxyCountry: "IN"` is a preference, not a guarantee: the pool may serve another country or return `407 … no usable proxies from country 'IN'`. Then use your own proxy:

```json
{ "proxy": { "useApifyProxy": false, "proxyUrls": ["http://user:password@gate.provider.com:8000"] } }
```

`proxyUrls` overrides `apifyProxyGroups`/`apifyProxyCountry` (dropped when set, `ProxyConfiguration` rejects the combination); the first egress-IP log confirms it works.

### Integrations & Automation

- **Apify Console** — run manually, or save the input as an Actor task and schedule it daily/weekly to refresh one category per city.
- **Apify API** — `POST https://api.apify.com/v2/acts/cyha8usQ64pSophWE/runs?token=YOUR_TOKEN` with the input JSON as body; poll `GET https://api.apify.com/v2/actor-runs/{runId}`; read `GET https://api.apify.com/v2/datasets/{datasetId}/items?format=json`.
- **Webhooks** — fire on `actor.run.succeeded` to push rows into your CRM, Sheet or queue the moment a run ends.
- **Zapier / Make** — the Apify integration exposes runs and dataset items as triggers/actions, so each run appends leads automatically.
- **Recurring schedule** — weekly monitoring of one category × one city keeps a lead pool fresh.

### Related Actors

| Actor | Notes |
|---|---|
| [lead-extractor---justdial](https://apify.com/happitap/lead-extractor---justdial) | JustDial leads — 737 users, 5.0★, $0.0028/record |
| [justdial-business-scraper](https://apify.com/thirdwatch/justdial-business-scraper) | 459 users, $0.002/result; claims decoded phone, timing, website, photos, "30M+ listings across 1000+ cities" |
| [justdial-business-scraper](https://apify.com/memo23/justdial-business-scraper) | 214 users, 5.0★, $0.03/business; claims WhatsApp, email, GPS, hours, year, turnover |
| [indiamart-supplier-scraper](https://apify.com/thirdwatch/indiamart-supplier-scraper) | IndiaMART B2B suppliers — 542 users, 4.55★, $0.0017/result |
| [sulekha-scraper](https://apify.com/happitap/sulekha-scraper) | Sulekha local-service listings, same India lead-gen cluster |

### FAQ

#### Is there an official JustDial API?

No public official API exists. RapidAPI wrappers and scraper-API vendors fill the gap; the data actually comes from JustDial's own internal JSON endpoints (`searchziva.php` and friends), which this Actor calls over plain HTTP.

#### What's the best way to get Indian business leads with phone numbers?

Scrape a live directory (JustDial for hyperlocal, phone-first listings), scrape a B2B marketplace (IndiaMART for suppliers), or buy a static database. Live scraping stays current and dedupable — this Actor returns phone, address, pincode, rating, reviews and coordinates per business with `docId` as merge key, browserless.

#### JustDial vs IndiaMART vs Sulekha — which is better for lead generation?

B2B versus hyperlocal: IndiaMART is supplier/manufacturer discovery (its Store actors target "suppliers"); JustDial and Sulekha are local service and retail directories, JustDial the phone-first one. Local consumer leads → JustDial; factory/trade supply → IndiaMART.

#### Why is JustDial blocking my scraper?

Akamai Bot Manager decides in order: desktop UA → 14-byte stub immediately (4 of 4); flagged IP reputation → stub regardless of country; datacenter IPs → HTTP 403. Fix: Indian residential proxies (`apifyProxyCountry: "IN"` + `apifyProxyGroups: ["RESIDENTIAL"]`), one fresh IP per request (up to 6/page) and no cookie replay across IPs.

#### How do I extract JustDial phone numbers without a browser?

Call the internal listing JSON APIs with plain HTTP + Cheerio: `read-wap*` returns objects with named keys (`VNumber` → `phone`), `write-wap*` the same fields columnar. A mobile User-Agent is mandatory — desktop gets the stub on every surface.

#### Is scraping JustDial legal?

JustDial's terms prohibit data mining, scraping and crawling, and `robots.txt` disallows the API paths used here. The output is public business contact data, but running it may breach the ToS and could attract liability under India's IT Act, 2000. Compliance is the user's responsibility.

#### What are the alternatives to this JustDial scraper?

thirdwatch/justdial-business-scraper (459 users, $0.002/result), memo23/justdial-business-scraper (214 users, 5.0★, $0.03/business, claims WhatsApp/email/GPS/hours), happitap/lead-extractor---justdial (737 users, $0.0028/record), tugelbay/justdial-leads-extractor ($0.00288/business), iskander/just-dial-extractor-scraper ($0.005/business); adjacent: IndiaMART, Sulekha, AskLaila and Yellow Pages India actors.

### SEO Keywords

justdial scraper, scrape justdial, justdial api, justdial business listings, justdial phone number extractor, justdial leads extractor, justdial data extraction, indian business leads, india business phone number database, b2b lead generation india, local business leads india, india company contact database, local business directory data india, local seo india data, india market research data, smb prospecting data india, justdial alternative api, justdial alternatives india, indiamart vs justdial, sulekha vs justdial, apify justdial, justdial scraper mumbai, justdial scraper delhi, justdial scraper bangalore, business leads delhi ncr

### For AI Agents & LLM Apps

**Purpose:** extract structured Indian business listings (name, phone, address, rating, coordinates) from JustDial's internal JSON listing APIs without a browser.

Minimal working input:

```json
{ "searchUrls": ["https://www.justdial.com/Pune/Stationery-Shops/nct-10453443"], "maxItemsPerSearch": 50 }
```

Variant — free-text mode: `{ "queries": ["dentists"], "city": "Pune", "maxItemsPerSearch": 50 }`

**Output fields:** `name`, `category`, `phone`, `address`, `locality`, `city`, `pincode`, `rating`, `reviewCount`, `verified`, `latitude`, `longitude`, `thumbnail`, `profileUrl`, `docId`, `searchTerm`, `searchUrl`, `apiSurface`, `scrapedAt`.

**Behaviors an agent should know:**

- No required fields; without `searchUrls`/`queries` a run returns no rows. `searchUrls` is most reliable; `queries` uses `city` (default `Pune`) + optional `area`/`nationalCatId`.
- `maxItemsPerSearch` (default 50, 1–500) is the only limit to set; page count derives at 30 rows/page. Internals: 400 req/run cap, concurrency 25, ≤25 pages/search, 6 fresh IPs/page (`maxIpAttempts`).
- Proxy defaults to Apify residential India; `proxy.proxyUrls` overrides and drops `apifyProxyCountry`/`apifyProxyGroups` (`ProxyConfiguration` rejects the combination).
- `docId` is the dedupe key; `profileUrl` derives from city + `docId`; `apiSurface` names the endpoint; `city` casing varies — compare case-insensitively.
- Rows never include `hours`, `website` or review text — they need JavaScript, which this Actor never runs.
- Empty dataset ≈ flagged proxy pool (`6 fresh IPs refused` / `soft-blocked (HTTP 200, empty results)`), not a wrong query.

**Billing:** pay-per-event — `result` $0.0018/business (FREE; $0.00165 at GOLD+) plus `Actor Start` $0.00005/run.

### Legal & Compliance Disclaimer

- Independent project, **not affiliated with, endorsed by, sponsored by or associated with JustDial / JD Software**.
- It accesses only JustDial's publicly reachable internal listing JSON endpoints over plain HTTP — no login, no account, no CAPTCHA or credential bypass — though those paths (`/api/`, `/api/india_api_read`, `/_next/`) are disallowed by justdial.com `robots.txt` and JustDial's terms prohibit data mining, scraping and crawling.
- Users are solely responsible for their own compliance with JustDial's Terms of Service and applicable data-protection law (India's Information Technology Act, 2000; GDPR/CCPA where they apply).
- The output contains personal contact data (business phone numbers and addresses) and must not be used for unsolicited commercial outreach in violation of applicable law.

# Actor input Schema

## `searchUrls` (type: `array`):

JustDial category or search result URLs, e.g. https://www.justdial.com/Pune/Stationery-Shops/nct-10453443. Most reliable mode — the category id in the URL is passed to JustDial's internal listing API.

## `queries` (type: `array`):

Free-text business searches, used together with `city` (e.g. \["plumbers", "dentists"] + city "Pune"). Use `searchUrls` instead if you have category URLs.

## `city` (type: `string`):

Indian city used with `queries`, e.g. Pune, Mumbai, Bangalore, Delhi, Hyderabad.

## `area` (type: `string`):

Optional locality to narrow the search, e.g. "Koregaon Park". Leave empty for the whole city.

## `nationalCatId` (type: `string`):

Optional JustDial national category id (nct-…) used with `queries` when you know it. Leave empty otherwise.

## `maxItemsPerSearch` (type: `integer`):

How many businesses to collect per search URL / query. The Actor works out how many listing pages that needs (30 per page) and stops on its own, so this is the only limit you need to set.

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

JustDial blocks Apify datacenter IPs outright, so this defaults to Indian residential proxies. Replace with your own Indian residential proxy via `proxyUrls` if this range gets flagged.

## Actor input object example

```json
{
  "city": "Pune",
  "area": "",
  "nationalCatId": "",
  "maxItemsPerSearch": 50,
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyCountry": "IN",
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

## `results` (type: `string`):

Default dataset, one item per business. Field-by-field documentation and the table views are defined in the dataset schema.

# 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 = {
    "maxItemsPerSearch": 50,
    "proxy": {
        "useApifyProxy": true,
        "apifyProxyCountry": "IN",
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("ahmed_jasarevic/justdial-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 = {
    "maxItemsPerSearch": 50,
    "proxy": {
        "useApifyProxy": True,
        "apifyProxyCountry": "IN",
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("ahmed_jasarevic/justdial-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 '{
  "maxItemsPerSearch": 50,
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyCountry": "IN",
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call ahmed_jasarevic/justdial-scraper --silent --output-dataset

```

## MCP server setup

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