# UK Tenders — Find a Tender + Contracts Finder OCDS (`ingenious_quip_bxq/uk-tenders-ocds-markdown`) Actor

UK public tenders from the official Find a Tender + Contracts Finder OCDS APIs (no HTML scraping, no API key). Filter by keywords, buyer, CPV, dates. One Markdown tender card + JSON per notice. 256 MB. Failed rows free. No email/phone harvesting.

- **URL**: https://apify.com/ingenious_quip_bxq/uk-tenders-ocds-markdown.md
- **Developed by:** [新世紀書僮](https://apify.com/ingenious_quip_bxq) (community)
- **Categories:** Business, Developer tools, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 tender notice cards

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

## UK Tenders — Find a Tender + Contracts Finder OCDS

Get UK public-sector tender and contract notices from the two **official** government OCDS APIs, **Find a Tender** (FTS) and **Contracts Finder** (CF). Each notice becomes a clean **JSON row** and a **Markdown tender card** you can read, paste into a CRM, or feed to an LLM / RAG pipeline.

- **Official APIs only.** Open Contracting Data Standard (OCDS) JSON endpoints published by the Cabinet Office. No HTML scraping of the find-tender or Contracts Finder websites.
- **No API key, no login.** Both feeds are open data under the Open Government Licence v3.0.
- **Light:** 256 MB default memory, newest-first paging, stops when it has enough matches.
- **Failures are free.** Not-found IDs, empty searches, rate limits, timeouts and server errors are logged as free rows.
- **No contact harvesting.** Buyer contact emails and phone numbers are never copied. Emails and phone numbers inside free text are replaced with `[contact removed]`.

### What you get

**Dataset**: one row per notice:

| Field | Example |
|---|---|
| `ocid`, `noticeId` | `ocds-h6vhtk-06dab4`, `093588-2026` |
| `source` | `find_a_tender` | `contracts_finder` |
| `title`, `buyer`, `buyerId` | "(CIH) Training and Qualification Provision", "VICO HOMES LIMITED" |
| `value`, `valueGross`, `currency` | 160000, 192000, GBP |
| `publishedDate`, `deadline`, `contractStart`, `contractEnd` | ISO 8601 |
| `stage`, `status`, `procedure`, `procedureDetails`, `mainCategory` | tender, active, open, "Below threshold - open competition", services |
| `cpvCodes[]`, `regions[]`, `lots`, `suitableForSme`, `suitableForVcse` | `[{"id":"80500000","description":"Training services"}]` |
| `awards[]` | status, date, value, supplier **names** |
| `description` | redacted excerpt (default 800 chars) |
| `documents[]` | title, type, url, format, datePublished |
| `noticeUrl`, `apiUrl` | official notice page link + the OCDS API endpoint used |
| `markdown` | the tender card |
| `result`, `errorClass`, `hint` | `ok` / `failed` / `empty` |

**Key-value store**

- `REPORT.md`: summary table plus one Markdown card per notice, followed by a "Not charged" section
- `SUMMARY` / `OUTPUT`: counts, filters, scanned releases, requests, charged events, duration, peak memory

#### Example card

```markdown
#### (CIH) Training and Qualification Provision

- **Buyer:** VICO HOMES LIMITED
- **Source:** Find a Tender · notice `093588-2026` · [view notice](https://www.find-tender.service.gov.uk/Notice/093588-2026)
- **OCID:** `ocds-h6vhtk-06dab4`
- **Stage / status:** tender / active
- **Procedure:** open — Below threshold - open competition
- **Value:** £160,000 GBP (gross £192,000 GBP)
- **Dates:** published 2026-10-05 · **deadline 2026-10-23 12:00 (+0100)**
- **CPV:** `80500000` Training services
- **Delivery:** UKE, UKF
- **Suitable for:** SME

> This agreement will be for the provision of providing CIH commercial qualifications …

**Documents / links**
- [Tender notice on Find a Tender](https://www.find-tender.service.gov.uk/Notice/093588-2026) — tenderNotice, 2026-10-05
```

### How search works

The official OCDS search endpoints filter **only by date window and paging**. They have no keyword, buyer or CPV search. This Actor therefore:

1. pages each selected source **newest first** within your date window (`dateFrom` / `dateTo`, or the last `daysBack` days),
2. applies your filters to every release (stages, query, keywords, buyer, CPV, open-only, minimum value),
3. stops when it has `maxNotices` matches or has scanned `maxPagesPerSource` pages (100 releases per page),
4. keeps the newest release per procurement (OCID), merges both sources newest-first, and writes the cards.

For narrow filters over long windows, raise `maxPagesPerSource`. If a source has no matches, you get one free `no_matches` row with the number of releases scanned.

**Client-side filtering.** The OCDS search APIs have **no reliable server-side keyword, buyer or CPV search** (Contracts Finder silently ignores `keyword`; Find a Tender returns 400 for unknown params). Stages, query, keywords, buyer, CPV, open-only and minimum value are therefore applied **client-side** as each page of releases is scanned. Stage filtering also runs client-side on OCDS tags (FTS `stages=` under-returns).

**Contracts Finder + default stages.** Since the Procurement Act 2023 went live, most new opportunities are published on **Find a Tender**. Contracts Finder currently carries **mostly award notices**. The default `stages: ["tender"]` therefore **often yields a free CF `no_matches` row** even when FTS returns tenders. To include Contracts Finder results, set `stages` to include `"award"` (for example `["tender", "award"]`, or `["award"]` with `sources: "contracts_finder"`).

### Input

| Field | Default | Notes |
|---|---|---|
| `query` | — | All words must match (title / description / buyer / CPV text). Use `"quotes"` for phrases |
| `keywords` | — | Any-of list, combined with `query` as AND |
| `buyerName` | — | Case-insensitive substring of the buyer name |
| `cpvCodes` | — | Prefix match. Trailing zeros widen: `72000000` = all IT services |
| `sources` | `both` | `both` | `find_a_tender` | `contracts_finder` |
| `stages` | `["tender"]` | `planning`, `tender`, `award`. Empty = all |
| `dateFrom` / `dateTo` | last 7 days | `YYYY-MM-DD` or `YYYY-MM-DDTHH:MM:SS` (CF: published, FTS: updated) |
| `daysBack` | 7 | Used when `dateFrom` is empty |
| `openOnly` | false | Keep only notices whose deadline is in the future |
| `minValue` | — | Keep notices with value ≥ this amount |
| `noticeIds` / `releaseUrls` | — | Direct fetch (see below). Skips search unless `searchAlsoWithIds` |
| `maxNotices` | 50 | Max cards (and charges) per run |
| `maxPagesPerSource` | 10 | Up to 100 releases per page |
| `latestPerOcid` | true | One card per procurement |
| `descriptionMaxChars` | 800 | Excerpt length |
| `maxConcurrency` | 2 | Max 4, kept low to respect the services |
| `maxRetries` | 3 | 429, CF 403 rate-limit page, 5xx, timeouts. Retry-After is honoured |
| `requestTimeoutSecs` | 60 | |

#### Direct fetch

`noticeIds` accepts:

- Find a Tender notice IDs `093588-2026` or OCIDs `ocds-h6vhtk-06dab4` (the newest release is used)
- Contracts Finder notice GUIDs (from `contractsfinder.service.gov.uk/Notice/<guid>`) or release IDs `<guid>-NNNNNN`
- Official notice URLs or OCDS API URLs on those two services, converted to API calls. HTML is never fetched

Contracts Finder OCIDs (`ocds-b5fd17-…`) have no reliable keyless lookup. Pass the notice GUID instead; otherwise you get a free `cf_ocid_unsupported` row.

#### Example inputs

```json
{ "query": "software", "stages": ["tender"], "openOnly": true, "daysBack": 14, "maxNotices": 25 }
```

```json
{ "sources": "find_a_tender", "cpvCodes": ["45000000"], "buyerName": "council", "minValue": 1000000 }
```

```json
{ "sources": "contracts_finder", "stages": ["award"], "keywords": ["cyber", "security"], "daysBack": 30 }
```

```json
{ "noticeIds": ["093588-2026", "https://www.contractsfinder.service.gov.uk/Notice/91ac2c18-297c-4746-a349-4c78d487a796"] }
```

### Pricing (pay-per-event)

| Event | Price | When |
|---|---|---|
| `apify-actor-start` | $0.001 | Once per run |
| `tender-notice` | **$0.003** | Each successful notice card saved to the dataset |

**Free:** not-found / invalid IDs, `no_matches` rows, rate-limited / timeout / server-error rows after retries. Your maximum charge per run is respected: the Actor stops writing billable cards when the limit is reached.

### Rate limits and etiquette

Requests send a User-Agent that identifies this Actor. Requests to each host are spaced out, and 429 responses (and Contracts Finder's 403 "rate limit" page) back off with Retry-After or exponential delays. Contracts Finder asks clients to pause about 5 minutes after a rate-limit response. If retries run out, the source is reported as a free `rate_limited` row.

### Data licence

Notice data © Crown copyright, available under the [Open Government Licence v3.0](https://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/). Source APIs: [Find a Tender OCDS](https://www.find-tender.service.gov.uk/apidocumentation/1.0/GET-ocdsReleasePackages), [Contracts Finder OCDS Search](https://www.contractsfinder.service.gov.uk/apidocumentation/Notices/1/GET-Published-Notice-OCDS-Search).

### Not included

- HTML scraping of tender websites or attached documents
- Contact emails, phone numbers or named contact lists. This is not a lead-generation tool
- EU TED notices (the UK left TED after 2020; FTS replaces it for UK notices)

# Changelog

This Actor's version history is a separate document: https://apify.com/ingenious_quip_bxq/uk-tenders-ocds-markdown/changelog.md

# Actor input Schema

## `query` (type: `string`):

Optional. All words must appear (case-insensitive) in title, description, buyer or CPV text. Use "quotes" for a phrase. The official OCDS APIs have no keyword search, so this filter runs on each page of notices in the date window.

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

Optional. A notice matches if ANY of these keywords appears. Combined with `query` as AND.

## `buyerName` (type: `string`):

Optional. Case-insensitive substring of the buyer / contracting authority name, e.g. "NHS" or "County Council".

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

Optional. CPV prefixes. Trailing zeros widen the match: 72000000 matches all IT services (72…), 48000000 software packages, 45233251 one exact code.

## `sources` (type: `string`):

Which official service(s) to search.

## `stages` (type: `array`):

OCDS stages to request: planning (pipeline / prior info), tender (open opportunities), award (contract awards). Empty = all stages.

## `dateFrom` (type: `string`):

Optional. YYYY-MM-DD or YYYY-MM-DDTHH:MM:SS. Default = dateTo (or now) minus `daysBack`. Contracts Finder filters on publishedFrom; Find a Tender on updatedFrom.

## `dateTo` (type: `string`):

Optional. YYYY-MM-DD (end of day) or YYYY-MM-DDTHH:MM:SS. Default = now.

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

Look-back window in days when dateFrom is not set.

## `openOnly` (type: `boolean`):

Keep only notices whose tender deadline is still in the future.

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

Optional. Keep only notices with an estimated/awarded value at or above this amount (notice currency, usually GBP). Notices without a value are dropped when set.

## `noticeIds` (type: `array`):

Optional. Fetch specific notices: Find a Tender notice IDs (093588-2026), FTS OCIDs (ocds-h6vhtk-06dab4), Contracts Finder notice GUIDs, or official notice / OCDS API URLs (find-tender.service.gov.uk/Notice/…, contractsfinder.service.gov.uk/Notice/…). URLs are converted to API calls; HTML pages are never fetched. When set, search is skipped unless 'Also run search' is on.

## `releaseUrls` (type: `array`):

Optional alias of noticeIds for OCDS API URLs (…/api/1.0/ocdsReleasePackages/…, …/Published/Notice/releases/<guid>.json, …/Published/OCDS/Release/<id>).

## `searchAlsoWithIds` (type: `boolean`):

By default a direct-fetch list skips the search. Turn on to do both.

## `maxNotices` (type: `integer`):

Maximum notice cards to output (and charge) per run, across all sources. Newest first.

## `maxPagesPerSource` (type: `integer`):

How many API pages (up to 100 releases each) to scan per source while looking for matches. Raise for narrow filters over wide windows.

## `latestPerOcid` (type: `boolean`):

Keep only the newest release per OCID (drops older updates of the same procurement in the window).

## `descriptionMaxChars` (type: `integer`):

Max characters of the notice description kept in the card / dataset.

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

Parallel API requests (sources / direct IDs). Kept low to respect the official services' rate limits.

## `maxRetries` (type: `integer`):

Retries on 429 / Contracts Finder 403 rate-limit pages / 5xx / timeouts, with backoff (Retry-After honoured).

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

Per request. Contracts Finder stage-filtered searches can take 10–20 s.

## Actor input object example

```json
{
  "query": "software",
  "sources": "both",
  "stages": [
    "tender"
  ],
  "daysBack": 7,
  "openOnly": false,
  "searchAlsoWithIds": false,
  "maxNotices": 50,
  "maxPagesPerSource": 10,
  "latestPerOcid": true,
  "descriptionMaxChars": 800,
  "maxConcurrency": 2,
  "maxRetries": 3,
  "requestTimeoutSecs": 60
}
```

# Actor output Schema

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

Dataset rows: ocid, noticeId, title, buyer, value, currency, publishedDate, deadline, status, stage, procedure, cpvCodes, documents, noticeUrl, source, markdown, errorClass.

## `reportMarkdown` (type: `string`):

Summary table + one Markdown card per tender notice.

## `summary` (type: `string`):

notices, scanned, bySource, byClass, charged, durationSecs, peakMemoryMb.

# 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 = {
    "query": "software",
    "stages": [
        "tender"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("ingenious_quip_bxq/uk-tenders-ocds-markdown").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 = {
    "query": "software",
    "stages": ["tender"],
}

# Run the Actor and wait for it to finish
run = client.actor("ingenious_quip_bxq/uk-tenders-ocds-markdown").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 '{
  "query": "software",
  "stages": [
    "tender"
  ]
}' |
apify call ingenious_quip_bxq/uk-tenders-ocds-markdown --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,ingenious_quip_bxq/uk-tenders-ocds-markdown"
        }
    }
}
```

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/agscd5p1G84GTaXA8/builds/V2e4xepehO8llVN1c/openapi.json
