# Sulekha Business Directory Scraper (`automation-lab/sulekha-city-business-directory`) Actor

Refresh Sulekha city business lists: discover category/city listings, enrich public profiles, and export names, nullable displayed phones, addresses and source URLs. No hidden contacts or Justdial.

- **URL**: https://apify.com/automation-lab/sulekha-city-business-directory.md
- **Developed by:** [Automation Lab](https://apify.com/automation-lab) (community)
- **Categories:** Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.79 / 1,000 business exporteds

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

## Sulekha Business Directory Scraper

Refresh a Sulekha business directory from supplied Indian city/category URLs. Discover linked businesses and enrich their public profiles into names, publicly displayed telephone links when available, physical address components and source provenance.

Built for local-market researchers and agencies maintaining recurring city business lists. Phones can be null, addresses can be partial, and the Actor does not promise every business in a city.

### Who is it for?

Local-market researchers and agencies refreshing public Indian city business directories. This is a source-attributed list export, not a verified sales-contact database.

### What does this Actor do?

- Accepts Sulekha category/city directories and public business profile URLs.
- Reads public HTML and structured business address markup.
- Enriches discovered profiles, rather than exporting directory summary cards as full addresses.
- Deduplicates canonical profile URLs across the run.
- Follows actual next-page links within a directory when supplied by the source.
- Exports typed rows to the default dataset for JSON, CSV and Excel downloads.

It does not scrape Justdial, purchase leads, submit enquiry forms, reveal hidden contacts, or access accounts.

### Why use it?

A contact list is more useful when each row includes where it came from and when it was observed. Keep `sourceUrl` as the join key in your own refresh pipeline and compare later snapshots downstream. No built-in change history, alerts or cross-source deduplication is implied.

### Getting started

1. Copy a public Sulekha category/city URL, such as `https://www.sulekha.com/plumbers/chennai`.
2. Add it to **Directory or profile URLs**.
3. Start with ten businesses and one directory page.
4. Run the Actor and inspect both its status and dataset.
5. Export the dataset using the Console's JSON, CSV or Excel download options.

```json
{
  "startUrls": ["https://www.sulekha.com/plumbers/chennai"],
  "maxItems": 10,
  "maxPages": 1
}
```

### Inputs

| Field | Behavior |
| --- | --- |
| `startUrls` | Required, 1–20 HTTPS Sulekha directory or profile URL strings. |
| `maxItems` | Global unique business-row limit after deduplication; default 10, minimum 1, maximum 100. No zero/unlimited mode. |
| `maxPages` | Directory-page cap per supplied directory; default 1, range 1–5. Direct profiles ignore this setting. |

Only `page` query parameters are supported. Fragments and trailing slashes are normalized. Login, enquiry and unrelated URLs are unsupported. There are no keyword matching or phone-presence filters.

Each directory contributes businesses in source order until the global row limit is reached. Duplicate URLs are skipped; distinct source URLs for the same real-world company may remain separate. If the same business is discovered twice, its first discovery determines `directoryUrl` and category/city context.

### Extracted fields

| Field | Meaning |
| --- | --- |
| `businessName` | Public profile name. |
| `phone` | First visible `tel:` link, or null; may be a source routing number. |
| `address` | Joined public address components; may be partial or null. |
| `streetAddress` | Structured street address, unchanged except whitespace. |
| `locality`, `region` | Listed address locality and region, independently nullable. |
| `postalCode`, `country` | Listed postal code and country, independently nullable. |
| `category`, `city` | Discovery directory slugs; direct profiles have null category and may use public areaServed city. |
| `sourceUrl` | Final profile URL used for extraction. |
| `directoryUrl` | Supplied discovery directory, or null for direct profiles. |
| `phoneSource` | `visible_tel_link` when a phone is returned, otherwise null. |
| `observedAt` | UTC extraction time, not the business's last-update time. |

### Output example

The following public business was observed during local verification. Source content can change.

```json
{
  "businessName": "Saira Electricals And Plumbing Works",
  "phone": "08048051027",
  "address": "No-44/1,Karunanidhi 1st Street, Nandhavana mettur, Thiruvallur, Avadi, Chennai - 600054, Chennai, 600054, IN",
  "streetAddress": "No-44/1,Karunanidhi 1st Street, Nandhavana mettur, Thiruvallur, Avadi, Chennai - 600054",
  "locality": "Chennai",
  "region": null,
  "postalCode": "600054",
  "country": "IN",
  "category": "plumbers",
  "city": "chennai",
  "sourceUrl": "https://www.sulekha.com/profile/saira-electricals-and-plumbing-works-avadi-chennai",
  "directoryUrl": "https://www.sulekha.com/plumbers/chennai",
  "phoneSource": "visible_tel_link",
  "observedAt": "2026-10-27T15:04:48.695Z"
}
```

### How much does it cost to extract Sulekha businesses?

Pay per event: one start event per valid run plus one item event per unique exported business. There is no separate event for directory pages, requests or address fields. Duplicate and failed profiles are not charged as business rows.

Current BRONZE price: $0.001 per run plus $0.002989 per business. Ten businesses cost $0.03089 and 100 cost $0.2999. Per-business prices are FREE $0.0034373, BRONZE $0.002989, SILVER $0.0023314, and GOLD/PLATINUM/DIAMOND $0.0017934. Spend tiers follow qualifying aggregate monthly Store spend, not a per-Actor volume schedule; the Store pricing panel shows the applicable price. These are Actor charges, not infrastructure-cost estimates. A failed run may retain its start charge and charges for already exported rows. Set a suitable total-charge limit in Console.

### Coverage and pagination

The Actor follows only actual `rel=next` links and never guesses page numbers. Some directories expose only an initial set of public listings; a high maxPages does not create more results. Load-more forms and hidden pagination APIs are not used. The tested Chennai plumbers directory exposes an initial twenty-item list, not a complete city census.

Residential India HTTP proxy is automatic and required. There are no user-configurable proxy modes or browser fallbacks. Requests are sequential with at least 500 ms spacing, up to three attempts per page, fresh proxy identity after failures and exponential backoff. No images or browser assets are downloaded.

### Failures and troubleshooting

A blocked response, changed markup, wrong profile identity or exhausted request fails the run rather than masquerading as an empty directory. Already saved rows are partial when the run fails. A recognized directory explicitly declaring zero items can succeed with an empty dataset.

The `SUMMARY` key-value record contains request counts, decoded HTML response bytes, retries, directory pages, profiles and saved rows. Its byte count is not compressed/billed proxy transfer. It is written on normal completion. A billing limit can stop output before maxItems.

If a run times out with a large limit, reduce the batch or increase its timeout. Confirm the directory is public and correctly formatted before retrying a failed URL. Persistent challenges or schema changes should be reported with the run link.

### Integrations

Schedule recurring executions with Apify schedules, then use a webhook or dataset integration to append rows to a spreadsheet, CRM staging table or data warehouse. Compare `sourceUrl` keys and observation times in your own pipeline. Verify contact accuracy and consent before any outreach; this Actor does not perform messaging or qualification.

### API usage

Use your Apify token securely; do not paste it into shared URLs or logs.

```bash
curl -X POST 'https://api.apify.com/v2/acts/automation-lab~sulekha-city-business-directory/runs' \
  -H "Authorization: Bearer $APIFY_TOKEN" -H 'Content-Type: application/json' \
  -d '{"startUrls":["https://www.sulekha.com/plumbers/chennai"],"maxItems":10}'
```

JavaScript with `apify-client`:

```js
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('automation-lab/sulekha-city-business-directory').call({
  startUrls: ['https://www.sulekha.com/plumbers/chennai'], maxItems: 10,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems({ limit: 10 });
```

Python with `apify-client`:

```python
import os
from apify_client import ApifyClient
client = ApifyClient(os.environ['APIFY_TOKEN'])
run = client.actor('automation-lab/sulekha-city-business-directory').call(
    run_input={'startUrls': ['https://www.sulekha.com/plumbers/chennai'], 'maxItems': 10})
items = client.dataset(run['defaultDatasetId']).list_items(limit=10).items
```

### MCP usage

For clients supporting authenticated HTTP MCP:

```bash
claude mcp add --transport http apify \
  "https://mcp.apify.com?tools=automation-lab/sulekha-city-business-directory"
```

Equivalent client configuration for Claude Desktop, Cursor, and VS Code:

```json
{"mcpServers":{"apify":{"url":"https://mcp.apify.com?tools=automation-lab/sulekha-city-business-directory"}}}
```

Example prompts: “Export ten Chennai plumber listings with their source URLs” or “Read the first twenty rows from the completed run.”

Discover actual tool names and schemas with `tools/list`. Actor selection also adds run and dataset tools; it does not expose exactly one tool. Example request: “Extract ten public Sulekha businesses from this Chennai plumbers directory and show their names, nullable phones and source URLs.”

Start once and retain its run and storage IDs. If the call returns before completion, follow that same run using bounded backoff (2, 4, 8 seconds, capped at 10) and a 120-second total deadline. On timeout, check the known run instead of restarting it. At deadline, report pending with the run ID. Failed runs have only partial results.

After success, read dataset pages of twenty rows, at most 100 rows and 64 KiB of serialized UTF-8 results admitted to model context, whichever comes first. Enforce bytes host-side, disclose omissions, track source offsets and report continuation when truncated. If the client cannot intercept oversized pages, do not claim a hard byte ceiling. Full exports remain in storage outside model context. This consumer budget does not change the Actor's collection contract.

### Legality and responsible use

Use public business information only for lawful purposes and comply with applicable source terms, privacy, marketing and data-retention requirements. Public availability is not permission for unsolicited marketing. No private accounts, contact-reveal workflows or personal enrichment are supported.

The Actor uses no AI models at runtime. It is independently developed and is not affiliated with or endorsed by Sulekha. Residential proxy and platform costs are paid by the developer through the documented Actor charges; no external account is needed.

Results and logs remain in your Apify run storage according to your account's retention settings; delete datasets and key-value stores from Console when no longer required. No separate business-data cache is maintained. Proxy identities are ephemeral. For support, open a Store issue with the run link, excluding credentials and private data.

Failed operations send sanitized diagnostic input, exceptions and Actor/build/run IDs to our private GlitchTip service for repair. Secret fields and URL queries are removed and reports are retained for 30 days.

### FAQ

**Why is phone null?** The public profile may not display a telephone link. The Actor does not reveal or infer numbers.

**Why fewer rows than requested?** The public listing set may end, pagination links may be absent, duplicate URLs may be removed, or the charge limit may stop output.

**Are numbers verified?** No. A displayed number can be a routing number or stale. Verify independently.

**Can it scrape Justdial too?** No. This product is Sulekha-only and makes no cross-source matching claim.

### Related Actors

For a different source, see [Google Maps Business Directory Scraper](https://apify.com/automation-lab/google-maps-keyword-location-directory). Its coverage and access behavior are separate; neither product guarantees the other's listings.

# Changelog

This Actor's version history is a separate document: https://apify.com/automation-lab/sulekha-city-business-directory/changelog.md

# Actor input Schema

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

Supply 1–20 HTTPS sulekha.com category/city directories or public business profiles. Directories discover profiles; each profile is enriched from public HTML. Duplicate profile URLs are saved once globally. Only page query parameters are allowed. No Justdial, enquiry or hidden-contact endpoints.

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

Global maximum unique enriched business rows across all supplied URLs, after URL deduplication. Default 10; range 1–100. Zero and unlimited are unsupported. Fewer rows are returned if discovered listings end or the billing limit stops output; source coverage is not guaranteed.

## `maxPages` (type: `integer`):

Default 1, range 1–5 per supplied directory. Follows only actual rel=next links within that directory; does not invent page numbers or interact with load-more forms. Ignored for direct profiles. Global maxItems can stop collection earlier.

## Actor input object example

```json
{
  "startUrls": [
    "https://www.sulekha.com/plumbers/chennai"
  ],
  "maxItems": 10,
  "maxPages": 1
}
```

# Actor output Schema

## `dataset` (type: `string`):

Default dataset containing enriched public business contact rows.

## `summary` (type: `string`):

Request counts, decoded response bytes and coverage limits on normal completion.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "startUrls": [
        "https://www.sulekha.com/plumbers/chennai"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation-lab/sulekha-city-business-directory").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "startUrls": ["https://www.sulekha.com/plumbers/chennai"] }

# Run the Actor and wait for it to finish
run = client.actor("automation-lab/sulekha-city-business-directory").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "startUrls": [
    "https://www.sulekha.com/plumbers/chennai"
  ]
}' |
apify call automation-lab/sulekha-city-business-directory --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,automation-lab/sulekha-city-business-directory"
        }
    }
}
```

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/zfGpM6PYZZNnHk7iG/builds/RYI4VmKOBc042yLnm/openapi.json
