# Government Tender Awards: Winners, Values & Bids (`aiqlabs/government-tender-awards`) Actor

Government contracts already awarded, not open opportunities: who won, for how much, and how many bidders they beat, from the US, UK and EU registers in one schema. Flags contracts labelled openly competed that drew a single bid, ranks repeat winners per buyer, never fills a blank with zero.

- **URL**: https://apify.com/aiqlabs/government-tender-awards.md
- **Developed by:** [Ai-Q Labs](https://apify.com/aiqlabs) (community)
- **Categories:** Developer tools, Automation, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-usage

## 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 Awards: Winners, Values & Bids

Every other government tender Actor gives you the contracts you can still bid on. That
is the easy half, and it is the half that tells you nothing about whether bidding is
worth your time.

This one reads the other end of the process: **contracts that have already been
awarded**. Who won, how much they were paid, how many bidders they beat, and which
buyers keep going back to the same supplier — from the US, UK and EU registers, in one
schema.

Four of the tender Actors currently on the store were read line by line before this was
built. All four list open opportunities; none of them returns an award result. One even
ships an `awardedTo` field with nothing in it.

### The finding this was built around

A US contract can be labelled **FULL AND OPEN COMPETITION** and receive **exactly one
offer**. The label describes the procedure. The offer count describes what happened.
When they disagree, that is the most useful line in the dataset: a market where the
paperwork says anyone could bid and, in practice, nobody else did.

Measured on 12 US software contracts while this was written — three of them:

| Winner | Label on the contract | Offers received |
|---|---|---|
| CARAHSOFT TECHNOLOGY CORP | FULL AND OPEN COMPETITION | **1** |
| V3GATE, LLC | FULL AND OPEN COMPETITION AFTER EXCLUSION OF SOURCES | **1** |
| THUNDERCAT TECHNOLOGY, LLC | FULL AND OPEN COMPETITION AFTER EXCLUSION OF SOURCES | **1** |

Those rows carry `uncontestedOpenAward: true` and are collected in the
`UNCONTESTED_AWARDS` record. A contract that was openly advertised and drew one bid is
either a market nobody knows about or one nobody thinks they can win. Both are worth
knowing before you write a proposal.

A sole-source award that received one bid is **not** flagged. Its label never claimed
competition, so there is no contradiction — it is reported as `single_bid_received`
instead. The two are kept apart on purpose.

### What it will not do

**It never fills a blank with a zero.** Award registers are full of holes. A UK buyer
often publishes the winner and withholds the price; a US contracting officer often
leaves the offer count empty. Writing those as `0` would turn *we do not know* into
*nobody bid*, and *the price is secret* into *the contract was free*.

So every uncertain number ships with the boolean that says whether it was published:

| Field | Companion |
|---|---|
| `awardAmount` | `amountDisclosed` |
| `bidsReceived` | `bidsDisclosed` |
| `singleBidder` | `null` when the count is unknown — never `false` |

The disclosure rates are an output in their own right. Over one live run of 177 awards:
the value was published on 47 and the bid count on 7. That is not a defect in the data
collection; it is what the registers publish, and a tool that hid it would be lying by
omission.

**It never adds currencies together.** GBP, EUR, USD, CZK and PLN awards all appear in
the same run. Totals are kept as a map per currency, because converting them would need
an exchange rate this Actor has no business inventing.

**It never credits a shared contract twice.** When three firms win a contract jointly,
each is credited with a third of the value in the supplier ranking, not the whole
amount.

### What comes out

One row per award, plus four aggregate records.

#### Dataset row

| Field | Meaning |
|---|---|
| `source`, `country`, `sourceUrl` | Which register, and a link straight to the original notice |
| `buyer`, `buyerUnit` | The contracting organisation |
| `suppliers`, `supplierCount`, `supplierCountries` | Who won, including joint winners |
| `awardAmount`, `currency`, `amountDisclosed`, `amountBasis` | What it was worth, in the register's own currency, and what the number actually measures |
| `bidsReceived`, `bidsDisclosed`, `singleBidder` | How many bids came in |
| `competitionLabel`, `uncontestedOpenAward` | The procedure claimed, and whether it matches the bid count |
| `lowestBidAmount`, `highestBidAmount` | The bid range, where the register publishes it |
| `awardDate`, `contractStart`, `contractEnd` | Dates, normalised to `yyyy-mm-dd` across three different formats |
| `awardsOnThisNotice` | How many awards the parent notice holds — a framework can hold over a hundred |
| `classification` | NAICS/PSC for the US, CPV for the EU |
| `issues` | Machine-readable flags: `open_competition_with_one_bid`, `award_value_not_published`, … |

#### Key-value records

| Record | What it answers |
|---|---|
| `TOP_SUPPLIERS` | Who keeps winning, across how many buyers, for how much per currency |
| `BUYER_PROFILES` | Per buyer: `repeatWinnerShare` — how often its awards go to its single most frequent supplier — plus its disclosure rates |
| `UNCONTESTED_AWARDS` | The openly-competed contracts that drew one bid, biggest first |
| `SUMMARY` | Counts, disclosure rates, the exact query sent to each register, and every caveat that applied to this run |

### The three registers

| | Winner | Value | Bid count | Keyword filter |
|---|---|---|---|---|
| **US** — USAspending.gov | always | always | in about half the awards | server-side |
| **UK** — Find a Tender | almost always | sometimes | rarely | applied here, after fetching |
| **EU** — TED | always | always | **never published** | server-side, full text |

All three are official public APIs and **none of them needs a key**. Nothing here is
scraped: no HTML is parsed, no login is used, no page is crawled.

Three differences worth knowing before you read the numbers:

- **US amounts are obligated dollars**, not a bid price, and every US row says so in
  `amountBasis`. Bid counts live in a separate detail endpoint, which costs one extra
  request per award — that is what `includeUsCompetitionDetail` controls.
- **UK dates are notice-publication dates**, not award dates; where the award itself
  carries a date it is used, and `ukAwardDatePublished` says which one you got.
- **EU rows are one per notice, not one per award.** TED returns lot results as
  parallel arrays with no reliable pairing between a winner and a lot, so a multi-lot
  notice lists every winner and states in `amountBasis` that the amount covers all of
  them. Splitting it would mean inventing a breakdown the publisher did not give.

### Three things real data changed

Unit tests pin the shapes. These only appeared once the code met the registers.

**1. The obvious UK filter hides every award that has a price.** Find a Tender accepts
`stages=award`, which looks like the correct way to ask for results. Measured over one
window it returned 486 awards — **all** under the old EU directive, **none** with a
published value. The unfiltered feed over the same window returned 435 awards of which
118 had values and 5 releases carried bid lists, because notices published under the UK
Procurement Act 2023 are missing from that filter entirely. Using it would have made
this Actor report that the UK never discloses what it paid. The filter is applied here
instead, on the release tag.

**2. One framework notice can swallow the whole run.** UK notice `071472-2026` holds
127 lot awards on its own — enough to fill the record budget on the first page and hide
the other 1,283 awards in the window. `maxAwardsPerNotice` bounds any single
procurement, whatever was left out is counted in `SUMMARY`, and every row carries
`awardsOnThisNotice` so the real size stays visible.

**3. The same award is republished every time its notice is updated.** In one 400-row
sample, 32 rows were repeats of an award already collected — enough to inflate every
supplier's win count. Awards are now deduplicated per procurement, keeping the most
recent copy.

### Limits

- **Contracts only.** Grants and loans are out of scope: they have no bidders.
- **No bid counts for the EU.** TED's search API exposes 1,830 fields and the received
  submissions count is not one of them. `bidsReceived` is `null` for every EU row
  rather than guessed from the tender value range.
- **The UK has no keyword parameter, and its feed is rate limited.** Keywords are
  matched here against title, description, buyer and winner, so a UK run reads the whole
  window looking for them — one test run scanned 3,400 releases to find 3 awards
  mentioning "cybersecurity". Find a Tender allows 12 requests per 90-second window per
  IP, and an Actor shares its egress with other tenants, so pages are spaced 8 seconds
  apart and reading stops after 40 pages with the truncation reported. **Narrow the date
  range rather than relying on the keyword** when you want UK coverage.
- **A refused page never discards the pages before it.** If a register starts refusing
  mid-run, what was already collected is kept, the reason is written into `SUMMARY`, and
  the run is marked truncated.
- **`repeatWinnerShare` is a share, not a verdict.** A buyer with three awards and one
  supplier scores 1.0 on very little evidence. Read it next to `awards`.
- Disclosure rates describe the awards this run collected, not the register as a whole.

### Example input

```json
{
  "sources": ["us", "uk", "eu"],
  "keywords": ["cybersecurity"],
  "awardedFrom": "2026-06-01",
  "awardedTo": "2026-07-31",
  "maxRecordsPerSource": 200,
  "includeUsCompetitionDetail": true
}
```

To go straight to the contradictions, set `onlyUncontested` to `true`. The rankings in
the key-value records are still built from every award found, not only the flagged ones.

# Actor input Schema

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

US = USAspending.gov federal contracts. UK = Find a Tender. EU = TED contract award notices. All three are official APIs and none needs a key.

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

Any of these words. Matched by the register itself for US and EU; matched here against title, description, buyer and winner for UK, because Find a Tender has no keyword parameter. Leave empty for everything in the date range.

## `awardedFrom` (type: `string`):

yyyy-mm-dd. US filters on the date the contract was signed, UK on the day the notice was published or updated, EU on the publication date. Defaults to 30 days ago.

## `awardedTo` (type: `string`):

yyyy-mm-dd. Defaults to today.

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

Three-letter codes such as DEU, FRA, ESP. EU register only; ignored for US and UK.

## `cpvCodes` (type: `array`):

Eight-digit CPV codes such as 72000000 for IT services. EU register only.

## `minAwardAmount` (type: `integer`):

In the currency each register publishes - no conversion is done. Awards whose value was never published are kept, because "not published" is not "below the minimum".

## `maxRecordsPerSource` (type: `integer`):

The main control over how long a run takes. The US register also costs one extra request per award when bid counts are on.

## `maxAwardsPerNotice` (type: `integer`):

A UK framework notice can carry over a hundred lot awards - one of them alone would fill the whole run and hide every other contract in the window. Anything left out is counted in SUMMARY, and every row carries awardsOnThisNotice so the real size is visible. Set 0 to take them all.

## `includeUsCompetitionDetail` (type: `boolean`):

The number of offers received and the competition label live only in the US award detail endpoint, so this costs one extra request per award. Turning it off makes the run much faster and removes the single-bid analysis for US rows.

## `onlyUncontested` (type: `boolean`):

Output just the contradictions. The rankings in the key-value records are still built from every award found.

## `maxConcurrency` (type: `integer`):

Applies to the US detail lookups. Lower it if the register starts refusing requests.

## `requestTimeoutSecs` (type: `integer`):

How long to wait for one response. The US search endpoint is slow on wide date ranges.

## Actor input object example

```json
{
  "sources": [
    "us",
    "uk",
    "eu"
  ],
  "keywords": [
    "software"
  ],
  "awardedFrom": "",
  "awardedTo": "",
  "countries": [],
  "cpvCodes": [],
  "minAwardAmount": 0,
  "maxRecordsPerSource": 50,
  "maxAwardsPerNotice": 25,
  "includeUsCompetitionDetail": true,
  "onlyUncontested": false,
  "maxConcurrency": 5,
  "requestTimeoutSecs": 30
}
```

# 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 = {
    "sources": [
        "us",
        "uk",
        "eu"
    ],
    "keywords": [
        "software"
    ],
    "awardedFrom": "",
    "awardedTo": "",
    "maxRecordsPerSource": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("aiqlabs/government-tender-awards").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 = {
    "sources": [
        "us",
        "uk",
        "eu",
    ],
    "keywords": ["software"],
    "awardedFrom": "",
    "awardedTo": "",
    "maxRecordsPerSource": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("aiqlabs/government-tender-awards").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 '{
  "sources": [
    "us",
    "uk",
    "eu"
  ],
  "keywords": [
    "software"
  ],
  "awardedFrom": "",
  "awardedTo": "",
  "maxRecordsPerSource": 50
}' |
apify call aiqlabs/government-tender-awards --silent --output-dataset

```

## MCP server setup

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

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/acts/SB9cEjCYiyUMNLMak/builds/2nJ1pr9DVR6O8pk7m/openapi.json
