# India State e-Tenders Scraper - GePNIC Portals & CPPP (`automatic/gepnic-tenders-scraper`) Actor

Scrape open Indian government tenders from 10 state e-procurement portals (Maharashtra, UP, Kerala, Tamil Nadu, MP and more) and CPPP. One row per tender with buyer, deadline in India time, value, EMD, fees, category, location and document names. Daily new-only feed; JSON, CSV or Excel.

- **URL**: https://apify.com/automatic/gepnic-tenders-scraper.md
- **Developed by:** [Philip Kirkbride](https://apify.com/automatic) (community)
- **Categories:** Lead generation
- **Features:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 tenders

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

## India State e-Tenders Scraper (GePNIC & CPPP)

Find open government tenders on India's state e-procurement portals and the Central Public Procurement Portal (CPPP), and get one clean row per tender: title, tender ID and reference number, buying organisation and its department, state, publication date, bid deadline and opening date in India time, and days left to bid. Turn on details to add the tender value, EMD, tender and processing fees, works/goods/services category, product category, tender type, form of contract, bid validity, period of work, location and PIN code, a short work description and the names of the tender documents.

Covered portals: Uttar Pradesh, Kerala, Tamil Nadu, Madhya Pradesh, Maharashtra, Punjab, West Bengal, Odisha, Haryana, Rajasthan and the central portal (CPPP, eprocure.gov.in). Together they list tens of thousands of open tenders at any time; Uttar Pradesh alone had about 11,800 on 3 November 2026.

You get only tenders that are still open for bids. Schedule a daily run with "published since 2 days" and your organisations or keywords to get tender alerts in email, Slack or a spreadsheet.

Looking for bids on GeM, the Government e-Marketplace? Our **GeM India Tenders Scraper** covers GeM; this actor covers the state and central e-procurement portals, where works contracts and most state department tenders are published.

**Cost.** $0.001 per tender plus a $0.005 run start, so the Console sample of 20 tenders costs about $0.03. See [Pricing](#pricing) for worked examples.

### Quick start

Open tenders in Uttar Pradesh:

```json
{"maxItems": 20}
```

Municipal corporation road tenders in Maharashtra and Tamil Nadu, published in the last 2 days:

```json
{"portals": ["maharashtra", "tamilNadu"], "organisations": ["municipal corporation"], "keywords": ["road"], "publishedSince": "2 days"}
```

Works tenders worth at least Rs 1 crore on the central portal, with details:

```json
{"portals": ["central"], "tenderCategories": ["works"], "minValue": 10000000, "includeDetails": true}
```

The Console opens with `{"portals": ["maharashtra"], "maxItems": 20}`: the open tenders of one state, 20 rows, about $0.03 and under a minute.

### Input

| Field | Type | Default | What it does |
|---|---|---:|---|
| `portals` | list | `["uttarPradesh"]` | One or more of `uttarPradesh`, `kerala`, `tamilNadu`, `madhyaPradesh`, `maharashtra`, `punjab`, `westBengal`, `central` (CPPP), `odisha`, `haryana`, `rajasthan`. They are read one after another. |
| `organisations` | list | `[]` | Buying organisations, any part of the name, case ignored, e.g. `municipal corporation`, `public works`, `Kerala Water Authority`. A name that matches no top-level organisation is looked for among the departments and offices inside them. |
| `keywords` | list | `[]` | Words searched in the tender title, e.g. `road`, `solar`, `hospital`. Several keywords return tenders that match any of them. |
| `tenderCategories` | list | `[]` | `works`, `goods` or `services`. |
| `minValue` | integer | none | Only tenders with a published value of at least this many rupees (1 crore = 10000000). Tenders without a published value are left out. |
| `publishedSince` | date | none | Only tenders published on or after this date (India time), e.g. `2026-11-01`, or within a period such as `2 days`. |
| `closingAfter` | date | none | Only tenders whose bid deadline is on or after this date, e.g. `2026-11-15`, or a period ahead such as `7 days`. |
| `maxItems` | integer | `50` | Maximum tenders returned, up to 5,000. |
| `includeDetails` | boolean | `false` | Add the detail fields listed below. |

`tenderCategories`, `minValue` and `includeDetails` can increase run time. Category and value filters also include the detail fields. Selective filters can return fewer tenders than `maxItems` within the run's search limits; add `organisations` or `keywords` to narrow the search.

### Use it as a daily feed

Turn on **Only new or changed since the last run** (`onlyNewSinceLastRun`) and run the
Actor on a schedule with the same input. It then returns only tenders this search has not
delivered before, or whose watched fields changed since the last run, so there is nothing
to deduplicate on your side.

- Every returned row carries `changeType` (`new` or `changed`) and `changedFields`.
- The first run has no saved history, so it returns everything and reports
  `feed.firstRun` in the run's OUTPUT.
- The history lives in the named key-value store `gepnic-tenders-scraper-feed-state`, one record per
  search. Leave `stateKey` empty to name the record from the input, so different searches
  keep separate histories; set a name to share one or to start a fresh feed.
- An entry leaves the history once its deadline is more than 90 days past and it has not been delivered in the last 90 days, so the history stays bounded.
- The watched fields are the title, the deadline, the status, the estimated value and the documents.

With the flag off (the default) the Actor returns every match, exactly as before.

### What it returns

Every row has `tenderId`, `tenderReference`, `title`, `buyer` (`name`, `id`, `unit`), `organisationChain`, `country`, `region` (the state; empty for CPPP), `portal`, `portalName`, `publishedAt`, `deadline`, `bidOpeningAt`, `currency`, `status`, `isOpenForBids`, `daysUntilDeadline`, `url`, `detailsParsed`, `detailsError`, `sourceQuery`, `source` and `scrapedAt`.

With details on, rows also fill `procurementMethod` (e.g. Open Tender, Limited), `tenderCategory`, `formOfContract`, `primaryProductCategory`, `productSubCategory`, `estimatedBidValue`, `emdAmount`, `tenderFee`, `processingFee`, `bidSubmissionStartAt`, `documentDownloadEndAt`, `offerValidityDays`, `periodOfWorkDays`, `location`, `pincode`, `descriptionExcerpt` (up to 600 characters), `documents` (each with `name`, `documentType` and `sizeKb`) and `corrigendumCount`. These keys are present but empty when details are off. If one tender page cannot be read, the row is still delivered with `detailsError` set.

Dates and times are ISO 8601 with the India offset (`+05:30`). Amounts are numbers in Indian rupees (INR). Field names such as `estimatedBidValue`, `emdAmount`, `bidOpeningAt`, `offerValidityDays` and `primaryProductCategory` match our GeM India Tenders Scraper, so the two datasets combine easily.

`url` opens the portal's official "Tenders by Organisation" page, where the tender is listed under its organisation; search for the tender ID there to open the notice and download the documents from the portal.

### What can be empty, and why

- `estimatedBidValue` is empty when the buyer did not publish a value; many tenders say "NA".
- `buyer.unit` is empty when the tender was published by the organisation itself rather than a department or office under it.
- Some buyers (notably on the Haryana portal) publish short titles that end in "..." ("A/mtc of Water Supply Schem..."). The portal holds no longer title; the tender documents describe the full scope.
- `descriptionExcerpt` is empty when the buyer published no description.

### Limits

- Only open tenders are available: awarded, cancelled and archived tenders are not part of the output.
- Tenders come organisation by organisation, in the portal's alphabetical order. Use `organisations` to pick buyers, or raise `maxItems` to cover a whole portal.
- Broad portal searches and runs with details can take several minutes. Narrow the organisations or keywords for a smaller, faster collection.
- Large buyers can have thousands of open tenders, so a selective result set may still take time to collect.
- Document files themselves are not downloaded: rows list the document names, type and size; download the files from the official portal.

### Privacy

Titles, descriptions, locations and document names are delivered exactly as the portal publishes them, including any names, e-mail addresses or phone numbers the buyer wrote into that text. The officials' contact block on the tender page is not part of the output.

### FAQ

**How do I get only new tenders each day?** Turn on `onlyNewSinceLastRun` (see above) and schedule a run with the same input; the Actor then returns only titles this search has not delivered before, or whose watched fields changed. Without the flag, schedule a daily run with `publishedSince: "2 days"` and your filters, and deduplicate on `tenderId`: each run is independent, so overlapping windows deliver (and charge for) the same tenders again.

**How much does a run cost?** $0.001 per delivered tender plus a $0.005 run start, so 20 tenders cost $0.025 and 100 cost $0.105. Filters, details and pages read are not charged separately; the start charge applies even when a search matches nothing.

**Why did I get fewer tenders than `maxItems`?** Either nothing else matched in the time the run had, or a category/value filter ran out of the pages a run may check. Add `organisations` or `keywords` to narrow the search instead of raising `maxItems`. The run's status message says which limit applied.

**How long does a run take?** About 2 seconds of reading per portal plus 3 seconds for each tender page read for `includeDetails`, `tenderCategories` or `minValue`. Twenty tenders with details take about a minute; a broad portal sweep or a value filter over thousands of tenders can take much longer.

**What happens when nothing matches?** The run succeeds with an empty dataset and a status message saying no tenders (or no organisation) matched. The run start is still charged.

**Does it cover GeM?** No. Use our GeM India Tenders Scraper for GeM bids and reverse auctions.

**Can I run it on a schedule?** Yes. Save the input you like, then add a daily or hourly schedule on the Schedules tab; each run returns the tenders that match at that time. Use it together with `onlyNewSinceLastRun` for a clean daily feed.

### Pricing

- **$0.001 per delivered tender** — one charge per row in the dataset, whether or not the buyer published a value or a document.
- **$0.005 per run start**, charged per GB of memory, so the default 512 MB run is one start charge. It is charged even when a run delivers no rows.
- Reading filters, detail pages and portal pages adds run time, not charges.

| Run | Rows | Cost |
|---|---:|---:|
| Console sample: Maharashtra, `maxItems` 20 | 20 | $0.025 |
| Daily alert: `publishedSince: "2 days"`, 50 tenders | 50 | $0.055 |
| Sweep of one large portal | 1,000 | $1.005 |

See the Pricing tab for the current price.

### Output example

```json
{"tenderId": "2026_ADAUP_1204856_1", "tenderReference": "33/CE/18/DA/2026", "title": "Lighting installation work on Mahatma Gandhi Marg.", "buyer": {"name": "Allahabad Development Authority", "id": "ADAUP", "unit": null}, "organisationChain": ["Allahabad Development Authority"], "country": "IN", "region": "Uttar Pradesh", "portal": "uttarPradesh", "portalName": "Uttar Pradesh", "publishedAt": "2026-11-03T17:15:00+05:30", "deadline": "2026-11-26T14:00:00+05:30", "bidOpeningAt": "2026-11-26T15:00:00+05:30", "procurementMethod": "Open Tender", "tenderCategory": "Works", "formOfContract": "Fixed-rate", "primaryProductCategory": "Electrical Works", "productSubCategory": null, "estimatedBidValue": 4153000.0, "emdAmount": 83000.0, "tenderFee": 590.0, "processingFee": null, "bidSubmissionStartAt": "2026-11-03T17:15:00+05:30", "documentDownloadEndAt": "2026-11-26T14:00:00+05:30", "offerValidityDays": 90, "periodOfWorkDays": 90, "location": "7th 8th floor indra bhawan praygaraj", "pincode": "211001", "descriptionExcerpt": "Lighting installation work on Mahatma Gandhi Marg.", "documents": [{"name": "Tendernotice_1.pdf", "documentType": "NIT Document", "sizeKb": 1977.99}, {"name": "BOQ_2282374.xls", "documentType": "BOQ", "sizeKb": 307.5}], "corrigendumCount": 0, "currency": "INR", "status": "open", "isOpenForBids": true, "daysUntilDeadline": 23, "url": "https://etender.up.nic.in/nicgep/app?page=FrontEndTendersByOrganisation&service=page", "detailsParsed": true, "detailsError": null, "sourceQuery": "portals=uttarPradesh", "source": "GePNIC (NIC e-procurement)", "scrapedAt": "2026-11-03T17:29:00.643946+00:00"}
```

### Support

For questions or issues, contact us from the Actor page.

# Actor input Schema

## `portals` (type: `array`):

Which portals to read, one after another: ten state portals and the central portal (CPPP).

## `organisations` (type: `array`):

Buyer organisations, any part of the name, case ignored, e.g. 'municipal corporation' or 'public works'. Empty: every organisation. Up to 30.

## `keywords` (type: `array`):

Words searched in the tender title, e.g. 'road' or 'solar'. Several keywords return tenders matching any of them. Up to 20.

## `tenderCategories` (type: `array`):

Only works, goods or services tenders. Slower: each tender is checked on its own page (about 3 seconds each), and a run checks at most 25 tenders per maxItems (500 at most), so a rare category can return few tenders.

## `minValue` (type: `integer`):

Only tenders with a published value of at least this many rupees. Tenders without a published value are left out. Slower: each tender is checked on its own page (about 3 seconds each), and a run checks at most 25 tenders per maxItems (500 at most), so a high value can return few tenders.

## `publishedSince` (type: `string`):

Only tenders published on or after this date (India time), e.g. 2026-11-01, or within a period such as 2 days.

## `closingAfter` (type: `string`):

Only tenders whose bid deadline is on or after this date, e.g. 2026-11-15, or a period ahead such as 7 days.

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

Maximum tenders returned, up to 5,000.

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

Add value, EMD, fees, category, tender type, location, description and document names from each tender page (about 3 seconds per tender).

## `onlyNewSinceLastRun` (type: `boolean`):

Return only tenders this search has not delivered before, or whose title, deadline, status, value or documents changed since the last run. Keep the same input and schedule to get a clean daily feed.

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

Optional name for the saved feed state. Leave empty to derive it from the input, so different searches keep separate histories. Set a new name to start the feed over.

## Actor input object example

```json
{
  "portals": [
    "maharashtra"
  ],
  "organisations": [],
  "keywords": [],
  "tenderCategories": [],
  "maxItems": 20,
  "includeDetails": false,
  "onlyNewSinceLastRun": false,
  "stateKey": ""
}
```

# Actor output Schema

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

No 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 = {
    "portals": [
        "maharashtra"
    ],
    "organisations": [],
    "keywords": [],
    "maxItems": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("automatic/gepnic-tenders-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 = {
    "portals": ["maharashtra"],
    "organisations": [],
    "keywords": [],
    "maxItems": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("automatic/gepnic-tenders-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 '{
  "portals": [
    "maharashtra"
  ],
  "organisations": [],
  "keywords": [],
  "maxItems": 20
}' |
apify call automatic/gepnic-tenders-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,automatic/gepnic-tenders-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/rcKByIqHT9GkXTI8W/builds/6WxZpwtQ1nSdkvhtm/openapi.json
