# GeM India Tenders Scraper (`barefoot_grade/gem-tenders-scraper`) Actor

Search India's Government e-Marketplace (GeM) bids: bid number, items, quantity, ministry, department, start and end dates, status and the bid document link. Filter by keyword, ministry, organisation, state, type and end date; optional EMD, estimated value and organisation details.

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

## Pricing

from $1.00 / 1,000 bids

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

## GeM India Tenders Scraper

Search bids and reverse auctions on India's Government e-Marketplace (GeM, bidplus.gem.gov.in), the national procurement portal for central and state government buyers, and get one clean row per bid: bid number, items, quantity, ministry, department, start and end dates, status, evaluation stage and the link to the bid document. Turn on details to add the buying organisation and office, the bid opening time, offer validity, estimated bid value, EMD and performance-guarantee terms, turnover and experience criteria, type of bid and evaluation method.

Use it for tender monitoring and alerts: schedule a daily run with "started in the last 1 day" and a keyword, ministry or state, and send the new rows to email, Slack or a spreadsheet.

### Quick Start

Open bids closing soonest:

```json
{"maxResults": 10}
```

New laptop bids from the last two days:

```json
{"keyword": "laptop", "startedWithinDays": 2}
```

Open Central Railway bids with details:

```json
{"ministry": "railways", "organization": "central railway", "includeDetails": true}
```

### Input

| Field | Type | Default | What it does |
|---|---|---:|---|
| `keyword` | string | `""` | Text searched across the bid (items, bid number, buyer), 3 to 100 characters. GeM matches loosely, so results are broad. |
| `keywordMatch` | string | `contains` | `contains` or `exact` (exact phrase). |
| `status` | string | `open` | `open`, `cancelled`, `results` (all result stages), `technicalEvaluation`, `financialEvaluation` or `awarded`. |
| `bidType` | string | `all` | `product`, `service`, `bidToRA`, `customProduct`, `boq`, `rateContract`, `globalTender`, `limitedTender` or `singleTender`. |
| `highValueOnly` | boolean | `false` | Only bids with a declared estimated value of Rs 2 crore or more. |
| `bidEndFrom`, `bidEndTo` | string | `""` | Bid end date window, `YYYY-MM-DD` (India time). |
| `startedWithinDays` | integer | none | Only bids that started (were published) in the last N days, today included. |
| `startedFrom`, `startedTo` | string | `""` | Bid start date window, `YYYY-MM-DD`. |
| `sort` | string | `default` | `endingSoonest`, `endingLatest`, `newest` or `oldest`. |
| `ministry` | string | `""` | Buyer ministry, full name or any unique part of it, e.g. `railways`. |
| `buyerState` | string | `""` | State government buyer, e.g. `Karnataka` (instead of a ministry). |
| `organization`, `department` | string | `""` | Organisation under the ministry or state, then a department under it. |
| `consigneeState`, `consigneeCity` | string | `""` | Where the goods or services are delivered. |
| `bidNumber` | string | `""` | One bid or RA by number, e.g. `GEM/2026/B/8123845`, in any status. |
| `maxResults` | integer | `100` | Maximum bids returned. Up to 10,000, or 750 with `includeDetails` on. |
| `includeDetails` | boolean | `false` | Add the detail fields listed below (slower). |
| `proxyConfiguration` | object | India | Keep the default: GeM only accepts visitors from India. |

The ministry, state, organisation and consignee filters use GeM's advanced search. It covers open bids only, so those filters cannot be combined with `keyword`, `bidType`, `highValueOnly`, `sort` or another `status`. The bid end and start date filters work with everything. Names are matched without regard to case, and a fragment works when it matches one name only; otherwise the run stops and lists the matching names.

### What it returns

Every row has `bidKey`, `bidNumber`, `bidId`, `bidKind` (`bid`, `reverseAuction` or `directReverseAuction`), `parentBidNumber`, `parentBidId`, `items`, `boqTitle`, `quantity`, `ministry`, `department`, `startAt`, `endAt`, `status` (`open`, `closed` or `cancelled`), `isOpenForBids`, `evaluationStage`, `isHighValue`, `isRateContract`, `isGlobalTender`, `isLimitedTender`, `isSinglePacket`, `isBoq`, `categoryId`, `url` (the bid document), `parentBidUrl`, `sourceQuery`, `source` and `scrapedAt`.

With `includeDetails` on, rows also fill `organisation`, `office`, `bidOpeningAt`, `offerValidityDays`, `estimatedBidValue`, `emdRequired`, `emdAmount`, `epbgRequired`, `epbgPercent`, `minimumTurnover`, `pastExperienceYears`, `pastPerformancePercent`, `bidToRaEnabled`, `typeOfBid`, `evaluationMethod` and `primaryProductCategory`. These keys are present but empty when details are off. If one bid's document cannot be read, the row is still delivered with `detailsError` set. `detailsParsed: true` means at least one detail field was extracted from the document; it does not promise complete financial terms. Those rows carry the details charge. A document parse error leaves all detail fields null and sets `detailsError`.

Dates and times are ISO 8601 with the India offset (`+05:30`). Amounts are numbers in Indian rupees (INR); turnover given in lakh or crore is converted to rupees. Buyer contact e-mails printed on bid documents are never returned.

### Output example

```json
{"bidKey":"bidplus.gem.gov.in:bid:GEM/2026/B/8159047","bidNumber":"GEM/2026/B/8159047","bidId":10033114,"bidKind":"bid","parentBidNumber":null,"parentBidId":null,"items":"Milk Testing Equipment,Milk Collection Accessories,Furniture & Fixture","boqTitle":null,"quantity":33,"ministry":null,"department":"Animal Husbandry and Fisheries Department Karnataka","startAt":"2026-10-15T12:12:32+05:30","endAt":"2026-10-30T13:00:00+05:30","status":"open","isOpenForBids":true,"evaluationStage":"Not evaluated","isHighValue":false,"isRateContract":false,"isGlobalTender":false,"isLimitedTender":false,"isSinglePacket":false,"isBoq":false,"categoryId":"home_univ_univ_univ_univ","url":"https://bidplus.gem.gov.in/showbidDocument/10033114","parentBidUrl":null,"sourceQuery":"buyerState=KARNATAKA status=open","source":"bidplus.gem.gov.in","bidOpeningAt":"2026-10-31T13:00:00+05:30","offerValidityDays":90,"organisation":"Karnataka Milk Federation Ltd","office":"Dkmul","estimatedBidValue":null,"emdRequired":true,"emdAmount":50000.0,"epbgRequired":true,"epbgPercent":5.0,"minimumTurnover":null,"pastExperienceYears":3,"pastPerformancePercent":80.0,"bidToRaEnabled":true,"typeOfBid":"Two Packet Bid","evaluationMethod":"Total value wise evaluation","primaryProductCategory":"Milk Testing Equipment","detailsError":null,"detailsParsed":true,"scrapedAt":"2026-10-27T01:27:22.899046+00:00"}
```

### Delivery and schedules

Schedules are stateless: each run searches independently and does not remember earlier deliveries. Overlapping date windows deliver duplicate rows across runs, and those delivered rows are charged again. Deduplicate downstream using `bidKey`.

Rows are saved page by page, so earlier pages remain available if a later listing request fails. Runs reserve 30 seconds before the platform timeout for final writes and status; without a run timeout setting, the budget defaults to 60 minutes minus that reserve. The scan limit across all status tabs is the greater of 500 pages or twice the pages needed for `maxResults` (10 rows per page), so sparse filters remain bounded. A budget expiry during document enrichment saves the current batch as base rows with a `detailsError` and no details charge.

The final status includes a stop reason and counts of invalid rows and skipped rows (duplicates or rows outside the date windows):

| Stop reason | Meaning |
|---|---|
| `complete` | The available search pages or date window were exhausted. |
| `maxResults` | The requested row limit was reached. |
| `timeBudget` | The remaining run time was exhausted; results can be partial. |
| `scanCap` | The request-sized scan limit was reached; narrow the search window. |
| `chargeLimit` | The maximum charge cannot cover another row with the requested details allowance. |
| `sourceTruncation` | Three consecutive pages contained no new usable identities, or an empty or short page arrived while the source total said more rows existed; the source stopped making progress. Results can be partial. |

A row needs a numeric bid id or a non-empty bid number. Rows missing both are counted as invalid. If GeM reports results but none has a usable identity, the run fails with `SOURCE_CHANGED`; a genuine zero-result search succeeds. Rows without a usable start time are skipped when a start-date filter is active. When only a numeric id is available, `bidNumber` is null and `bidKey` uses `bidplus.gem.gov.in:id:<id>`. When only a bid number is available, `url` links to the search page and detail lookup is skipped with a row-level error.

### Limits and caveats

- GeM lists 10 bids per page; listing runs deliver several hundred bids a minute (100 bids took about 10 seconds in our tests). Detail runs read one bid document per bid and are much slower (about 4 seconds per bid on the default 1 GB memory, so about 50 minutes for 750 bids); with `includeDetails` on a run returns at most 750 bids and fits the default 1-hour timeout. For more, split the search by end date, ministry or state.
- `ministry` is empty for state-government buyers, and their bid documents often give the organisation as "N/a" (returned as null); `department` and `office` then name the buyer.
- GeM publishes an estimated value only when the buyer declared one, so `estimatedBidValue` is often empty. Award values and bidder lists are not part of the output.
- Scheduled reverse auctions link to a list of schedules instead of one document; their detail fields stay empty and `detailsError` says why.

### FAQ

**How fresh is the data?** Every run reads GeM live.

**How do I get only new bids each day?** Schedule a daily run with `startedWithinDays: 1` (or 2, to be safe around midnight India time) and your filters. Use `bidKey` to deduplicate downstream; overlapping schedules deliver and charge for the same bids again.

**Why can't I combine a ministry with a keyword?** GeM's own search offers the two separately. Run the ministry search and filter the `items` column, or run a keyword search and filter the `ministry` column.

**What happens when nothing matches?** The run succeeds with an empty dataset.

### Price

You pay per event. Platform usage is included in the price.

- **Run start: $0.005.** The run-start event is charged per GB of memory; the default 1024 MB run is one start event. The start is charged even if the run saves no results.
- **Bid: $0.001** for each delivered bid row.
- **Bid details: $0.003** extra for each delivered bid whose document (PDF) was read and its details filled (`detailsParsed` is `true`).

A listing run costs about $1 per 1,000 bids; with `includeDetails` on, about $4 per 1,000 bids. You are never charged for details when `includeDetails` is off, or for a bid whose document failed to download, could not be read, or does not exist (scheduled reverse auctions); those rows cost $0.001 like any other bid.

**Capping a run:** set a maximum cost per run (`maxTotalChargeUsd`) in the run options. The run stops cleanly when the next bid, with its details charge if any, would go over the cap: you are never charged more than the cap, and the dataset holds only bids the cap covered. The status message says when the cap stopped the run.

### Support

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

# Actor input Schema

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

Text searched across the bid (items, bid number, buyer), as in the keyword box on the GeM all-bids page, e.g. 'laptop' or 'CCTV'. 3 to 100 characters. GeM matches loosely: 'Ministry of Railways' as a keyword finds about 31,000 open bids, against about 1,300 with the Ministry filter.

## `keywordMatch` (type: `string`):

'Contains' is GeM's default full-text search; 'Exact' asks for the exact phrase.

## `status` (type: `string`):

Which bids to read. 'Open' (accepting offers) is the usual choice for alerts; the result stages cover bids already closed.

## `bidType` (type: `string`):

GeM's 'By Type' filter.

## `highValueOnly` (type: `boolean`):

Only bids whose buyer declared an estimated value of Rs 2 crore or more.

## `bidEndFrom` (type: `string`):

Only bids closing on or after this date (India time), YYYY-MM-DD.

## `bidEndTo` (type: `string`):

Only bids closing on or before this date (India time), YYYY-MM-DD.

## `startedWithinDays` (type: `integer`):

Only bids published (bid start date) in the last N days, counting today in India time. Handy for daily alerts. Do not combine with 'Started from'.

## `startedFrom` (type: `string`):

Earliest bid start date, YYYY-MM-DD (inclusive).

## `startedTo` (type: `string`):

Latest bid start date, YYYY-MM-DD (inclusive).

## `sort` (type: `string`):

Order of the results. 'Default' is GeM's own order for the chosen status (open bids closing soonest first), or newest first when a start-date filter is set.

## `ministry` (type: `string`):

Central ministry of the buyer, e.g. 'Ministry of Railways' or 'railways' (any unique part of the name). Uses GeM's advanced search, which covers open bids only and cannot be combined with keyword, bid type, status or sort.

## `buyerState` (type: `string`):

State government buyer, e.g. 'Karnataka'. Use ministry or buyer state, not both. Open bids only.

## `organization` (type: `string`):

Organisation under the ministry or state, e.g. 'Central Railway'. Needs a ministry or buyer state.

## `department` (type: `string`):

Department under the organisation. Needs an organisation.

## `consigneeState` (type: `string`):

State where the goods or services are delivered, e.g. 'Maharashtra'. Open bids only; cannot be combined with the ministry filters.

## `consigneeCity` (type: `string`):

Delivery city within the consignee state, e.g. 'Pune'.

## `bidNumber` (type: `string`):

One bid or RA by its number, e.g. 'GEM/2026/B/8123845'. Searched in open, result and cancelled bids.

## `maxResults` (type: `integer`):

Stop after this many bids. With 'Include details' on, at most 750 bids per run.

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

Also read each bid's document (PDF): organisation and office, bid opening time, offer validity, estimated bid value, EMD and ePBG, minimum turnover, experience and past-performance criteria, type of bid and evaluation method. Slower: one 100-180 KB document per bid.

## `proxyConfiguration` (type: `object`):

GeM only accepts connections from India, so the default is Apify residential proxy in India. Datacenter proxy and non-Indian exits are refused.

## Actor input object example

```json
{
  "keyword": "",
  "keywordMatch": "contains",
  "status": "open",
  "bidType": "all",
  "highValueOnly": false,
  "bidEndFrom": "",
  "bidEndTo": "",
  "startedFrom": "",
  "startedTo": "",
  "sort": "default",
  "ministry": "",
  "buyerState": "",
  "organization": "",
  "department": "",
  "consigneeState": "",
  "consigneeCity": "",
  "bidNumber": "",
  "maxResults": 10,
  "includeDetails": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "IN"
  }
}
```

# 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 = {
    "keyword": "",
    "keywordMatch": "contains",
    "status": "open",
    "bidType": "all",
    "highValueOnly": false,
    "bidEndFrom": "",
    "bidEndTo": "",
    "startedFrom": "",
    "startedTo": "",
    "sort": "default",
    "ministry": "",
    "buyerState": "",
    "organization": "",
    "department": "",
    "consigneeState": "",
    "consigneeCity": "",
    "bidNumber": "",
    "maxResults": 10,
    "includeDetails": false,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "IN"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("barefoot_grade/gem-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 = {
    "keyword": "",
    "keywordMatch": "contains",
    "status": "open",
    "bidType": "all",
    "highValueOnly": False,
    "bidEndFrom": "",
    "bidEndTo": "",
    "startedFrom": "",
    "startedTo": "",
    "sort": "default",
    "ministry": "",
    "buyerState": "",
    "organization": "",
    "department": "",
    "consigneeState": "",
    "consigneeCity": "",
    "bidNumber": "",
    "maxResults": 10,
    "includeDetails": False,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "IN",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("barefoot_grade/gem-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 '{
  "keyword": "",
  "keywordMatch": "contains",
  "status": "open",
  "bidType": "all",
  "highValueOnly": false,
  "bidEndFrom": "",
  "bidEndTo": "",
  "startedFrom": "",
  "startedTo": "",
  "sort": "default",
  "ministry": "",
  "buyerState": "",
  "organization": "",
  "department": "",
  "consigneeState": "",
  "consigneeCity": "",
  "bidNumber": "",
  "maxResults": 10,
  "includeDetails": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "IN"
  }
}' |
apify call barefoot_grade/gem-tenders-scraper --silent --output-dataset

```

## MCP server setup

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