Developer guides

Contracts Finder API in depth: OCDS search, stages, paging and overlap with Find a Tender

The Contracts Finder search endpoint needs no key and returns Open Contracting (OCDS) JSON under the Open Government Licence v3.0; it is the only UK source for most contracts below the Find a Tender thresholds. Source: Arcadium Data, data as of .

Contracts Finder is the UK government service where authorities in England advertise opportunities and publish awards, including contracts far below the thresholds that force a notice on Find a Tender. If you want the long tail of UK public spending (a council cleaning contract, a school IT framework call-off) this is the place. If you want the big nationally visible tenders, you also need Find a Tender, and the two overlap.

The search API is small, but three things cost people days: the stage filter, the meaning of a zero value, and notices that appear on both services. This guide covers the request, a safe daily loop, and those traps.

The official route: the OCDS search endpoint

The request

GET https://www.contractsfinder.service.gov.uk/Published/Notices/OCDS/Search takes a publication window and a stage list and returns an OCDS release package. The parameters we use:

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"

Python

import time
import requests

def cf_day(day: str, next_day: str):
    url = (
        "https://www.contractsfinder.service.gov.uk/Published/Notices/OCDS/Search"
        f"?publishedFrom={day}T00:00:00&publishedTo={next_day}T00:00:00"
        "&stages=planning,tender,award&limit=100"
    )
    while url:
        pkg = requests.get(url, timeout=60).json()
        releases = pkg.get("releases", [])
        yield from releases
        url = pkg.get("links", {}).get("next") if releases else None
        time.sleep(1.0)

for r in cf_day("2026-10-23", "2026-10-24"):
    print(r["id"], r.get("tag"), r.get("buyer", {}).get("name"))

What one release is

A release is one published notice. Its id is the notice UUID plus a counter (<uuid>-1, -2 for a later version), and the public page is https://www.contractsfinder.service.gov.uk/Notice/<uuid>. The ocid identifies the procedure, so an opportunity and its award share it. Group by ocid before you count procedures, and by the notice UUID before you count notices.

Traps in the fields

FieldTrapWhat to do
Award valueA value of 0 means "not stated", not freeTreat 0 as null; never sum it
Award value, currencyTypos produce awards of billions on small contractsCap or flag values far above the buyer's norm
Supplier partyIds are Contracts Finder's own (GB-CFS-n); a Companies House number is sometimes in identifierJoin to Companies House only when that number is present
Supplier namesThe register is full of sole tradersPublish only names with a company or public-body form
Party contact pointsNamed officials, emails and phonesDrop the whole contactPoint object at parse time
Cancelled noticesA status field marks a withdrawn opportunityRemove it from open-opportunity counts
Legacy noticesOlder Public Contracts Regulations 2015 notices share the feed with Procurement Act onesDo not assume the UK1 to UK17 codes are present

Overlap with Find a Tender

Some contracts are advertised on both services. The two do not share an ocid prefix, so the join is fuzzy. What works in practice: same buyer name and same title after lower-casing and stripping punctuation, same notice kind, publication dates within a few days of each other, and equal values and deadlines when both are present. Keep the Find a Tender row as the primary because its schema is richer, and mark the Contracts Finder row as the duplicate instead of deleting it, so you can undo a wrong match.

Rates and a daily loop

No numeric quota is published. A pause of about one second between requests ran a twelve-month backfill without trouble in our crawl. Run one day window per day, overlap the previous day by a few hours because late updates happen, store the last finished window, and keep raw JSON beside your parsed rows. If a run fails halfway, rerun the whole day: upserting by notice UUID makes that safe, and it is cheaper than reasoning about a half-read cursor. The licence is Open Government Licence v3.0: attribute the source and link the notice.

Reading the stages together

The three stages tell one story only if you join them. A planning release announces that a buyer intends to buy; a tender release opens the competition with a deadline; an award release names the supplier and the value. They share the ocid, so a pipeline view is a group-by on it. Not every procedure shows all three: many small contracts appear only as an award, published after the fact, and some opportunities never get a public award (a cancelled procedure, or a buyer that forgot). Do not read a missing award as a lost tender, and do not treat an award as a new opportunity. If you build a "contracts ending" list, use the award release and its contract period, then check the Find a Tender side for a pipeline notice of the replacement.

The fast route: one normalised UK market view

Arcadium Data reads Contracts Finder and Find a Tender every day, lists duplicates once, treats a zero value as missing, never reads contact points, and serves buyers, suppliers, CPV codes and values in EUR in one schema next to TED and the national boards of France, Germany, Poland and Bulgaria. The market summary for the UK over the last twelve months is open without a key:

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/markets?country=GB"

Python

import requests

data = requests.get("https://api.arcadiumdata.io/v1/tenders/markets?country=GB", timeout=30).json()
print(data["awards"], data["valueEur"], data["concentration"]["level"])
for w in data["topWinners"]:
    print(w["name"], w["awards"])

TypeScript

const res = await fetch('https://api.arcadiumdata.io/v1/tenders/markets?country=GB');
const data = (await res.json()) as { awards: number; valueEur: number; topWinners: { name: string; awards: number }[] };
console.log(data.awards, data.valueEur);
for (const w of data.topWinners) console.log(w.name, w.awards);

Response (abridged)

{
  "since": "2025-10-25", "until": "2026-10-25",
  "awards": 50186, "awardsWithWinner": 45198, "frameworks": 1503,
  "valueEur": 291295833387, "medianValueEur": 139953,
  "concentration": { "level": "unconcentrated", "top10Share": 0.014 },
  "topWinners": [{ "name": "SOFTCAT PLC", "awards": 144 }],
  "topBuyers": [{ "name": "NHS England", "awards": 1034 }]
}

Add cpv=72 to limit the view to IT services. With a free key, GET /v1/tenders with source=cf lists the Contracts Finder notices themselves (filters kind, cpv, publishedSince, openOn and a cursor). Winners of UK contracts rarely state a website, so company joins go through the Companies House number, not a domain. Contracts that end soon: re-tender data guide; browse UK tenders.

Comparison

Contracts Finder directArcadium Data /v1/tenders
KeyNoneNone for market summaries; free key for notice search
ScopeEngland, above and below thresholdsUK Contracts Finder plus Find a Tender, TED and national boards
FormatOCDS, nestedOne flat notice schema
Zero valuesYou must treat as missingStored as missing
Duplicates with Find a TenderFuzzy match yourselfListed once
Contact pointsIn the dataNever read
LicenceOGL v3.0OGL v3.0 for UK notices, source URL kept

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 Contracts Finder API need a key?

No. The OCDS search endpoint answers anonymous requests and the data is published under the Open Government Licence v3.0. There is no published quota, so keep about one second between requests.

The stages parameter takes planning (pipeline notices), tender (open opportunities) and award (awards and award updates), comma separated. Daily loops usually request all three.

How do I page through Contracts Finder results?

Request a publication window with up to 100 releases and follow links.next from each package until a page comes back empty. One window per day is the safest way to backfill.

Why is an award value zero?

Contracts Finder uses 0 when the value was not stated, so treat it as missing. Also watch for typos that turn small awards into billions.

Is Contracts Finder the same as Find a Tender?

No. Contracts Finder covers England including low-value contracts; Find a Tender is the national platform for above-threshold contracts and Procurement Act notices. Some contracts appear on both.

Cite as: Arcadium Data, "Contracts Finder API in depth: OCDS search, stages, paging and overlap with Find a Tender", https://arcadiumdata.io/guides/contracts-finder-api/, data as of 2026-10-25.