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:
publishedFromandpublishedTo: ISO date-times, interpreted in UK time. Use midnight to midnight windows.stages: comma separated, fromplanning(pipeline notices),tender(open opportunities) andaward(awards and award updates).limit: up to 100 releases a page.- The package has
links.nextwhile more pages remain. Follow it until a page has no releases; do not build the cursor yourself.
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
| Field | Trap | What to do |
|---|---|---|
| Award value | A value of 0 means "not stated", not free | Treat 0 as null; never sum it |
| Award value, currency | Typos produce awards of billions on small contracts | Cap or flag values far above the buyer's norm |
| Supplier party | Ids are Contracts Finder's own (GB-CFS-n); a Companies House number is sometimes in identifier | Join to Companies House only when that number is present |
| Supplier names | The register is full of sole traders | Publish only names with a company or public-body form |
| Party contact points | Named officials, emails and phones | Drop the whole contactPoint object at parse time |
| Cancelled notices | A status field marks a withdrawn opportunity | Remove it from open-opportunity counts |
| Legacy notices | Older Public Contracts Regulations 2015 notices share the feed with Procurement Act ones | Do 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 direct | Arcadium Data /v1/tenders | |
|---|---|---|
| Key | None | None for market summaries; free key for notice search |
| Scope | England, above and below thresholds | UK Contracts Finder plus Find a Tender, TED and national boards |
| Format | OCDS, nested | One flat notice schema |
| Zero values | You must treat as missing | Stored as missing |
| Duplicates with Find a Tender | Fuzzy match yourself | Listed once |
| Contact points | In the data | Never read |
| Licence | OGL v3.0 | OGL 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.
Related
- UK Find a Tender API guide
- UK procurement notice types UK1-UK17
- Who won government contracts
- CPV codes explained
- UK tenders
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.
What are the stages in the Contracts Finder OCDS search?
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.