UK Tenders API

Search UK public contract notices and awards

The UK Tenders API returns contract opportunities and awards published by UK public bodies on Contracts Finder — central government, the NHS, councils and universities — as clean JSON, with no api_key.

no credit card1,000 free credits · instant API key · live in 10 seconds
Missing a UK Tenders endpoint, or need a source we don't have yet?Contact us real people · same-day reply.
U
/uk-tenders/v1

2 active endpoints. Every call is 1 credit.

  • POST/uk-tenders/v1/search
  • POST/uk-tenders/v1/notice

What UK Tenders endpoints does ReefAPI ship?

2 live read endpoints. Read-only data API: no writes, no account actions, no dashboard access on the target site.

2 endpoints

search

1 cr

Search UK public-sector contract notices by phrase, status, value, deadline, CPV code and pub…

required
q
optional
phrase, statuses, notice_types, published_from, published_to, published_within_days, deadline_from, min_value, cpv_codes, sort, limit

notice

1 cr

One notice in full by id.

required
id
optional

Every parameter, every allowed value →

UK Tenders API

2 of 2 endpoints, ready to run

View docs ↗

UK public-body notices with the full description already in the row, the published value range, the winning supplier and awarded amount where there is one, CPV codes, deadline, region and status.

1 credit1 required · 5 optional
POST/uk-tenders/v1/search
idle
// Press "Try it" and this pane shows exactly what the
// live site returned this second — including an empty
// result, if that is the truth. No key, no account.

How the UK Tenders API works

UK Tenders is a normal ReefAPI surface — the same four rules that hold for every other engine on the key.

01
Authenticate
x-api-key header

No OAuth app, no request signing, no per-site account. One key covers all 185 engines.

02
Call
POST /uk-tenders/v1/…

Every route is a POST with a JSON body. Parameters are validated against the published schema before anything is charged.

03
Pay
1 credit per call

Credits, not seats. Failed and blocked calls are never charged, and cache hits cost nothing.

04
Read
{ ok, data, meta, error }

One envelope everywhere. meta carries latency_ms, record_count and the endpoint that answered.

Filter for freshness upstream, because sorting will not do it for you

This is the one thing to get right here. The sort is applied to the rows that came back, not to the whole result set, so asking for the twenty-five newest of five hundred and seventy does not give you the twenty-five newest.

01search
POST/uk-tenders/v1/search
{"q": "software", "published_within_days": 30, "limit": 25}

The date filter runs upstream, so this really does narrow the corpus before anything is sorted. Same query without it reached back to 2015.

02search
POST/uk-tenders/v1/search
{"q": "software", "statuses": "Open", "limit": 25}

The live slice. Every row already carries the full description, so there is no second call for the requirement.

03notice
POST/uk-tenders/v1/notice
{"id": "..."}

For an award, this is where the money is: supplier, address, amount, procedure type, small-business flag, and the view count as a rough competition signal.

One credit for a page of up to five hundred notices with their descriptions and their awards already attached. Use it to watch a category, or to price a bid against what the same buyer paid last time.

request
curl -X POST https://api.reefapi.com/uk-tenders/v1/search \
  -H "x-api-key: $REEF_KEY" \
  -H "content-type: application/json" \
  -d '{"q":"web scraping","limit":10}'
response envelope
{
  "ok": true,
  "data": { … },
  "meta": {
    "api": "uk-tenders",
    "endpoint": "search",
    "mode": "live",
    "latency_ms": …,
    "record_count": …
  },
  "error": null
}

The value fields, and the one you must never sum

Contract values on Contracts Finder are the part everyone gets wrong, because a framework agreement publishes the same ceiling in several places and none of the numbers mean what they look like. Every figure below is from a measured notice.

FieldMeasuredWhat it actually is
value_low / value_high before award0.0 and 500,000,000.0 on a framework; 100,000.0 and 500,000.0 on an ordinary contractthe buyer's published estimate range. A 0.0 low means not stated, not free.
value_low / value_high after award0.0 and 0.0 with value_currency nullemptied once status becomes Awarded — read awarded_value instead
awarded_value49,999,999.66the contract or framework ceiling, not one supplier's share
awards[].valuethe identical 49,999,999.66 on all 43 suppliers⚠️ never sum this column. 43 × £50m would report £2.15bn for a £50m framework.
awarded_value on an unawarded notice0.0 with awarded_supplier null and awarded_at null0.0 here means no award has been made, not a contract worth nothing
awarded_supplier"TechnologyOne UK Ltd"only the first entry of awards[]. A multi-provider framework has many.
id vs notice_identifier3c4f68ee-869e-… vs a14fba33-802d-…two different UUIDs on one notice. The public URL and the notice action both use id.
cpv_codes["48422000"] with cpv_description "Software package suites"8-digit EU CPV classification, the reliable way to scope a sector
region"Any region" · or all twelve UK regions comma-joined into one stringfree text, not an array and not an enum — split on commas at your own risk
awards[].duns_number"739837958"the supplier's DUNS, which is what joins an award to a company database

search returns historical notices by default, newest first — a measured query returned notices from 2022 and 2024 with status Closed and Awarded. A full description and a populated deadline are not evidence a tender is open. Filter on status and check that deadline is in the future before you put anything in front of a bid team.

Is it current, and is any of it still open

Measured on 2026-08-28 with nine searches on the same corpus: with and without each filter, at limit 25 and at limit 500, plus six notices opened in full.

Against us: sorting does not get you the newest

One phrase, sorted newest, limit 25: every row was published between 2015 and early 2025. The same query at limit 500 contained notices from three weeks before we measured. The sort is applied locally to the slice that came back, so the newest notice was simply not in the first twenty-five. Use the date filters, which run upstream, and treat sort as ordering rather than selection.

This is mostly an archive of who won, not a list of what is open

Five hundred rows on one phrase broke down as 388 awarded, 106 closed and 6 withdrawn — not one open. Narrowing the same corpus to open notices returned 13. That is the real shape of this source: its value is the award history, and the live pipeline inside it is a thin slice you have to ask for by name.

But it is genuinely current when you filter for it

One notice we opened had been published four minutes before the call. A thirty-day window on a common phrase returned a live set; a seven-day window returned 41 notices. The freshness is there — the default view just does not show it to you.

The money is in the row, and it is not the number you expected

Rows carry the published value range and, separately, the amount actually awarded, and they are frequently not the same number: one row published three hundred thousand pounds against thirty thousand awarded. Both are real and they answer different questions. Across 100 rows, 70 of the 78 awarded ones carried a published value — but the range is often half-filled, with value_low holding the figure and value_high sitting at zero. Read both ends before you treat it as a range, and read awarded_value for what was actually spent.

Against us: no paging at all, and the total ignores local filters

Every offset and page parameter is ignored, so limit is the depth ceiling — 500 rows took 17 seconds and the response set truncated true and paging_supported false. Separately, the reported total does not move when a locally-applied filter cuts the rows: a minimum-value filter returned 5 rows while the total still read 570. Count the array, not the total.

An empty result is an error with the filter named

Two filter combinations genuinely had no matches, and both returned a NOT_FOUND whose message names the query it sent and which filter was applied. That is a real zero, not a fetch failure, and you can tell them apart without guessing.

What people build with UK Tenders

The jobs this data is most often used for.

2

endpoints

1

credit per call

01

Public-sector sales teams call search with statuses: ["Open"] — an unfiltered search returns mostly Awarded and Closed notices — and add deadline_from to keep only tenders there is still time to answer.

02

Procurement analysts read awarded_supplier and awarded_value across award notices to size a market and see who is winning it.

03

SME-focused bid teams filter on suitable_for_sme and the value range to find contracts they can realistically win.

04

Competitive-intelligence tools use notice to read every award row on a framework, including supplier address and procedure type.

What UK Tenders data costs

The cheapest call here is 1 credit, so $15/mo (Pro) buys 10,000 of them — $1.50 per 1,000 credits. Credits roll over and never expire, and failed or blocked calls are not charged.

Full pricing →
$0.67–$1.50 / 1,000 credits
  • 1,000 free credits on signup, no card
  • One key, all 185 APIs, one credit pool
  • Failed and blocked calls are never charged
  • Credits roll over and never expire

Call it in two lines

Sign up, get 1,000 credits and one key that works on every engine. Then this is the whole protocol.

curl
curl -X POST https://api.reefapi.com/uk-tenders/v1/search \
  -H "x-api-key: $REEF_KEY" \
  -H "content-type: application/json" \
  -d '{"q":"web scraping","limit":10}'
python
import requests

r = requests.post(
    "https://api.reefapi.com/uk-tenders/v1/search",
    headers={"x-api-key": REEF_KEY},
    json={
  "q": "web scraping",
  "limit": 10
},
)
print(r.json()["data"])
FAQ

Have a question? We got answers.

The questions people actually ask before wiring up UK Tenders.

Get a free key →
Can I add up awards[].value to get what a framework was worth?

No, and this is the single most expensive mistake with this data. A measured multi-provider framework returned 43 award rows, and every one of them carried the identical value of 49,999,999.66 — that is the framework's ceiling repeated per supplier, not each supplier's slice. Contracts Finder does not publish the split. Summing that column reports £2.15bn for a £50m framework. Take awarded_value from the notice for the ceiling, count awards[] for the number of suppliers, and accept that per-supplier value is not available.

awarded_value came back as 0.0. Was the contract worth nothing?

No — it means no award has been made yet. A measured open notice returned awarded_value 0.0 alongside awarded_supplier null and awarded_at null, which is the honest reading: nothing has been awarded. Do not treat 0.0 as a real amount. Check awarded_supplier or status before you use awarded_value at all, and note the mirror image after an award lands: value_low and value_high both go to 0.0 with value_currency null, so the pre-award estimate disappears once the real number exists.

Why does one notice have two UUIDs?

id and notice_identifier are different values on the same record — a measured notice returned id 3c4f68ee-869e-4c04-b644-a5ee66bba046 and notice_identifier a14fba33-802d-4003-9f99-1fdee242f097. id is the one that appears in the contractsfinder.service.gov.uk/Notice/ URL and the one the notice action takes. Store id as your key. notice_identifier belongs to the publishing system and is not interchangeable with it.

Why does a search for open tenders return closed ones?

Because search covers the whole published archive, not just live opportunities, and sorts newest first — a measured query returned notices from 2022 and 2024 with status Closed and Awarded, each with a full description and a deadline field that had long passed. Filter on status, and independently check that deadline is in the future. This is the reason a bid pipeline built naively on this endpoint fills up with dead work.

Is region a list I can filter on?

Not as returned. It is free text, and it varies wildly in shape: one measured notice returned "Any region", another returned all twelve UK regions concatenated into a single comma-separated string starting "North East,North West,Yorkshire and The Humber…". There is no array and no code. If you need geography, cpv_codes plus the buyer name gets you further, and the notice action adds postcode and coordinates that the search row leaves null.

What does the notice action add over a search row?

The award detail, which is where the analysis actually lives. A measured notice expanded into awards[], each entry carrying supplier, supplier_address as free text, value, awarded_at, contract_start and contract_end, procedure_type (measured "Open"), awarded_to_sme, awarded_to_vcso and duns_number. It also fills in postcode and coordinates. If you are mapping who wins public work, duns_number is the field that joins these rows to a company register without name matching.

How do I find contracts an SME can realistically win?

Filter on suitable_for_sme and pair it with a value range. A measured cyber-security search with that filter returned 1,466 total matches, including a £100k–£500k contract and a £10k–£50k one, both flagged suitable_for_sme true. total_matches is returned on every search so you can size a niche before paging it — the cheap way to answer whether a sector is worth building a bid function around. At award time, awards[].awarded_to_sme tells you whether SMEs actually won, which is a different and more useful question.

Which buyers does this cover?

Every UK public body that publishes to Contracts Finder — central government, the NHS, local councils and universities. Measured buyers in one session ranged from East Sussex County Council to Crescent Purchasing Limited, a purchasing consortium buying on behalf of its members. That last case matters: the buyer named on the notice is not always the organization that will use the contract, so a framework's buyer field understates its real reach.

What is the UK Tenders API?

UK Tenders API is a ReefAPI endpoint group for uk public-sector tender notices and awards with values, deadlines and buyers. It returns live JSON through POST requests under /uk-tenders/v1.

Is the UK Tenders API free to try?

Yes. ReefAPI starts with 1,000 free credits, no card required. UK Tenders calls use the same shared credit balance as every other ReefAPI engine.

Do I need an UK Tenders login or account?

No login to UK Tenders is needed for the API response. You call ReefAPI with your x-api-key header, and the playground can run live examples before you create a production key.

How fresh is the UK Tenders data?

The page example is captured from a live search call, and production requests fetch live data through ReefAPI rather than a static sample.

How many credits does the UK Tenders API use?

UK Tenders actions currently cost 1 credit per successful call. Failed or blocked calls are free, and all APIs draw from one credit pool.

Can I call UK Tenders from an AI assistant or MCP client?

Yes. Connect ReefAPI once through MCP and your assistant can call uk-tenders actions with the same key, credit pool and JSON envelope used by normal REST requests.

2 Government & Tenders APIs on the same key

One key, one credit pool, one response envelope. If you are pulling UK Tenders, you are one call away from the rest of the category — no second contract, no second integration.

Need something this API does not do?

Name the endpoint, the field, or a source we do not carry yet. We ship new APIs every week and you would be first to get the key. Real people read every message and reply the same day.

0/4000

No account needed · we reply from [email protected]

Try it on your own data before you pay anything

The call above is the real endpoint, not a recording. A free key gives you 1,000 credits, the other 184 APIs, and the same envelope everywhere.

Endpoints, parameters and credit costs on this page are read from the live catalog and cannot drift from what the API accepts. Field notes were captured on 2026-08-28.