Developer guides

UK Find a Tender API: OCDS release packages, Contracts Finder and the Procurement Act

UK public notices are open data: Find a Tender and Contracts Finder both publish OCDS JSON without a key under the Open Government Licence v3.0, and since 24 February 2025 every notice carries a Procurement Act code from UK1 to UK17. Source: Arcadium Data, data as of .

Two services cover UK procurement. Find a Tender (FTS) is the national platform for above-threshold contracts and, since the Procurement Act 2023 came into force on 24 February 2025, for the notices that act requires. Contracts Finder (CF) lists opportunities and awards of central and local government in England, including below-threshold contracts. Both publish the Open Contracting Data Standard (OCDS), a JSON format where one release is one published notice and the ocid joins every notice of the same procedure.

The official route is simple to start and slow to finish: paging, rate limits and a deeply nested schema. This guide covers both endpoints, the traps in the data, and the shortcut.

The official route: OCDS release packages

Find a Tender

GET https://www.find-tender.service.gov.uk/api/1.0/ocdsReleasePackages needs no key. It filters by update time (updatedFrom, updatedTo, ISO date-times) and returns a release package with a releases array and a links.next URL while more pages remain. The page size goes up to 100 (limit). Follow links.next until a page comes back with no releases.

curl

# All Find a Tender releases updated on 2026-10-23 (first page of 100)
curl -s "https://www.find-tender.service.gov.uk/api/1.0/ocdsReleasePackages?updatedFrom=2026-10-23T00:00:00&updatedTo=2026-10-24T00:00:00&limit=100"

Python

import time
import requests

def fts_day(day_from: str, day_to: str):
    """Yield every release updated in [day_from, day_to). Dates as YYYY-MM-DD."""
    url = (
        "https://www.find-tender.service.gov.uk/api/1.0/ocdsReleasePackages"
        "?updatedFrom=" + day_from + "T00:00:00&updatedTo=" + day_to + "T00:00:00&limit=100"
    )
    while url:
        r = requests.get(url, timeout=60)
        if r.status_code == 429:  # the platform sheds bursts: wait as told
            time.sleep(int(r.headers.get("Retry-After", "120")))
            continue
        r.raise_for_status()
        pkg = r.json()
        releases = pkg.get("releases", [])
        yield from releases
        url = pkg.get("links", {}).get("next") if releases else None
        time.sleep(1.5)  # about one request per 1.5 s keeps a long backfill moving

for release in fts_day("2026-10-23", "2026-10-24"):
    print(release["ocid"], release.get("tag"), release.get("buyer", {}).get("name"))

TypeScript

type Release = { ocid: string; tag?: string[]; buyer?: { name?: string } };
type Pkg = { releases?: Release[]; links?: { next?: string } };

export async function* ftsDay(from: string, to: string): AsyncGenerator<Release> {
  let url: string | undefined =
    'https://www.find-tender.service.gov.uk/api/1.0/ocdsReleasePackages?updatedFrom=' +
    from + 'T00:00:00&updatedTo=' + to + 'T00:00:00&limit=100';
  while (url) {
    const res = await fetch(url);
    if (res.status === 429) {
      await new Promise((r) => setTimeout(r, Number(res.headers.get('retry-after') ?? 120) * 1000));
      continue;
    }
    if (!res.ok) throw new Error('FTS ' + res.status);
    const pkg = (await res.json()) as Pkg;
    yield* pkg.releases ?? [];
    url = pkg.releases?.length ? pkg.links?.next : undefined;
    await new Promise((r) => setTimeout(r, 1500));
  }
}

Contracts Finder

The Contracts Finder search API is GET https://www.contractsfinder.service.gov.uk/Published/Notices/OCDS/Search with publishedFrom, publishedTo, stages (planning, tender, award, comma separated), limit (100) and a cursor in links.next. It is anonymous too. The two services overlap for some contracts, so deduplicate by procedure before you count.

curl

curl -s "https://www.contractsfinder.service.gov.uk/Published/Notices/OCDS/Search?publishedFrom=2026-10-23T00:00:00&publishedTo=2026-10-24T00:00:00&stages=planning,tender,award&limit=100"

Rate limits and etiquette

Neither service publishes a numeric quota. In our crawl, Find a Tender answers bursts with HTTP 429 and Retry-After: 120 after a few dozen requests at one per second; a gap of about 1.5 seconds between requests kept a twelve-month backfill running. Read the day windows one at a time, honour Retry-After, and store the last updatedTo you finished so a restart does not repeat work.

The fields that matter

OCDS pathWhat it holdsGotcha
ocidThe procedureOne procedure has many releases (planning, tender, award, changes); group by it
tag[]Stage: planning, tender, award, contract, ...A release can carry several tags
documents[].noticeTypeThe Procurement Act code UK1-UK17Sits under tender, planning, awards[] or contracts[] depending on the stage
parties[] with rolesBuyer, supplier, and so onMatch buyer.id to the party; supplier ids use schemes such as GB-COH (Companies House) and GB-PPON
tender.classification, items[].classificationCPV codesFilter on scheme = CPV
awards[].value, contracts[].valueAwarded valueAward values often sit only on contracts[]; frameworks state a ceiling, not spend
contracts[].periodStart, end, durationThe input for "when does it end"

A backfill plan

Procurement Act notices start on 24 February 2025, so a full history is a bit under two years of day windows. Run one window per day, oldest first, write each finished day to a checkpoint, and keep the raw release JSON next to your parsed rows: the schema is large and you will want fields you did not parse the first time. At about 1.5 seconds per request and 100 releases per page, a typical day takes a few minutes; budget a few hours for the whole history and expect the occasional 429 pause. Run the daily update the same way, with a window that overlaps the last finished day by a few hours, because releases are dated by update time and late updates happen.

Joining notices to companies

Supplier parties carry identifiers. When the scheme is GB-COH, the value is a Companies House number you can check against the register; GB-PPON is the Public Procurement Organisation Number. Contracts Finder suppliers use their own party ids and only sometimes a Companies House number. A website is rarely stated, so joining UK winners to domains needs the Companies House number plus a second source.

Personal data warning

Personal data warning

Every party can carry a contactPoint with a named official, an email and a phone number. Drop it in your parser; you almost never need it, and storing it makes you a controller of personal data. Supplier registers (especially Contracts Finder) also hold sole traders, so apply a rule such as "only names with a company or public-body form" before you publish winners.

The notice codes

The Procurement Act 2023 replaced the old notice names with UK1 to UK17: pipeline (UK1), planned procurement (UK3), tender (UK4), contract award (UK6), contract details (UK7), contract change (UK10), contract termination (UK11) and more. Our notice types guide explains each code and which ones find a re-tender.

Licence: Open Government Licence v3.0. Attribute the source and keep the notice URL (https://www.find-tender.service.gov.uk/Notice/{id}).

The fast route: normalised notices, one schema

Arcadium Data reads Find a Tender and Contracts Finder daily next to TED and the national boards of France, Germany, Poland and Bulgaria, and serves one normalised notice: buyer, country and region, CPV codes, values in EUR, deadline, winning organisations, with duplicates between sources listed once. Contact points are never read.

GET /v1/tenders filters by source (fts for Find a Tender, cf for Contracts Finder), country, cpv, kind, openOn and more. The search and paging need a free key; anonymous calls to this route are limited to winnerDomain lookups. Without a key, the contract-end view for the UK is open:

Calls on this page work without a key from one IP (10 requests a minute, 100 a day, short answers, point lookups). A free key lifts the limits: sign up. Routes, parameters and fields: OpenAPI.

curl

curl -s "https://api.arcadiumdata.io/v1/tenders/expiring?country=GB&kind=pipeline&limit=2"

Response (abridged)

{
  "from": "2026-10-25",
  "to": "2028-04-25",
  "count": 2,
  "data": [{
    "id": "fts:033454-2026",
    "title": "SSP-FTS-61450 - Provision of Commercial Storage and Distribution Services for Medical Consumables",
    "buyer": { "name": "NHS Wales Shared Services Partnership-Procurement Services (hosted by Velindre University NHS Trust)", "country": "GB", "nuts": "UKL15" },
    "cpvCodes": ["63120000"],
    "value": { "eur": 876208.67, "basis": "estimate", "band": "100k_1m" },
    "startOn": "2026-08-01",
    "endOn": "2027-07-31",
    "endBasis": "end_date",
    "expectedRetender": { "from": "2026-10-31", "to": "2027-05-31" },
    "noticeCode": "UK3",
    "url": "https://www.find-tender.service.gov.uk/Notice/033454-2026"
  }]
}

With a key, search the notices themselves:

curl

curl -s "https://api.arcadiumdata.io/v1/tenders?source=fts&country=GB&kind=competition&openOn=2026-10-25&limit=50" \
  -H "Authorization: Bearer $ARCADIUM_API_KEY"

Python

import os
import requests

headers = {"Authorization": "Bearer " + os.environ["ARCADIUM_API_KEY"]}
url = "https://api.arcadiumdata.io/v1/tenders?source=fts&kind=competition&cpv=72&openOn=2026-10-25&limit=50"
while url:
    page = requests.get(url, headers=headers, timeout=60).json()
    for n in page["data"]:
        print(n["id"], n["buyer"]["name"], n["deadlineOn"], n["url"])
    cursor = page.get("nextCursor")
    url = (
        "https://api.arcadiumdata.io/v1/tenders?source=fts&kind=competition&cpv=72&openOn=2026-10-25&limit=50&cursor=" + cursor
        if cursor
        else None
    )

TypeScript

const key = process.env.ARCADIUM_API_KEY!;
let cursor: string | null = null;
do {
  const url =
    'https://api.arcadiumdata.io/v1/tenders?source=cf&kind=award&country=GB&limit=50' + (cursor ? '&cursor=' + cursor : '');
  const res = await fetch(url, { headers: { Authorization: 'Bearer ' + key } });
  const page = (await res.json()) as { data: { id: string; title: string; winners: string[] }[]; nextCursor: string | null };
  for (const n of page.data) console.log(n.id, n.winners.join(', '));
  cursor = page.nextCursor;
} while (cursor);

Row fields include id (fts:033454-2026), kind, procedureId (the OCDS ocid), noticeType, cpvCodes, estimatedValue, awardValue, winners, winnerDomains and the official notice url. UK winners rarely state a website, so winnerDomains is often empty for them. For the free tool behind the contract-end view, see Re-tender Radar; for what each UK code means, the notice types guide.

Comparison

Find a Tender / Contracts Finder directArcadium Data /v1/tenders
KeyNoneFree key for search; anonymous for contract-end and winner lookups
FormatOCDS, nested; you map itOne flat notice schema across UK, EU and national boards
PagingUpdate-time windows and links.nextFilters plus nextCursor
Rate limits429 with Retry-After: 120 on burstsPer plan; headers on every response
Duplicates FTS vs CFYou find themListed once
Contact pointsIn the dataNever read
Contract end dateYou derive it from contracts[].periodendOn with endBasis and an expected re-tender window
LicenceOGL v3.0OGL v3.0 for UK notices, source URL on every row

Get the data without the plumbing

Arcadium Data serves these datasets from one REST API and one MCP server, with sources, licences and dates on every answer. No key is needed to try the routes in this guide.

Sources

Last updated 2026-10-25. This guide is ours and independent of the organisations it describes. Found a mistake? Tell us and we correct it.

Questions

Does the Find a Tender API need an API key?

No. The OCDS endpoint at find-tender.service.gov.uk/api/1.0/ocdsReleasePackages answers anonymous requests. It publishes under the Open Government Licence v3.0. Expect HTTP 429 when you send bursts and honour the Retry-After header.

What is the difference between Find a Tender and Contracts Finder?

Find a Tender is the national platform for above-threshold contracts and the Procurement Act 2023 notices. Contracts Finder lists opportunities and awards in England, including below-threshold contracts. They overlap for some contracts.

How do I page through all releases?

Query a time window with updatedFrom and updatedTo, then follow links.next from each release package until a page has no releases. Use one window per day for a backfill.

Where is the Procurement Act notice code in the OCDS data?

In documents[].noticeType, which sits under tender, planning, awards[] or contracts[] depending on the stage. The values run from UK1 to UK17.

Can I use the data commercially?

Yes, under the Open Government Licence v3.0 with attribution. Do not keep the named contact points in the notices; they are personal data.

Cite as: Arcadium Data, "UK Find a Tender API: OCDS release packages, Contracts Finder and the Procurement Act", https://arcadiumdata.io/guides/uk-find-a-tender-api/, data as of 2026-10-25.