# 🇸🇬 Singapore GeBIZ Tender & Award Change Watch (`nexgenwatch/gebiz-tender-watch`) Actor

Monitor public Singapore GeBIZ listings and get structured, seed-zero deltas when tenders open, change, close, are cancelled, or publish an award state. Before/after snapshots, changed-field lists, grace-windowed closures, and a source receipt on every run.

- **URL**: https://apify.com/nexgenwatch/gebiz-tender-watch.md
- **Developed by:** [NexGen Watch](https://apify.com/nexgenwatch) (community)
- **Categories:** Business, News
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $33.50 / 1,000 tender/award deltas

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/platform/actors/running/actors-in-store#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

## 🇸🇬 Singapore GeBIZ Tender & Award Change Watch

GeBIZ shows you today's listing, not what changed. This is a seed-zero change feed: when a Singapore government tender opens, changes, closes, is cancelled, or reaches an award state, you get a structured delta with before/after — not another full scrape to diff yourself.

### What you get

- `tender_delta` — `NEW`, `CHANGED`, `CLOSED`, `PENDING_AWARD`, `AWARDED`, `CANCELLED`, `NO_AWARD`, `REAPPEARED`, each with `before`/`after` and `changed_fields`
- `tender_record` — a normalized listing snapshot: document and reference number, type, status, title, agency, category, published/closes dates, `source_hash`
- A closure grace window (`closureGraceRuns`) so a listing that briefly drops out is not reported as closed
- `missing_complete_runs` on each delta, so you can see the evidence behind a closure call
- `source_receipt` every run — terminal state, canary result, page/request counters, never billed

### Use cases by team

- **Suppliers & bid teams:** Route matching opportunities to bid owners the run they appear, and get told when one changes under you.
- **Procurement & compliance teams:** Retain dated public status records with before/after evidence of each change.
- **Award & incumbency analysts:** Catch the `PENDING_AWARD` → `AWARDED` transition rather than polling for it.
- **Researchers:** Study public agency and category behaviour over time from a deterministic, bounded feed.

### Example inputs

*Seed a watch on two agencies*

```json
{
  "watchMode": true,
  "baselineKey": "sg-infocomm",
  "agencies": [
    "Government Technology Agency"
  ],
  "publishedWithinDays": 30
}
```

*Award-state changes only*

```json
{
  "watchMode": true,
  "baselineKey": "sg-awards",
  "emitDeltaTypes": [
    "PENDING_AWARD",
    "AWARDED",
    "NO_AWARD"
  ]
}
```

*Keyword + type targeting*

```json
{
  "watchMode": true,
  "baselineKey": "sg-cloud",
  "keywords": [
    "cloud"
  ],
  "opportunityTypes": [
    "Tender",
    "Quotation"
  ],
  "maxItems": 200
}
```

### 📊 Sample Output

```json
{
  "record_type": "tender_delta",
  "delta_type": "AWARDED",
  "document_number": "GT2026000123W",
  "reference_number": "GTA/2026/CLOUD/07",
  "opportunity_type": "Tender",
  "status": "AWARDED",
  "title": "Provision of cloud hosting services",
  "agency": "Government Technology Agency",
  "procurement_category": "Infocomm Technology",
  "changed_fields": [
    "status"
  ],
  "before": {
    "status": "PENDING_AWARD"
  },
  "after": {
    "status": "AWARDED"
  },
  "missing_complete_runs": 0,
  "published_at": "2026-06-18",
  "closes_at": "2026-07-16",
  "source_url": "https://www.gebiz.gov.sg/ptn/opportunity/BOListing.xhtml",
  "observed_at": "2026-07-25T00:00:00Z"
}
```

### How it works

Configure agencies, categories, opportunity types, states and keywords. It reads only the **public, logged-out GeBIZ business-opportunity listing**, normalizes each listing into a `tender_record` with a `source_hash`, and diffs against a private baseline. The **first watch run stores the baseline and emits no alerts**; later runs emit only qualified `tender_delta` events with before/after snapshots and `changed_fields`. Closure is grace-windowed over `closureGraceRuns`, so a transient disappearance does not fire a false `CLOSED`. Snapshot mode exists, but the product is the recurring change feed.

### Pricing

| Event | Price (FREE tier) |
|---|---|
| Actor start (`apify-actor-start`) | $0.02 flat, once per run |
| Tender/award delta (`tender_delta`) — **primary** | $0.05 |
| Tender record snapshot (`tender_record`) | $0.10 |
| Source check (`source_check`) | $0.10, once per run |

Pay-per-event on a four-tier ladder (FREE → GOLD, with Platinum/Diamond floored at the Gold rate). Primary event `tender_delta`: **$0.05 / $0.045 / $0.04 / $0.0335**. `tender_record` and `source_check`: $0.10 / $0.09 / $0.08 / $0.067. Worked example — a watch run on FREE with 8 qualified deltas: $0.02 start + $0.10 source check + 8 × $0.05 = **$0.52**. The seeding run and genuine-zero runs charge the start + source check only; receipts are never billed. New to Apify? [Start free](https://apify.com/fpr=2ayu9b).

### Run it — API, CLI, MCP

API:

```bash
curl -X POST "https://api.apify.com/v2/acts/nexgenwatch~gebiz-tender-watch/runs?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" -d @input.json
```

CLI:

```bash
apify call nexgenwatch/gebiz-tender-watch -i @input.json
```

MCP: expose `nexgenwatch/gebiz-tender-watch` through Apify's MCP server, select it as a tool, and pass the same input object.

### Related actors — App Intelligence cluster

| Actor | What it does |
|---|---|
| [EU TED Tender & Award Watch](https://apify.com/nexgenwatch/eu-ted-tender-award-watch?fpr=2ayu9b) | Watch EU-wide public procurement: tenders, awards, incumbent changes. |
| [UK Find a Tender Watch](https://apify.com/nexgenwatch/uk-fts-tender-award-watch?fpr=2ayu9b) | Watch UK tenders, award results and incumbent changes. |
| [CanadaBuys Tender Watch](https://apify.com/nexgenwatch/canadabuys-tender-watch?fpr=2ayu9b) | Watch Government of Canada tender opportunities and awards. |
| [AusTender Award Watch](https://apify.com/nexgenwatch/austender-award-watch?fpr=2ayu9b) | Watch Australian federal contract awards and incumbents. |
| [MAS Directory Change Watch](https://apify.com/nexgenwatch/mas-register-watch?fpr=2ayu9b) | Watch Singapore MAS financial-institution licence changes. |

### Compliance & honest limits

- Public, logged-out GeBIZ listing only — no login, CAPTCHA, paywall, or access-control defeating.
- The first watch run seeds a baseline and emits **zero** deltas by design.
- Detail-page enrichment is off by default (`includePublicDetail`, `maxDetailRequests: 0`) to keep runs bounded and polite.
- A closure is reported only after `closureGraceRuns` consecutive complete runs without the listing — deliberately conservative.
- A source-confirmed empty listing is `GENUINE_EMPTY`; truncated runs are `PARTIAL`; exhausted retries are `BLOCKED`. Receipts are never billed.

### FAQ

**What does seed-zero mean?**

The first watch run stores a private baseline and emits no alerts at all. Deltas start on the second run. This is why you are not billed a flood of false 'new' events on day one.

**Which changes can I subscribe to?**

Set `emitDeltaTypes` to any subset of `NEW`, `CHANGED`, `CLOSED`, `PENDING_AWARD`, `AWARDED`, `CANCELLED`, `NO_AWARD`, `REAPPEARED`.

**How do you avoid false closures?**

A listing must be absent for `closureGraceRuns` consecutive complete runs before `CLOSED` fires, and each delta carries `missing_complete_runs` so you can audit the call.

**Does zero mean blocked?**

No. Every run emits a `source_receipt` with an explicit terminal state — `NORMAL`, `GENUINE_EMPTY`, `PARTIAL` or `BLOCKED` — plus a canary result and page counters.

**What is `REAPPEARED` for?**

A listing that dropped out and came back. It is emitted explicitly rather than being silently re-reported as `NEW`.

**What am I charged on a quiet run?**

The $0.02 start plus the $0.10 source check. Deltas are the primary unit, so quiet weeks are cheap.

**Can I get full snapshots instead of deltas?**

Yes — snapshot mode returns `tender_record` rows, billed at $0.10 each. Most buyers want the delta feed.

### Troubleshooting

- **First run emitted nothing?** That is seed-zero behaviour. Re-run with the same `baselineKey` and deltas begin.
- **Everything came back as NEW?** You changed `baselineKey`, or set `resetBaseline`-equivalent behaviour, so the baseline was re-seeded.
- **Missing an agency's listings?** Check `agencies` spelling against the public listing, and confirm `publishedWithinDays` is wide enough.
- **Run marked PARTIAL?** You hit `maxItems` or `maxPages`. Raise them or narrow `states`/`opportunityTypes`.
- **Closures arriving late?** By design — lower `closureGraceRuns` if you want faster, less certain closure calls.

### About

Part of the NexGenData procurement-watch family — one honest, deterministic contract across UK, EU, Canadian, Australian and Singaporean public procurement. Built by NexGenData. Questions or need a custom feed? Open an issue on the Actor page.

# Actor input Schema

## `watchMode` (type: `boolean`):

Seed a private baseline and emit only qualified deltas on later runs. The first successful watch run seeds and emits zero deltas.

## `baselineKey` (type: `string`):

Named private watch baseline (1-120 chars, ^\[A-Za-z0-9.\_:-]+$). If unset, derived from a SHA-256 of the normalized filters. Changing the filters derives a new baseline.

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

0-10 phrases (2-80 chars each), matched locally match-any against title / document number / agency / procurement category.

## `agencies` (type: `array`):

0-20 exact visible agency labels. Empty means all agencies.

## `procurementCategories` (type: `array`):

0-20 visible leaf labels or 'Parent > Leaf' paths. Empty means all categories.

## `opportunityTypes` (type: `array`):

Non-empty subset of Qualification, Quotation, Request for Information, Tender, Tender Lite.

## `states` (type: `array`):

Non-empty subset of OPEN, PENDING\_AWARD, AWARDED, CANCELLED, NO\_AWARD, CLOSED.

## `publishedWithinDays` (type: `integer`):

Keep only opportunities published within this many days (Asia/Singapore). Local filtering only.

## `includePublicDetail` (type: `boolean`):

Fetch logged-out public detail pages for emitted deltas only. Never attachments or bid documents.

## `emitDeltaTypes` (type: `array`):

Non-empty subset of NEW, CHANGED, CLOSED, PENDING\_AWARD, AWARDED, CANCELLED, NO\_AWARD, REAPPEARED.

## `closureGraceRuns` (type: `integer`):

Complete absences (1-3) before an unseen record is emitted as CLOSED. Explicit source state overrides.

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

Maximum records or deltas to emit this run (receipts excluded).

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

Maximum listing pages to traverse.

## `maxDetailRequests` (type: `integer`):

Maximum logged-out detail page fetches this run. Zero unless includePublicDetail is enabled.

## Actor input object example

```json
{
  "watchMode": true,
  "keywords": [],
  "agencies": [],
  "procurementCategories": [],
  "opportunityTypes": [
    "Tender",
    "Tender Lite",
    "Quotation"
  ],
  "states": [
    "OPEN",
    "PENDING_AWARD",
    "AWARDED",
    "CANCELLED",
    "NO_AWARD"
  ],
  "publishedWithinDays": 30,
  "includePublicDetail": false,
  "emitDeltaTypes": [
    "NEW",
    "CHANGED",
    "CLOSED",
    "PENDING_AWARD",
    "AWARDED",
    "CANCELLED",
    "NO_AWARD",
    "REAPPEARED"
  ],
  "closureGraceRuns": 2,
  "maxItems": 200,
  "maxPages": 30,
  "maxDetailRequests": 0
}
```

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("nexgenwatch/gebiz-tender-watch").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("nexgenwatch/gebiz-tender-watch").call(run_input=run_input)

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

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

```

## CLI example

```bash
echo '{}' |
apify call nexgenwatch/gebiz-tender-watch --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=nexgenwatch/gebiz-tender-watch",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/RskgaAV4Ou5V0JJkc/builds/0lW7qxO2RZDdOadVR/openapi.json
