Nexus Tools / Data guides

UK public tenders as JSON or CSV: Find a Tender and Contracts Finder OCDS APIs, a Python example, and a ready-made dataset

How to read UK procurement notices from the two official OCDS APIs yourself, what trips people up (a broken stages filter, many releases per notice, rate limits), and when a normalized feed is worth paying for.

Updated: November 2026. API behaviour below was tested live on 4 November 2026.

The short version

1. The two APIs

Find a TenderContracts Finder
EndpointGET https://www.find-tender.service.gov.uk/api/1.0/ocdsReleasePackagesGET https://www.contractsfinder.service.gov.uk/Published/Notices/OCDS/Search
DocsGET-ocdsReleasePackagesGET-Published-Notice-OCDS-Search
Date filterupdatedFrom, updatedTo as YYYY-MM-DDTHH:MM:SSpublishedFrom, publishedTo (ISO 8601)
Other filtersstages (planning, tender, award), but see belowstages (planning, tender, award, implementation), comma-separated
Page sizelimit 1 to 100, default 100limit 1 to 100, default 100
Next pagelinks.next (URL containing a cursor)links.next (URL containing a cursor)
Single noticeappend /104923-2026 (notice id) or an ocds-h6vhtk-... process idnot covered here
Rate limitingdocumented: 429 or 503 with a Retry-After headerdocumented: 403, wait 5 minutes
CoversAbove-threshold procurement for the UK, plus other notice types since the Procurement Act 2023 regimeBelow-threshold opportunities and awards of English buyers and central government

Minimal requests (both anonymous):

curl -s "https://www.find-tender.service.gov.uk/api/1.0/ocdsReleasePackages?limit=2&updatedFrom=2026-11-04T00:00:00"

curl -s "https://www.contractsfinder.service.gov.uk/Published/Notices/OCDS/Search?stages=tender&limit=2&publishedFrom=2026-11-04T00:00:00"

Both return a release package with the same envelope: uri, version ("1.1"), extensions, publishedDate, publisher (Cabinet Office), license (the OGL v3.0 URL), publicationPolicy, releases and links. Releases come newest first.

Find a Tender: what we found

Contracts Finder: what we found

2. DIY: both sources to CSV in Python

Tested against both live APIs on 4 November 2026: it returned 91 tender releases (81 from Find a Tender, 10 from Contracts Finder) from the last 24 hours, capped at 5 pages per source. Needs pip install requests. Put a real contact address in the User-Agent.

import csv
import time
from datetime import datetime, timedelta, timezone

import requests

HEADERS = {"User-Agent": "my-tender-script/1.0 (you@example.com)"}  # identify yourself
FTS = "https://www.find-tender.service.gov.uk/api/1.0/ocdsReleasePackages"
CF = "https://www.contractsfinder.service.gov.uk/Published/Notices/OCDS/Search"


def pages(url, params, pause, max_pages=5):
    """Follow links.next (a ready-made URL with the cursor) and obey Retry-After on 429/503."""
    next_url, next_params = url, params
    for _ in range(max_pages):
        for attempt in range(4):
            r = requests.get(next_url, params=next_params, headers=HEADERS, timeout=60)
            if r.status_code in (429, 503):
                time.sleep(int(r.headers.get("Retry-After", 10)) + 1)
                continue
            if r.status_code == 403:  # Contracts Finder says: wait 5 minutes
                raise RuntimeError("rate limited (403), stop and retry in 5+ minutes")
            r.raise_for_status()
            break
        else:
            raise RuntimeError("service unavailable")
        data = r.json()
        yield from data["releases"]
        next_url, next_params = data.get("links", {}).get("next"), None
        if not next_url:
            return
        time.sleep(pause)


def row(source, rel):
    t = rel.get("tender", {})
    value = t.get("value") or {}
    cpvs = [t.get("classification")] + (t.get("additionalClassifications") or [])  # Contracts Finder
    for item in t.get("items", []):                                                 # Find a Tender
        cpvs += item.get("additionalClassifications") or []
    cpv = [c["id"] for c in cpvs if c and c.get("scheme") == "CPV"]
    return {
        "source": source,
        "id": rel["id"],
        "published": rel["date"][:10],
        "title": t.get("title"),
        "buyer": (rel.get("buyer") or {}).get("name"),
        "cpv": ";".join(dict.fromkeys(cpv)),
        "value": value.get("amount", value.get("amountGross")),  # Find a Tender may give only amountGross
        "currency": value.get("currency"),
        "deadline": (t.get("tenderPeriod") or {}).get("endDate"),
    }


since = (datetime.now(timezone.utc) - timedelta(days=1)).strftime("%Y-%m-%dT%H:%M:%S")
rows = []

# Find a Tender: no usable `stages` filter, so keep releases whose tender document is a UK4 tender notice.
for rel in pages(FTS, {"limit": 100, "updatedFrom": since}, pause=1.2):
    docs = rel.get("tender", {}).get("documents", [])
    if any(d.get("noticeType") == "UK4" for d in docs):
        rows.append(row("find-a-tender", rel))

# Contracts Finder: `stages=tender` works server-side. Use a longer pause between pages.
for rel in pages(CF, {"stages": "tender", "limit": 100, "publishedFrom": since}, pause=2.5):
    rows.append(row("contracts-finder", rel))

with open("uk_tenders.csv", "w", newline="", encoding="utf-8") as f:
    w = csv.DictWriter(f, fieldnames=rows[0].keys())
    w.writeheader()
    w.writerows(rows)
print(len(rows), "tender releases")

It does not de-duplicate notices published on both services, does not keep only the latest Contracts Finder version per notice, and does not handle award notices or values in other shapes. That is the part that grows.

3. When the Actor saves time

The Apify Actor UK Public Tenders (by Nexus Scrapers) reads both official APIs and returns one schema. It pays off when you would otherwise maintain the glue code above:

Pricing is pay per result: $0.003 per result at the time of writing; the Actor page shows the current price.

What it does not do: it does not return tender documents (only notice data and links), does not cover Public Contracts Scotland, Sell2Wales or eSourcing NI below-threshold notices, and does not give lot-level detail. For a one-off export of a single stage, the script above is enough.

4. Real sample: 20 normalized records

These 20 records were produced on 4 November 2026 by running the Actor's own source and normalizer code locally against the live APIs, without the Apify platform: 12 tender notices (latest three days, 6 from each service) and 8 award notices (latest ten days, 4 from each service), merged and de-duplicated like the Actor does. They are the newest matches, not hand-picked. One notice contained an organisation e-mail address inside its free-text description; we replaced it with [email removed]. Everything else is unchanged.

Download: sample.json (full records, UTF-8) and sample.csv (flattened: lists joined with | , opens in Excel). Free to use under the licence in section 5.

NoticeSrcTypeBuyerTitleValueWinners
a17cebab…CFnoticeTHE NATIONAL TRUST FOR PLACES OF…National Trust - NI - Lead Designer & Multidisciplinary t…40,000 GBP-
104923-2026FTSnoticeStonewater LimitedStoke Road, Henlade10,300,000 GBP-
8665d1a9…CFnoticeTheatre Royal PlymouthTheatre Royal Plymouth - Architect-led Multi-disciplinary…750,000 GBP-
104919-2026FTSnoticeNorth Northamptonshire CouncilNorth Northamptonshire Council - Operation & maintenance…273,000 GBP-
e3696531…CFnoticeDERBY CITY COUNCILENQ1406 - LED Screens, Staging, PA Equipment and Lighting…95,000 GBP-
104918-2026FTSnoticeFermanagh and Omagh District Coun…TENDER FOR CAPITAL YOUTH PROGRAMME (ENNISKILLEN ROUND O)…162,000 GBP-
81aa3ffe…CFnoticeNewry, Mourne and Down District C…CA18639 - Tender 23/2026 - SUPPLY AND DELIVERY OF WORKSHO…--
104917-2026FTSnoticeNHS EnglandNational Education and Training for NHS Talking Therapies…125,264,006 GBP-
b6442fed…CFnoticeNorth East Lincolnshire Borough C…Railway Street Footbridge (20/79/49) Geotechnical Investi…90,000 GBP-
104914-2026FTSnoticeLondon Borough of HounslowTender for the Provision of Learning Disability And Autis…4,700,000 GBP-
6ea78e35…CFnoticeBalfour Beatty VINCI JV HS2BBV Sublot 4 - HSST1020 - SALTLEY VIADUCT GABION WALL1,000,000 GBP-
104907-2026FTSnoticeVICO HOMES LIMITEDKitchen Replacement Programme110,180,000 GBP-
429e8abe…CFawardSouth Tyneside CouncilProvision of an attendance collection service for CLA/CIN…185,828 GBPWelfare Call (LAC) Ltd
104925-2026FTSawardRoyal Borough of GreenwichNotice of Intention to Award the NextGen School Health an…10,014,104 GBPBROMLEY HEALTHCARE COMMUNITY INTE…
a003256f…CFawarda3b773bedbb680101b84a5305b961983Transformation Delivery Services - Transforming and Conne…41,400,000 GBPKPMG LLP
104924-2026FTSawardLondon Borough of BrentAdvocacy Services for Brent and Harrow2,224,358 GBPPOWHER
9b32358e…CFawardSouth Tyneside CouncilContact Centre and CRM Enablement55,000 GBPPRICE WATERHOUSE COOPERS LLP
104922-2026FTSawardUniversity Hospitals Birmingham N…UHB Meteor Park Warehouse Trucks166,413 GBPJungheinrich UK Ltd
488e5053…CFawardNHS EnglandTransformation Delivery Services - Establishing Operation…41,400,000 GBPPA CONSULTING SERVICES LIMITED
104921-2026FTSawardConstruction Industry Training Bo…Skills and Training 2026 Research (Sept)78,150 GBPNavigator Insight Limited

FTS = Find a Tender, CF = Contracts Finder. Value is the estimated value for tenders and the awarded value for awards (GBP); blank when the notice states none.

One Find a Tender record in full (description shortened):

{
  "schemaVersion": "1.0",
  "source": "find-a-tender",
  "sourceId": "104923-2026",
  "url": "https://www.find-tender.service.gov.uk/Notice/104923-2026",
  "documentUrls": {
    "html": "https://www.find-tender.service.gov.uk/Notice/104923-2026",
    "pdf": null,
    "xml": null
  },
  "noticeType": "contract-notice",
  "noticeTypeCode": "UK4",
  "procedureType": "open",
  "contractNature": "works",
  "title": "Stoke Road, Henlade",
  "description": "This procurement exercise is to support the delivery of 39 mixed-tenure homes on a greenfield site off Stoke Road, Henlade TA3 5FF, south of Henlade Close (Pha…",
  "language": "en",
  "buyer": {
    "name": "Stonewater Limited",
    "country": "GB",
    "city": "Leicester",
    "nutsCode": "UKF22",
    "buyerType": "regional-or-local-authority"
  },
  "cpvCodes": [
    {
      "code": "45000000",
      "label": "Construction work"
    }
  ],
  "mainCpv": "45000000",
  "mainCpvDivision": "Construction work",
  "estimatedValue": {
    "amount": 10300000,
    "currency": "GBP"
  },
  "lots": 1,
  "publicationDate": "2026-11-04",
  "submissionDeadline": "2026-12-16T17:00:00+00:00",
  "awardInfo": null,
  "placeOfPerformance": {
    "country": "GB",
    "city": null,
    "nutsCode": "UKK12"
  },
  "scrapedAt": "2026-11-04T21:56:10+00:00",
  "ukOcid": "ocds-h6vhtk-07b368",
  "ukBuyerReference": "00378-DVP",
  "ukProcurementMethodDetails": "Open procedure",
  "ukRegions": [
    "South West"
  ],
  "ukAboveThreshold": true,
  "ukFramework": null,
  "ukSuitability": {
    "sme": true,
    "vcse": true
  },
  "ukOcdsUrl": "https://www.find-tender.service.gov.uk/api/1.0/ocdsReleasePackages/104923-2026",
  "ukAlsoPublishedOn": []
}

5. Data licence and attribution

Every release package carries a license field pointing to the Open Government Licence v3.0. Under the OGL you may copy, publish, distribute, adapt and use the information commercially, as long as you acknowledge the source and include the attribution statement. Use the standard wording: "Contains public sector information licensed under the Open Government Licence v3.0." Name the publisher too (Cabinet Office, Find a Tender / Contracts Finder). The OGL does not cover personal data protected by data protection law, and notices can include names and contact details, so handle those in line with UK GDPR. CPV labels in the sample come from the EU's CPV 2008 vocabulary (Regulation (EC) No 213/2008), published by the Publications Office of the European Union. This is a summary, not legal advice; read the licence for the exact terms.

Links