UK public-sector contracts are published openly. You do not need a paid tender portal to see them. Since the Procurement Act 2023 changes took effect on 24 February 2025, every new UK public procurement notice goes on Find a Tender. Contracts Finder still holds older notices and many below-threshold opportunities in England.
This guide shows the official, free routes first: website alerts with no code, then the open APIs if you want a spreadsheet or a Slack ping. At the end, there is an optional shortcut if you would rather not maintain the plumbing.
What you are actually looking at
A “tender alert” is just a filter over public notices. The useful fields are usually:
- notice type (planning / pipeline, tender, or award)
- title, buyer and description
- estimated or awarded value, and currency
- CPV codes (the EU/UK category codes for the thing being bought)
- region or delivery location
- deadline and a link back to the official notice
- for awards: the winning supplier, and sometimes a company number
The Cabinet Office publishes this data under the Open Government Licence v3.0. Official developer docs are on Find a Tender’s data and API page and the Contracts Finder API help. A short overview also sits on GOV.UK’s Open Contracting page.
The no-code route: official saved-search emails
If you only want an email when something matches your work, start here. It is free and official.
- Open the Find a Tender search.
- Sign in with a GOV.UK One Login account (create one if you do not have one).
- Run a search with your keywords, CPV codes, region or notice type.
- Save the search and choose how often you want email notifications.
Do the same on Contracts Finder if you also care about older or lower-value English notices. Many SMEs stop at this step, and that is a perfectly good outcome.
The limits show up quickly if you are a bid writer or a salesperson rather than a casual browser:
- you still have two websites, not one feed
- saved emails are hard to dump into Excel, a CRM or Slack
- CPV and region filters are coarser than “CPV prefix 72, London and the South East, value over £50,000, tenders and awards only, last seven days, skip anything I have already seen”
- award notices are awkward to turn into a “who just won, and what is their company number?” list
- you cannot easily keep a clean “only new since yesterday” dataset
If those limits do not bother you, you can stop reading. The official emails are the right tool.
The DIY API route: both official OCDS feeds
Both services expose a public Open Contracting Data Standard (OCDS) API. You do not need an API key for these read endpoints. You will need a little glue: pagination, de-duplication, and your own filters.
Find a Tender, last seven days of tender-stage notices:
curl -sS "https://www.find-tender.service.gov.uk/api/1.0/ocdsReleasePackages?updatedFrom=2026-11-05T00:00:00&updatedTo=2026-11-12T23:59:59&stages=tender&limit=100"
Useful query parameters, from the official release-package docs: limit (max 100), cursor for the next page, updatedFrom / updatedTo, and stages (planning, tender, award).
Contracts Finder, same idea:
curl -sS "https://www.contractsfinder.service.gov.uk/Published/Notices/OCDS/Search?publishedFrom=2026-11-05T00:00:00&publishedTo=2026-11-12T23:59:59&stages=tender&limit=100"
The Contracts Finder OCDS search docs use publishedFrom, publishedTo, stages, limit and cursor. There is also a daily CSV harvester if you only want that day’s Contracts Finder notices.
A minimal Python sketch that writes a flat CSV from Find a Tender:
import csv, json, urllib.request
url = (
"https://www.find-tender.service.gov.uk/api/1.0/ocdsReleasePackages"
"?updatedFrom=2026-11-05T00:00:00&updatedTo=2026-11-12T23:59:59"
"&stages=tender&limit=100"
)
with urllib.request.urlopen(url) as r:
package = json.load(r)
rows = []
for release in package.get("releases", []):
tender = release.get("tender", {})
buyer = (release.get("buyer") or [{}])[0] if isinstance(release.get("buyer"), list) else (release.get("buyer") or {})
title = tender.get("title") or ""
if "software" not in title.lower() and "digital" not in title.lower():
continue
value = (tender.get("value") or {}).get("amount")
rows.append({
"id": release.get("id"),
"ocid": release.get("ocid"),
"title": title,
"buyer": buyer.get("name"),
"value": value,
"deadline": (tender.get("tenderPeriod") or {}).get("endDate"),
})
with open("tenders.csv", "w", newline="", encoding="utf-8") as f:
w = csv.DictWriter(f, fieldnames=rows[0].keys() if rows else ["id"])
w.writeheader()
w.writerows(rows)
print(f"Wrote {len(rows)} rows")
That is enough for a one-off research pull. To turn it into a reliable daily alert you still have to:
- follow
cursoruntil the results are exhausted (100 notices per page) - call Contracts Finder as well, then de-duplicate by notice or OCID
- map nested OCDS parties, items, values, CPV classifications and awards into flat columns
- store notice IDs so tomorrow’s run only emits new rows
- retry on HTTP 429 and occasional 5xx responses
- optionally match buyers and winning suppliers to a Companies House number (the Companies House API is free, with its own key and rate limits)
None of that is difficult. It is just work you have to keep running.
The shortcut, if you do not want to maintain a scraper
If you want one filtered feed, in JSON, CSV or Excel, with an “only new” mode you can schedule, UK Government Tenders Feed is a small Actor on Apify Store that reads those same official APIs. It does not scrape behind a login. Pricing is pay-per-result: $5 per 1,000 results, plus a $0.00005 start charge. You can cap the maximum cost of a run.
A typical input for “IT and digital tenders and awards, London and the South East, last seven days, only new rows” looks like this:
{
"keywords": ["software", "digital"],
"cpvCodes": ["72000000"],
"regions": ["London", "South East"],
"valueMin": 50000,
"noticeTypes": ["tender", "award"],
"daysBack": 7,
"onlyNew": true,
"maxResults": 200
}
cpvCodes are prefixes: 72 or 72000000 matches IT services, 45 is construction, 79 business services, 85 health and social work. Regions accept names or ITL/NUTS codes (UKI, UKJ, UKM…).
A flattened output row looks like this:
{
"id": "104641-2026",
"ocid": "ocds-h6vhtk-...",
"noticeType": "award",
"title": "Lakes Estate Regeneration",
"buyerName": "Milton Keynes City Council",
"regions": ["UKJ12"],
"value": 72400000,
"currency": "GBP",
"cpvCodes": ["45000000"],
"status": "complete",
"awardSuppliers": ["John Graham Construction Ltd Trading as GRAHAM"],
"supplierCompanyNumbers": ["NI003503"],
"awardValue": 72400000,
"url": "https://www.find-tender.service.gov.uk/Notice/104641-2026",
"source": "Find a Tender",
"isNew": true
}
Turn on Only new since last run and schedule it daily in Apify. From there you can send new rows to email, Slack, Google Sheets or a CRM.
Optional: paste a free Companies House API key to match buyers and winning suppliers when the notice itself does not already print a company number.
Which route should you pick?
- Browsing and a daily email: official saved searches on Find a Tender and Contracts Finder.
- A one-off research extract, and you are happy writing a script: the official OCDS APIs, free.
- A scheduled, filtered, de-duplicated feed in a spreadsheet, with award suppliers and an “only new” flag: the Actor is the shorter path.
Either way, the source of truth stays the official notices. If a row matters commercially, open the url and read the notice on GOV.UK before you bid.
Contains public sector information licensed under the Open Government Licence v3.0. Source: Find a Tender service and Contracts Finder (Cabinet Office).