# Government Tender Scraper - SAM.gov & TED EU Procurement (`fuyuki0/government-tender-scraper`) Actor

Scrape government tenders and public procurement notices from SAM.gov (US federal) and TED (European Union) in one run. Filter by keyword, country, and contract value. Remembers what it already returned, so scheduled runs deliver only new tenders instead of the same list every time.

- **URL**: https://apify.com/fuyuki0/government-tender-scraper.md
- **Developed by:** [Phupatchara Treenuson](https://apify.com/fuyuki0) (community)
- **Categories:** Automation, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.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/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

## Government Tender Scraper — SAM.gov & TED EU Procurement Notices

Scrape government tenders and public procurement notices from **SAM.gov** (US federal
contract opportunities), **TED** (Tenders Electronic Daily, the European Union's procurement
journal), and **Find a Tender** (the UK's central procurement platform) in a single run.
Filter by keyword, country, contract value, and deadline. Export to JSON, CSV, or Excel.

Unlike other tender scrapers, this one **remembers what it already returned**. Schedule it
daily and you receive only the new tenders published since your last run — not the same
list every morning with the diffing left to you.

### What this Actor does

- **Scrape US government contracts** from the official SAM.gov opportunities API
- **Scrape EU public tenders** from the official TED API v3, covering all 27 member states
- **Scrape UK public contracts** from the official Find a Tender OCDS API
- **Search three jurisdictions in one run**, with one deduplication state across all of them
- **Get only new tenders** — built-in deduplication across scheduled runs
- **Filter tenders by keyword**, so you see only contracts matching what you sell
- **Filter by contract value** and by country
- **Exclude irrelevant notices** with negative keywords
- **One normalised schema** across all sources, so US, EU, and UK notices arrive identically shaped

### Who uses this

- **Contractors and suppliers bidding on government contracts** who want a daily feed of
  relevant opportunities without paying $50–500/month for a tender alert service
- **AI agents and MCP tools** that need public procurement data as a callable tool
- **Market researchers** tracking public spending in a sector or country
- **Lead generation** for companies selling into the public sector

### Example output

One record per notice, identical shape regardless of source:

```json
{
  "uid": "3f9a1c2b7d4e5f60",
  "source": "ted.europa.eu",
  "source_id": "547030-2026",
  "title": "Malta – Pipes and fittings – Supply and delivery of ductile iron pipes",
  "buyer": "Water Services Corporation",
  "country": "MLT",
  "published": "2026-08-07",
  "deadline": "2026-09-16",
  "value_amount": 1155475.0,
  "value_currency": "EUR",
  "categories": ["44163000", "44163100", "42131280"],
  "description": "The subject of this tender is the supply and delivery of ductile iron pipes...",
  "url": "https://ted.europa.eu/en/notice/-/detail/547030-2026"
}
```

### Input parameters

| Field | Meaning |
|---|---|
| `keywords` | Return notices matching any of these in title, description, or CPV/NAICS codes |
| `excludedKeywords` | Drop notices mentioning any of these |
| `sources` | Any of `sam.gov`, `ted.europa.eu`, `find-tender.service.gov.uk`. All three by default |
| `samApiKey` | Free key from sam.gov. Required only for SAM.gov; TED and Find a Tender need none |
| `countries` | ISO codes to restrict TED results to, e.g. `DEU`, `FRA`, `ESP` |
| `daysBack` | How far back to look for newly published tenders. Default 7 |
| `onlyNew` | Return only tenders not seen in previous runs. Default true |
| `minValue` / `maxValue` | Contract value bounds |
| `includeExpired` | Keep tenders whose deadline has passed. Default false |
| `maxResults` | Cap on notices per run, and therefore on cost. Split evenly across the selected sources so every jurisdiction is represented |

### How to scrape government tenders daily

1. Set `keywords` to the work you actually bid on — for example `["asbestos", "demolition"]`
2. Leave `onlyNew` on
3. Schedule the Actor to run every morning
4. Connect the dataset to email, Slack, or a webhook

Each run delivers only tenders published since the last one.

### How to get a SAM.gov API key

Free. Register at [sam.gov](https://sam.gov), then request a public API key from your account
details page. Registered keys allow 1,000 requests per day. TED and Find a Tender require no
key at all, so you can run the EU and UK sources with no setup.

### Behaviour worth knowing

**Tenders with no published value are kept.** Most notices publish no contract value, so
filtering them out would hide the majority of the market. Filter on `value_amount` yourself
if you disagree.

**Tenders with no deadline are kept.** Several notice types carry none, and missing a live
opportunity is worse than seeing one extra row.

**Partial results beat no results.** If one source fails mid-run, whatever it returned is
kept and the other sources still report.

**Already-awarded UK contracts are excluded.** The Find a Tender feed mixes open
opportunities with contracts already awarded. Awards are dropped, since they are decisions
already made rather than work you can bid on.

**State commits only after delivery.** A run that fails before pushing results re-reports
them next time rather than losing them.

**Seen-state is forgotten after 180 days**, so stored state cannot grow without bound.

### Data sources

All three are official public APIs intended for programmatic access. No login-walled
scraping, no circumvention of access controls, no personal data.

- [SAM.gov Get Opportunities API](https://open.gsa.gov/api/get-opportunities-public-api/)
- [TED API v3](https://docs.ted.europa.eu/api/latest/index.html)
- [UK Find a Tender OCDS API](https://www.find-tender.service.gov.uk/Developer/Documentation)

### Pricing

Pay per event: you are charged per tender delivered. A run that finds nothing new costs
nothing beyond platform compute. Cap your spend with `maxResults`.

### Development

```bash
python -m venv .venv
./.venv/bin/pip install pytest httpx
./.venv/bin/python -m pytest tests/ -q
```

Parsing, filtering, and deduplication are pure functions with no network access, so the
suite runs offline in under a second. Only `src/main.py` touches the network.

# Actor input Schema

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

Return notices mentioning any of these terms in the title, description, or classification codes. Leave empty to return everything in the date window.

## `excludedKeywords` (type: `array`):

Drop notices mentioning any of these terms.

## `sources` (type: `array`):

Which procurement sources to search.

## `samApiKey` (type: `string`):

Required only for SAM.gov. Free from sam.gov - register, then request a public API key from your account details page. TED needs no key.

## `countries` (type: `array`):

ISO country codes to restrict TED results to, e.g. DEU, FRA, ESP.

## `daysBack` (type: `integer`):

How far back to look for newly published notices.

## `onlyNew` (type: `boolean`):

Remember what previous runs returned and report only what is new. Leave on for scheduled monitoring; turn off to re-export everything in the window.

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

Notices below this value are dropped. Notices that publish no value are always kept, since most do not publish one.

## `maxValue` (type: `integer`):

Notices above this value are dropped. Notices that publish no value are always kept.

## `includeExpired` (type: `boolean`):

Keep notices you can no longer bid on. Off by default. Notices that publish no deadline are always kept.

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

Caps how many notices one run returns, and therefore what it costs.

## Actor input object example

```json
{
  "keywords": [
    "construction",
    "engineering"
  ],
  "sources": [
    "sam.gov",
    "ted.europa.eu",
    "find-tender.service.gov.uk"
  ],
  "daysBack": 7,
  "onlyNew": true,
  "includeExpired": false,
  "maxResults": 200
}
```

# Actor output Schema

## `tenders` (type: `string`):

Every tender delivered by this run, as JSON. One record per notice, with buyer, deadline, contract value, classification codes, and a link to the full notice.

## `tendersCsv` (type: `string`):

The same notices as a CSV file, for opening in Excel or Google Sheets.

## `datasetView` (type: `string`):

Table view of the notices returned by this run.

# 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 = {
    "keywords": [
        "construction",
        "engineering"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("fuyuki0/government-tender-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 = { "keywords": [
        "construction",
        "engineering",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("fuyuki0/government-tender-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 '{
  "keywords": [
    "construction",
    "engineering"
  ]
}' |
apify call fuyuki0/government-tender-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,fuyuki0/government-tender-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/ph8NyUcUHk1jRkacB/builds/hGoBrtjOsqUmMMLPE/openapi.json
