The UK publishes public-sector tenders on two official sites: Find a Tender (UK-wide, mostly higher-value contracts) and Contracts Finder (England, lower-value contracts). Both have open-data APIs, but they use different endpoints, different date filters and a strict rate limit. If all you want is "new software tenders over £50k, every morning", that is a lot of plumbing.
This guide uses the UK Tenders Actor on Apify, which reads both official APIs and returns one flat record per notice.
1. Pick your filters
Tenders are classified with CPV codes. A few common divisions:
| CPV | Sector |
|---|---|
| 72000000 | IT services |
| 48000000 | Software packages |
| 45000000 | Construction work |
| 79000000 | Business services |
| 85000000 | Health and social work |
A division code matches everything under it, so 72000000 covers all IT services.
2. Run it once
curl -X POST "https://api.apify.com/v2/acts/studded_lantana~uk-tenders/run-sync-get-dataset-items" \ -H "Authorization: Bearer $APIFY_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "stages": ["tender"], "daysBack": 2, "cpvCodes": ["72000000", "48000000"], "minValue": 50000, "openOnly": true, "maxItems": 100 }' You get back a JSON array. Each item has title, buyer_name, value_amount, tender_deadline, cpv_descriptions and a url to the official notice.
daysBack counts calendar days in UTC including today, so 2 means yesterday and today. With 1 a morning run would only see what was published since midnight.
Setting minValue drops notices where the buyer published no value. If you would rather see everything, leave it out.
3. Turn it into a morning digest
import os from apify_client import ApifyClient client = ApifyClient(os.environ["APIFY_TOKEN"]) run = client.actor("studded_lantana/uk-tenders").call(run_input={ "stages": ["tender"], "daysBack": 2, "cpvCodes": ["72000000", "48000000"], "openOnly": True, }) for n in client.dataset(run.default_dataset_id).iterate_items(): value = f"£{n['value_amount']:,.0f}" if n.get("value_amount") else "value not published" deadline = (n.get("tender_deadline") or "no deadline given")[:10] print(f"{n['title']}\n {n['buyer_name']} | {value} | closes {deadline}\n {n['url']}\n") Send that text wherever you read things: email, Slack or a Google Sheet. A narrow filter can return nothing on a quiet day: when I ran this on a Saturday in October 2026, the two days held 24 notices and none was an open IT tender. Over the seven days before, the same filter returned several.
4. Schedule it
In the Apify console, open the Actor, save your input as a task, and add a schedule (for example 0 7 * * 1-5 for weekdays at 07:00). Because each run covers two days, every notice shows up on two consecutive runs, and Find a Tender filters by the date a notice was last updated, so an amended notice can come back later. Keep the source and notice_id of what you have already sent and skip those.
5. See who is winning
Change "stages" to ["award"] and each record gains award_value_total, award_date and a suppliers list. That tells you which companies win work from the buyers you care about.
Cost
$0.005 per run plus $0.002 per notice returned. A run that returns 20 notices costs 4.5 cents.
The data is public sector information licensed under the Open Government Licence v3.0. The Actor is not affiliated with the Cabinet Office, Find a Tender or Contracts Finder.

