UK Public Sector Tenders — Find a Tender API & Contracts Finder
Pricing
from $2.50 / 1,000 result items
UK Public Sector Tenders — Find a Tender API & Contracts Finder
UK procurement tenders and contract awards from BOTH official portals — Find a Tender (above threshold) and Contracts Finder (sub threshold) — in one deduplicated feed. Filter by CPV, value, keyword, region, deadline. Buyer email, phone, address. First 25 records per run FREE, then $0.003/result.
Pricing
from $2.50 / 1,000 result items
Rating
0.0
(0)
Developer
Fetch Smith
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
2
Monthly active users
2 days ago
Last modified
Categories
Share
UK Public Sector Tenders — Find a Tender & Contracts Finder (OCDS API)
Search UK public-sector procurement notices — live tender opportunities, contract awards and pipeline notices — from both official UK portals, and get them back as flat, ready-to-use JSON rows in one merged, deduplicated feed.
- Find a Tender (FTS) publishes above-threshold contracts. It is an authoritative but thin feed — roughly 7–8 tender-stage notices a day.
- Contracts Finder (CF) publishes the far larger sub-threshold flow — the sub-£139k central-government and sub-£214k wider-public-sector contracts that never reach Find a Tender. Typically ~18 tender-stage and 100+ award notices a day.
Searching only one portal means missing most of the UK market. This Actor queries both by default, normalizes them into a single row shape, dedupes them, and tags every row with a source field (fts / cf) so you always know which portal it came from.
Both portals publish open contracting data (OCDS) — deeply nested release packages where the buyer's email is four levels down inside a parties[] array, the CPV codes are scattered across tender.items[].additionalClassifications[], and the awarded value usually isn't on the award at all. The two portals also disagree in small ways (Contracts Finder has no lots and puts SME suitability and the contract period on the tender; Find a Tender puts them per lot). This Actor flattens and reconciles all of it into one row per notice.
Post-Brexit UK notices are not in EU TED, so this is additive coverage if you already track EU procurement (see our EU TED Tenders Scraper).
In short: a government contracts UK tenders API covering Find a Tender and Contracts Finder in one deduplicated feed.
What you get
One row per notice, including:
- Buyer contact details —
buyerName,buyerEmail,buyerPhone,buyerUrl, full postal address and UK NUTS region. This is what turns a notice into a lead. - The opportunity —
title,description,status,procurementMethod,mainProcurementCategory,legalBasis. - Money —
valueAmount/valueCurrencyfor tenders;awardValueAmount/awardValueCurrencyfor awards. - Timing —
deadlineDate(submission deadline),awardPeriodStart,contractStartDate,contractEndDate,contractDateSigned. - Classification —
cpvCode+cpvDescriptionplus the fullcpvCodeslist gathered from every lot and item. - Lots —
lotCount,lotTitles,suitableForSme,suitableForVcse(read per-lot on Find a Tender, tender-level on Contracts Finder). - Awards —
awardedSuppliers(who won),awardStatus,awardStatuses(set only when a multi-lot notice's lots don't all land the same way — see FAQ),contractCount. - Delivery —
deliveryRegions(NUTS codes) anddeliveryLocations(free text). source/sourceName— which portal the row came from (fts/cf).noticeUrl— the public notice page on the portal it came from.
Use cases
- Bid pipeline / lead generation. Filter to your CPV codes and
openOnly: trueand you get every live UK opportunity in your sector, with the buyer's email address attached. - Competitor intelligence. Set
stages: ["award"]to see who is winning contracts, for how much, from which buyers. - Market sizing. Pull a year of awards for a CPV family and total the contract values.
- Alerting. Run on a schedule with
updatedWithinDays: 1and push new matches into your CRM or Slack, or usewatchLabelbelow to get only what's new automatically.
Input
| Field | Type | Default | Notes |
|---|---|---|---|
sources | array | ["fts","cf"] | Which portals to search. cf = Contracts Finder (sub-threshold, high volume), fts = Find a Tender (above-threshold, thin). Both by default. |
stages | array | ["tender"] | planning, tender (opportunities), award (winners), contract (signed) and/or implementation (delivery-stage updates). |
updatedWithinDays | integer | 7 | Only notices published/updated in the last N days. Newest first. |
dateFrom / dateTo | string | — | Absolute date window, e.g. 2026-08-01 (or a full ISO datetime). Setting either one overrides updatedWithinDays. Both portals enforce this server-side, so nothing is fetched and discarded. A bare date means midnight, so dateTo: "2026-08-31" excludes the 31st — use 2026-09-01 to include it. |
cpvCodes | array | [] | e.g. 72000000. A code matches its whole CPV subtree and nothing outside it — 72000000 matches every 72xxxxxx code, 80000000 matches 80xxxxxx only. Matched against every CPV on the notice, not just the headline one. |
searchQuery | string | — | Every word must appear in title, description, buyer name, CPV description or lot titles (AND logic). Each word is matched from a word start, so it also matches longer words that begin with it (consult finds "consultancy") but never mid-word (it does not match "mitigation" or "site"). |
keywordsAny | array | [] | Alternative to searchQuery for OR logic: a notice matches if it contains any of these phrases (each can be multi-word) in the same fields. Use this to cover several unrelated opportunity types in one run, e.g. ["software", "IT support", "cyber"] — searchQuery can only express AND-of-words within a single phrase. Set both if you want "any of these phrases, AND also these words". |
buyerName | string | — | Case-insensitive substring match on the buying authority's name only, e.g. NHS, Ministry of Defence. Narrower than searchQuery — use it to get a buyer's own notices rather than every notice that mentions them. |
regions | array | [] | OR-matched filter on delivery region/location text, e.g. ["London", "Scotland", "North West"]. Matches against the notice's own deliveryRegions/deliveryLocations. Notices with no delivery-region data are excluded when this is set. |
minValueGbp / maxValueGbp | integer | — | Contract-value bounds. Notices with no published value are excluded when either is set. |
openOnly | boolean | false | Only notices whose submission deadline is still in the future. |
maxResults | integer | 100 | Hard stop. |
maxPagesScanned | integer | 50 | Safety cap on API pages read while looking for matches. Raise it for narrow filters over long date ranges. |
includeRawOcds | boolean | false | Attach the complete, unmodified OCDS 1.1 release JSON as a rawOcds field on every row, alongside the normalized fields — for pipelines that want the full nested government data (all parties, all documents, amendment history), not just the flattened columns. |
watchLabel | string | — | Turn this run into an alert: see "Watch mode" below. |
webhookUrl | string | — | Optional. POST a small JSON completion summary (notices pushed, releases scanned/filtered, pages, dataset ID, watch new count, plus the full RUN_SUMMARY completeness object) here when the run finishes — see FAQ. |
Example
{"stages": ["tender"],"updatedWithinDays": 30,"cpvCodes": ["72000000"],"openOnly": true,"maxResults": 200}
Sample output (one row, trimmed)
{"ocid": "ocds-h6vhtk-06f762","noticeId": "086108-2026","noticeUrl": "https://www.find-tender.service.gov.uk/Notice/086108-2026","stage": ["tender"],"title": "Maryhill Housing Association - Heating Maintenance 2026 - 2031","status": "active","procurementMethod": "open","mainProcurementCategory": "services","cpvCode": "50720000","cpvDescription": "Repair and maintenance services of central heating","cpvCodes": ["50720000", "44621200", "44620000", "44621000", "42160000", "44621100", "44621110", "44621112", "42511110", "42533000"],"valueAmount": 710000,"valueCurrency": "GBP","deadlineDate": "2026-10-12T17:00:00+01:00","buyerName": "Maryhill Housing Association","buyerEmail": "enquiries@maryhill.org.uk","buyerPhone": "+44 1419462466","buyerPostalCode": "G20 8RG","deliveryRegions": ["UKM82"],"deliveryLocations": ["Maryhill & Ruchill areas of Glasgow"]}
Only what's new since last run (watchLabel)
A bid-monitoring job is a subscription, not a search: you want the notices published since you last looked, not the same hundreds of rows re-delivered (and re-charged) every morning.
Set watchLabel to a name for the query — nhs-cleaning, say — and this Actor keeps track of which notices it has already given you under that name:
- The first run on a new label is a free baseline. It scans both selected portals for everything currently matching your filters, records it, returns zero results and charges nothing.
- Every run after that returns only the new notices. Already-delivered rows are dropped before they are pushed or billed, so you never pay for the same notice twice — across either portal.
- A notice counts as delivered only once it has actually been charged. Anything cut off by
maxResultsor a charge limit stays "new" for the next run rather than vanishing. - Changing a filter starts a fresh baseline — a different filter is a different question, so you don't get a dump of everything the old, narrower query happened to exclude.
- The rolling
updatedWithinDayswindow is deliberately not part of that identity. It moves every day; if it counted, a daily schedule would re-seed forever and never deliver anything.dateFrom/dateTo, when you set them explicitly, do count. - Do not put a fixed
dateToon a watched query. It is an upper bound on the one date that is always "now" for a brand-new notice, so once the calendar passes it the watch can never report anything again — see the FAQ entry below. Since v0.1.43 the run warns about it and setsRUN_SUMMARY.watchDeadDateBoundinstead of going quietly dead.
The baseline lives in a key-value store named fetchsmith-uk-tender-watch on your own account, so it survives between runs and you can inspect or reset it yourself. Point an Apify schedule at the Actor and you have a UK procurement alert covering both portals.
Baseline size cap. A watch record holds at most 60,000 notice ids. Past that, the oldest ids fall off first — and a notice whose id has fallen off is no longer recognised as already-delivered, so a later run hands it over and charges for it again. As of v0.1.30 this is never silent: the run logs a warning, sets a status message in the Apify Console, records truncatedLastRun/truncatedTotal in the watch record itself, and reports baselineTruncated/baselineTruncatedTotal in RUN_SUMMARY and on the webhookUrl payload. All of them stay at 0 unless something was actually dropped. If you see a non-zero count, narrow the watch — a cpvCodes value, a buyerName, a shorter updatedWithinDays, or one portal in sources — so the result set stays under the cap. Note that the separate seed-cap bound (20,000) is what stops the first baseline walk; this cap is about the stored record growing over many runs.
Pricing
The first 25 matching records of every run are free. After that, $0.003 per result on the free plan, dropping to $0.0025 on Gold and above (Bronze $0.0028, Silver $0.0026), no start fee. You are charged only for rows that actually land in your dataset — notices filtered out by cpvCodes, searchQuery, the value bounds or openOnly are never charged, even though the Actor had to read them from the API to decide.
Dual-portal competitors, all prices pulled live and verified 2026-10-03. This is a fragmented, fast-moving niche — 110 Store listings mention UK tenders (a 15-term sweep, re-measured 2026-10-09; the count has run 88 → 93 → 99 → 102 → 104 → 108 → 111 → 110 over the past week — normal churn in a flooded niche, not a wave of new entrants) — so here are the live numbers instead of a blanket cheapest-anywhere claim. Three dual-portal Actors charge less per row than we do at every plan tier, and because we give the first 25 rows away free, each one only actually becomes cheaper above a specific run size: accountable_eel/uk-tender-alerts (never named before) is a monitoring/alerts product over both Find a Tender and Contracts Finder, billing new-since-last-run notices at $0.003/notice on the free plan tapering to $0.0015 on Gold and above (Bronze $0.00255, Silver $0.00195) plus a $0.00005 start fee — it ties us on the free plan (our 25-row allowance keeps us ahead there) but crosses over at about 280 rows on Bronze, ~100 rows on Silver and ~63 rows on Gold and above. deriverge/uk-tenders-scraper covers both portals at $0.001/notice (free plan) tapering to $0.0005 (Gold and above) with no start fee — so it is dearer than us on any run of 25 rows or fewer (we charge $0 there) and crosses over at about 38 rows on the free plan (~32 rows on Gold and above); on a 100-row run it costs about $0.10 against our $0.225 on the free plan ($0.05 vs $0.1875 on Diamond). It dropped to that price on 2026-09-24; before then it was $0.004→$0.002 and we were cheaper at every volume. fetchwerk/uk-tenders-scraper also covers both portals, at a flat $0.002/record plus a $0.00005 start fee with no plan taper (plus an optional $0.05 one-time charge if you turn its digest mode on), which crosses over at about 76 rows on the free plan and about 126 rows on Gold and above. publicrecords/uk-find-a-tender-scraper is dual-portal at $0.003/notice (free plan) tapering to $0.0021 (Gold and above) plus a $0.00005 start fee: it never beats us on the free plan, where it matches our rate without our 25-row allowance, but it does cross over above roughly 157 rows on Gold and above (~325 on Silver, ~700 on Bronze). Three more are effectively at parity: rein8/public-tenders-uk-eu and celestjux/celestjux-uk-tenders both charge a flat $0.003/record plus a $0.00005 start fee with no plan taper, and knotty_mistveil/uk-public-tenders a flat $0.003/record with no start fee at all — so our first-25-free allowance and our $0.0025 Gold-and-above rate make us a little cheaper on a run of any size against all three, but treat that as a tie, not a moat. Everything else in the dual-portal group is more expensive than we are: ciel_labs/uk-government-tenders-contracts-finder (the niche leader) also gives the first 25 records free but then charges a flat $0.008/record, 2.7–3.2x ours; neverempty/uk-tenders-scraper — whose listing, like ours, explicitly claims cross-portal duplicate merging — charges $0.01 (free) tapering to $0.0073 (Silver and above); publicdata/uk-contracts-finder-find-a-tender charges $0.005/notice (free) tapering to $0.003 (Diamond) plus a second, separately tiered Actor-start event on top; nefes-tools/uk-tenders $0.005/notice plus a $0.001 start fee; conserving_celerytop/eu-uk-public-tenders $0.004/notice tapering to $0.0028 plus a $0.00005 start fee; devon_gtme/uk-tenders-monitor $0.01/item plus a $0.005 start fee; civicrows/uk-unified-tender-feed $0.015/item; nexgensignal/uk-contract-opportunity-records $0.05/record tapering to $0.0335. Also newly disclosed, single-portal Find-a-Tender-only listings (not dual-portal, so grouped here for completeness) all dearer at every volume: nexgenwatch/uk-fts-tender-award-watch $0.004 tapering to $0.00268/tender notice plus a separate award-notice event, a watch-mode event and a $0.02 start fee; civicrows/uk-find-a-tender-notices (an FTS-only sibling of the civicrows listing above) $0.008/item; fortuitous_pirate/uk-find-a-tender-scraper (markets itself "$10/1k") $0.01/record plus a $0.05 fee; ukopendata/uk-public-tenders-find-a-tender $0.005/notice, no start fee; kaz_kakyo/uk-tender-notices $0.005/notice plus a $0.00005 start fee; ausgovdata/uk-find-a-tender $0.005/result plus a $0.00005 start fee.
Single-portal alternatives, verified 2026-10-03. These can undercut us in raw per-row price — several of them by a lot — if you are willing to run two Actors and do the cross-portal merge and dedup yourself. The cheapest per delivered row listing we have found is here: primebuyer/uk-tenders-mcp charges $0.00003/item with no start fee, about 100x under our free-plan rate, but reads Find a Tender only. We no longer call it the cheapest listing in the niche outright: two query-priced listings — dogmatic_eyepiece/uk-government-contract-intelligence and marielise.dev/procurement-intelligence-copilot — each carry a $0.00001 per-dataset-item rate, 3x under primebuyer, but neither sells rows at that rate. You reach their rows through per-query events instead ($0.005/opportunity search up to $0.02/recompete analysis on the former; $0.004 per opportunity returned, $0.015 per AI-reviewed one on the latter), so their cost per useful row lands above ours, not below it — a raw per-item number alone does not decide who is cheaper when the rows sit behind a query fee. On the Contracts Finder side automation_craft/uk-contracts-finder-scraper charges $0.0003/notice tapering to $0.00024 plus a $0.001 start fee (and the same again per notice-detail fetch); its FTS-side sibling automation_craft/uk-find-a-tender-scraper is $0.0005/notice tapering to $0.0004 plus a $0.002 start fee plus $0.00001 for every notice it scans, including ones its own filters then throw away — a row shape we deliberately do not charge for. Also cheaper per row, Contracts Finder only: springlike_meadowland/uk-public-tender-scraper at $0.001/release plus a $0.00005 start fee; jungle_synthesizer/contractsfinder-uk-government-contract-notices-scraper at $0.001 tapering to $0.0008/record plus a $0.0001 start fee; steadydata/uk-contracts-finder at $0.002 tapering to $0.0013/notice with no start fee; scrapersdelight/contracts-finder-awards-scraper at $0.002/notice, award notices only. publicmoney/contracts-finder-scraper, with a companion publicmoney/find-a-tender-scraper for the FTS side, tapers from $0.002 down to $0.0007/notice per portal. adobeflex/contracts-finder-uk-lite and adobeflex/find-a-tender-uk-lite each cover one portal at $0.002/notice row plus $0.001 per search plus a $0.00005 start fee. fascinating_lentil/global-government-contracts-aggregator is a flat $0.002/record but covers Contracts Finder only — no Find a Tender coverage at all, so it structurally cannot see the ~7–8 above-threshold notices a day that exist only on that portal. logiover/global-public-tenders-scraper (the largest multi-country listing that turns up on these searches) is $0.002/item plus a $0.002 start fee across many countries rather than a UK-focused feed. Three more multi-country, Contracts-Finder-only listings newly disclosed, all dearer than fascinating_lentil at any realistic volume: jungle_synthesizer/eu-national-procurement-portals-scraper reads TED plus 6 national portals including UK Contracts Finder at $0.001/record behind a $0.10 start fee; parseforge/us-gov-contract-watch-scraper bundles SAM.gov, CanadaBuys and UK Contracts Finder at a tiered $0.0075 down to $0.006675/result; georgy.malanichev/govtender-scraper bundles SAM.gov, TED and Contracts Finder at a flat $0.003/record behind a steep $0.50 one-time start fee. neuton/uk-find-tender-notices-scraper matches our free-plan rate at a flat $0.003/item, Find a Tender only, and neuton/uk-contracts-finder-notices-scraper does the same for the other portal. The rest of the single-portal group costs more per row than our dual-portal feed: nocodeventure/uk-government-contracts (the niche's second-largest listing, Contracts Finder only) charges $0.03/item (free) tapering to $0.01 (Gold and above) plus a $0.05 Actor-start fee and separate proxy charges; parseforge/uk-contracts-finder-scraper $0.0467 tapering to $0.03503/item; parseforge/ukcontracts-tenders-scraper $0.0085 tapering to $0.00769/item; parseforge/uk-gov-tenders-scraper $0.021 tapering to $0.019/item; crawlerbros/uk-contracts-finder-scraper $0.005 tapering to $0.003/item plus a $0.005 start fee; nexgendata/uk-find-a-tender $0.1/item. benthepythondev/uk-contracts-finder-scraper is a near-exact tie on headline rate, $0.003 tapering to $0.0021/notice plus a tiered start fee, Contracts Finder only.
Cheaper listings in the niche's long tail, newly disclosed 2026-10-03. The sweeps above rank by Store users, so they surface the niche's biggest listings first; a deliberate re-price of all 43 listings this niche's 15-term sweep returns that we had never named — every one of them at 1–2 users — turned up six more that charge less per delivered row than our $0.003 (free plan) → $0.0025 (Gold and above), none of them a dual-portal feed. On the Find a Tender side, humble-echidna/eu-ted-tenders pairs EU TED with UK Find a Tender at $0.002/notice tapering to $0.0014 (Gold and above) plus a $0.00005 start fee — cheaper than us at every tier, though our first 25 rows are free and it has no Contracts Finder coverage. Contracts Finder only: vanheelsing/public-procurement-monitor at a flat $0.002/row plus a $0.00005 start fee. Multi-country feeds that include the UK among several others: ovular_cappuccino/global-tender-monitor at a flat $0.0019/tender record with no start fee; ikoles/eu-uk-procurement-buyer-award-signals at a flat $0.002/procurement signal plus a $0.00005 start fee; chorelet/government-tenders-scraper (EU TED + UK + SAM.gov) at $0.001 tapering to $0.0007/tender plus $0.002→$0.0014 again for each notice-detail fetch and a $0.00005 start fee; and deriverge/public-tenders-scraper at $0.001 tapering to $0.0005/notice with no start fee — a multi-country sibling of the already-named deriverge/uk-tenders-scraper, priced identically. The other 37 are at parity or dearer; the most head-on of them is pohjastudio/uk-public-tenders-find-a-tender-scraper, Find a Tender only, at $0.005/notice behind a $0.02 Actor-start fee.
21 more never-named rivals, disclosed 2026-10-04 — none undercuts us. A fresh full-list diff of the 93-listing sweep (vs. 88 the day before) turned up 21 listings this README had never named, all still at 1–2 users. None beats our $0.003 (free plan) → $0.0025 (Gold and above) tiered rate, but two are close enough to be worth naming individually rather than lumping with the dearer tail: jpopendata/uk-public-tenders (dual-portal like us) charges a flat $0.003/record — ties our free-plan per-row rate exactly — but behind a $0.05 Actor-start fee and with no plan taper, so our 25-free-row allowance and our $0.0025 Gold-and-above rate keep us cheaper at every run size; frenchdatalab/uk-public-tenders (also dual-portal) is a flat $0.004/record plus a $0.00005 start fee, dearer than us at every tier. The rest are single-portal, Contracts-Finder-only, or a narrower non-substitute scope, all confirmed dearer: civicrows/uk-contracts-finder-notices $0.008/record; signalrows/tenders-uk-contracts-finder $0.004/record; straightforward_hydra/uk-contracts-finder-intelligence $0.005/notice; ukopendata/uk-contracts-finder-notices (a Contracts-Finder-side sibling of the already-named ukopendata/uk-public-tenders-find-a-tender) $0.005/notice; devilscrapes/uk-contracts-finder-tenders-scraper $0.004/row plus a $0.20 start fee, the dearest start fee found in this niche; minimal_ricegrass/uk-tender-monitor $0.04/item; scrapers_lat/uk-contracts-finder-scraper $0.012 tapering to $0.009231/result; parseforge/contractsfinder-uk-tenders-scraper (another sibling of the already-named parseforge family) $0.021 tapering to $0.019/result plus a tiered start fee. Narrower-scope, not full substitutes: foxlabs/uk-contracts-finder-supplier-data (award/supplier records only) $0.004/award; dataio/uk-public-contract-awards (awards only) $0.006 tapering to $0.004/award; usta/procurement-amendment-deadline-deltas (deadline/cancellation changes only) $0.02/row; zinin/uk-public-tender-lead-alert (a filtered lead-alert product) $0.01 tapering to $0.008/result-found plus a tiered start fee; exceptional_nugget/ocds-tender-opportunity-monitor (a source-change monitor, not a row export) $0.03/source-checked. Seven more are narrow watchLabel-only sub-products from the same publisher as the already-named nexgenwatch/uk-fts-tender-award-watch, each billing a $0.02 start fee plus a $0.10 (free) → $0.067 (Gold and above) per-source-check event: nexgenwatch/uk-contracts-finder-new-opportunity-watch adds a $0.05→$0.0335 opportunity-delta event on top. Verified 2026-10-05, correcting an ambiguous handle list: this paragraph used to name the other six only as bare -suffix abbreviations after the one above, which both defeated our own niche-unnamed tool's exact-handle matching and, because the abbreviated root silently dropped the new- that the first handle actually has, pointed a reader at the wrong Actor for five of the six. They are, in full and separately: nexgenwatch/uk-contracts-finder-contract-award-watch ($0.005→$0.0034 award-delta), nexgenwatch/uk-contracts-finder-deadline-change-watch ($0.15→$0.1005 opportunity-delta), nexgenwatch/uk-contracts-finder-incumbent-win-watch ($0.3→$0.201 award-delta), and nexgenwatch/uk-contracts-finder-opportunity-watch and nexgenwatch/uk-contracts-finder-award-watch (source-check only, their delta event exists in the schema but is not yet priced). None of the seven sells rows at a per-row rate at all — the billable unit is a periodic check or a change event, not a result — so none is a row-for-row substitute regardless of price.
6 more never-named rivals, verified live 2026-10-05 (niche re-swept 99 matched, up from 93). zhucl1006/uk-contracts-finder-awards (Contracts Finder awards only) undercuts us: alongside a near-free $0.00001 default dataset-item event it bills its real product, award-record, at a flat $0.002/record plus a $0.005 start fee and a separate $0.01 summary-row event — cheaper than our $0.0025 Gold-and-above rate on awards at any realistic volume, though it is awards-only, not the full Find a Tender + Contracts Finder feed. The other five are dearer or a different product shape, not substitutes: nightwave-owner/uk-find-a-tender (Find a Tender only) $0.004/tender flat; 1mp/uk-public-tenders (dual-portal) $0.008/result flat; datagrit/uk-contract-expiry-radar (Contracts Finder only) is a narrower "recompete leads" product — contracts nearing expiry with the incumbent supplier — tapering $0.00665→$0.0035/expiring-contract plus a $0.01 start fee; agent_muse/tender-opportunity-monitor (EU TED + UK Find a Tender) $0.005/result plus a $0.02 start fee; khadinakbar/scrape-public-tenders (EU TED + UK Contracts Finder + Find a Tender) $0.005/tender-found, no plan taper. Two more are differently-shaped products we checked and ruled out as non-substitutes rather than dismissing on title alone: dami_studio/rfp-tender-fit-monitor bills $0.003–$0.25 per AI fit-scoring/digest event across TED+FTS+SAM.gov, not a row export; aicatraz/gov-rfp-aggregator-mcp is an MCP tool billing $0.003/tool-call and $2.50/export across SAM.gov, UK Contracts Finder and other portals.
19 more never-named rivals, live-priced 2026-10-06 (niche re-swept 102 matched, up from 99; the >=3-user cut came back empty — max 2 users — so the whole unnamed tail was priced). Three are genuine undercutters. jtpalms/gov-tenders-monitor (Find a Tender only — also bundles CanadaBuys, EU TED and SAM.gov) bills $0.001/notice tapering to $0.0008 (Gold and above) plus a $0.00005 start fee, cheaper than us above about 38 rows on the free plan (~37 rows on Gold and above). thriftykiwi/public-tenders-aggregator (Contracts Finder only — also bundles EU TED and AusTender) is a flat $0.001/item with no start fee, cheaper above about 38 rows on the free plan (~42 rows on Gold and above). Both read one of our two portals, not both. optimistprime/uk-eu-public-tenders is a genuine three-source substitute — Find a Tender, Contracts Finder and TED in one feed — at $0.003/tender (free plan) tapering to $0.002 (Gold and above) plus a flat $0.002 Actor-start fee: it ties our free-plan per-row rate, but our 25-free-row allowance keeps us cheaper there at every volume; on Gold and above it crosses over above about 129 rows (~223 rows on Silver, ~720 rows on Bronze). One more real dual-portal (FTS+CF) listing, dearer at every tier: compass_lab/uk-tenders-scraper is a flat $0.004/item, no start fee, 1.6x our Gold-and-above rate. axiomworks/uk-eu-public-tenders-scraper combines Contracts Finder and TED (not Find a Tender) at $0.0068 tapering to $0.00476/result — dearer than us at every tier. The remaining 14 are all dearer still or a different product shape, each checked against its own live description rather than its title: apeye/government-tenders-api (SAM.gov+TED+FTS) $0.008/item plus a $0.01 start fee; fetchfinch/eu-uk-tender-match-monitor (TED+FTS) $0.005/item; nefes-tools/eu-tenders (TED+FTS) $0.005/notice plus a $0.001 start fee; meridianlabs/eu-uk-tenders-scraper (TED+FTS) a flat $0.003/notice with no taper and no start fee — ties our free-plan rate but has no 25-row allowance, so we are cheaper at every volume; gazidev/global-tender-alerts (TED+FTS+CF+CanadaBuys+SAM.gov) $0.005/tender-result plus a $0.0001 start fee; everyotherfriday/public-tenders (TED+FTS/CF+CanadaBuys+AusTender) $0.004/notice ($0.006 with a documents list attached); practicalmodules/eu-uk-tenders (TED+FTS+CF+SAM.gov) $0.005/tender-notice; nexgenwatch/tender-pipeline-report (FTS+TED) is a $15→$10.05 per-report product behind a $0.05 start fee, not a row export — not a substitute; mikee368/eu-tender-monitor reads TED, TenderNed (NL), BOAMP (FR) and Doffin (NO) — no UK portal at all despite matching this niche's sweep terms, flat $0.003/item; datalantern/government-tenders (CF+FTS+AusTender+CanadaBuys) $0.003/notice plus a $0.005 start fee; ambolt/tender-feed (TED+FTS+SAM.gov) $0.005/notice; dobus/eu-uk-public-tender-intelligence-api reads TED only — no UK portal at all despite the name, $0.003/item plus a $0.01 start fee; scrapesage/global-tenders-scraper (TED+FTS+World Bank) $0.008→$0.002 tapering (its separate lead event is $0.012→$0.003); scrapemint/government-tender-finder (TED+CF+World Bank) a flat $0.01/row. Re-verified live 2026-10-06 (cycle 1334): all 19 handles in this paragraph re-fetched directly from the platform — user counts and headline per-row/per-event prices for every one matched this paragraph exactly, zero drift.
1 more never-named rival, verified live 2026-10-07 (niche re-swept 104 matched, up from 102; the >=3-user cohort was empty again — max 2 users — so the whole 5-listing unnamed tail was priced). pontio/uk-tender-notices (Find a Tender only) bills a tiered $0.002/notice (free plan) tapering to $0.0014 (Gold and above) (Bronze $0.0018, Silver $0.0016), no start fee — undercuts us at every tier, though it reads only one of our two portals. The other four are dearer or a different product shape: oldjard/uk-eu-public-tenders (Find a Tender + Contracts Finder + TED, a three-source superset) is a flat $0.003/notice plus a $0.00005 start fee — ties our free-plan per-row rate exactly, but our 25-row allowance and lower paid tiers keep us cheaper at every real run size; avorelis/uk-public-tenders (Contracts Finder only) is a flat $0.004/record, dearer than us at every tier; xtracto/gov-tenders-ocds (Contracts Finder + EU + Ukraine) tapers $0.005 down to $0.003/record — dearer than us at every corresponding tier, and even its cheapest (Gold-and-above) rate only matches what we charge on our free plan, not our real $0.0025 Gold-and-above rate; zhucl1006/government-contract-winners-leads is a differently-shaped B2B lead-gen product (company-level leads off contract award winners, not a notice export) at a flat $0.015/lead, not a substitute.
2 more never-named rivals, verified live 2026-10-08 (niche re-swept 108 matched, up from 104; the >=3-user cohort was empty again — max 2 users — so the whole 5-listing unnamed tail was priced). cleanpull/public-tenders-tracker (a five-source superset: Find a Tender, Contracts Finder, EU TED, AusTender and SAM.gov in one schema) bills a tiered $0.002/record (free plan) tapering to $0.0016 (Gold and above) (Bronze $0.002, Silver $0.0018), no start fee — undercuts us at every tier despite covering three more portals than we do. arched_friend/uk-tender-monitor (Contracts Finder only) is a flat $0.002/notice, no taper, no start fee — also undercuts us at every tier, though single-portal. The other three: folt/eu-tenders-monitor (TED + Find a Tender + Contracts Finder) is a flat $0.003/record with no taper — ties our free-plan rate exactly, but our 25-free-row allowance and our lower paid tiers keep us cheaper at every real run size; lindenwerk/uk-tender-matcher (Find a Tender only) is a flat $0.008/match, dearer than us at every tier; arched_friend/federal-contract-finder matched this niche's sweep terms on the word "contract" but reads US federal contracts and grants, no UK portal at all — not a substitute, a false match.
2 more never-named rivals, verified live 2026-10-08 (niche re-swept 111 matched, up from 108; the >=3-user cohort was empty again — max 2 users — so the whole 2-listing unnamed tail was priced). Both are brand-new dual-portal substitutes, created the same day as this sweep. friedl/uk-public-tenders (Find a Tender + Contracts Finder) bills a tiered $0.002/result (free plan) tapering to $0.0014 (Gold and above) (Bronze $0.0018, Silver $0.0016), no start fee — undercuts us at every tier with no crossover. ennobling_spray/uk-public-tenders (the same two portals) is a flat $0.002/notice, no taper, no start fee — also undercuts us outright at every tier.
What we do not claim (verified 2026-10-08). We are not the cheapest Actor in this niche, and we are not close to it: primebuyer/uk-tenders-mcp is roughly 100x cheaper per row on the Find a Tender side, automation_craft's two single-portal listings are 6–10x cheaper per row, zhucl1006/uk-contracts-finder-awards is cheaper per row on the Contracts Finder side, deriverge/uk-tenders-scraper and accountable_eel/uk-tender-alerts are each cheaper than us on both portals at once above a certain run size (~32 and ~63 rows respectively on Gold and above), the publicmoney/contracts-finder-scraper pair is cheaper per row if you run two Actors and merge them yourself, six more single-portal or multi-country listings in the niche's 1–2-user long tail (humble-echidna/eu-ted-tenders, vanheelsing/public-procurement-monitor, ovular_cappuccino/global-tender-monitor, ikoles/eu-uk-procurement-buyer-award-signals, chorelet/government-tenders-scraper, deriverge/public-tenders-scraper) each beat our per-row rate too, jtpalms/gov-tenders-monitor and thriftykiwi/public-tenders-aggregator (one portal each) cross over above ~38-42 rows, optimistprime/uk-eu-public-tenders (a genuine three-source FTS+CF+TED substitute) crosses over above ~129 rows on Gold and above, pontio/uk-tender-notices (Find a Tender only), cleanpull/public-tenders-tracker (five-source superset), arched_friend/uk-tender-monitor (Contracts Finder only) and the brand-new friedl/uk-public-tenders and ennobling_spray/uk-public-tenders (both dual-portal) each undercut us outright at every tier with no crossover at all. What you get here instead is what this listing documents and we re-test every few weeks: both portals reconciled into one row shape with duplicates merged and a source tag per row, buyer email/phone/postal address pulled out of the OCDS parties[] nesting, all five OCDS stages (including contract and implementation, which Find a Tender silently drops server-side and this Actor filters client-side instead), free filtering — rows removed by cpvCodes, searchQuery, the value bounds or openOnly are never charged — and a watchLabel baseline whose 60,000-id cap is never silent when it truncates. We have not run the other Actors listed above, so the prices here are from their live Store pricing records and the scope claims are from their own listings, not from our own testing of their output.
2026-10-07 cleanup: dropped every sub-20-user rival count from this README per the standing rule (an exact count is only published at 20+ users, since totalUsers churns in both directions and a one-user tick under 20 is automatically STALE at the checker's 10% tolerance) — 102 bare counts removed across the sweeps above (plus the "largest at 16 users" line in the first dual-portal paragraph), every price claim and feature comparison left intact. No live re-check was needed for this edit: it only removes numbers, it doesn't restate any.
Full-niche rechecked 2026-10-09: for the first time, every listing in this niche is already named above. The 15-term sweep returned 110 matching listings (111 the day before — churn, not growth), and a full-list diff against the handles this README names found 0 unnamed rivals — 0 whose owner is never mentioned, 0 whose owner appears only in unrelated prose. Every prior recheck on this page (2026-10-04 through 2026-10-08) still had an unnamed tail to price; this one did not. Separately, every rival named above was re-priced against its live Apify price record the same day, at every plan rung rather than just the free-plan rate, and each one's secondary charge events were scanned too (a multi-mode rival can hide a cheap per-row leg behind a dearer headline event): no rival undercuts us at any tier without this README already disclosing it, and no secondary event turned out to be an undisclosed per-row rate. The undercutters named above remain the full set. Note the per-rival prices quoted in the paragraphs above carry their own verification dates — this recheck confirms the cheaper-than-us verdicts and the completeness of the rival list, and does not restate every quoted figure.
14 more never-named rivals, verified live-priced 2026-10-16 — the "0 unnamed" verdict above was a tooling artifact, not a real completeness check. The 2026-10-09 recheck ran on a 15-term sweep capped at limit=100 per term, a bug fixed fleet-wide in cycle 1780 after it was shown to serve only ~10% of a busy niche's matchable Store listings. Re-swept with the fixed limit=1000 call: 139 matching listings, not 110 — the gap was never new entrants, it was truncation. A full diff against every handle this README names found 30 unnamed listings, all still at 1-2 users (the >=3-user cohort this README's own rule prioritizes came back empty again), so the whole tail was live-priced. Three are false matches with no UK portal at all despite tripping the sweep's keyword terms: flamelit_constellation/usaspending-expiring-contracts-finder, transparent_meteorite/federal-grants-contracts and unhampered_dolly/us-federal-contracts-finder are all US SAM.gov/USAspending/Grants.gov products. Two are narrower, non-substitute shapes: felixgoldwin/uk-contract-renewal-monitor (Find a Tender only; tracks contract end-dates/renewals/amendments, not a general notice export) and locaihost/uk-procurement-act (Find a Tender only; late-payment compliance and Procurement Act notices, not a general notice export, and dearer regardless). Eleven are dearer than us at every tier we checked and are not repeated here. The other fourteen are genuine undercutters, none with a start fee unless noted: dual-portal (Find a Tender + Contracts Finder), undercutting us at every tier with no crossover — studded_lantana/uk-tenders and grit-77/uk-find-a-tender, both a flat $0.002/notice, no taper; broader-than-dual-portal and still undercutting at every tier — kadi_bence/gov-tenders-scraper (FTS+CF+AusTender) at a flat $0.0015/notice, and locaihost/gov-tenders (FTS+CF+TED+CanadaBuys+SAM.gov+AusTender, a six-source superset) tiered $0.003 (free) tapering to $0.00165 (Gold and above) — ties our free-plan rate, undercuts every paid tier. Single-portal, undercutting at every tier on that one portal: amygraphics/uk-gov-tenders-scraper (Find a Tender only, buyer contact emails included free, MCP-ready) at a flat $0.0008/notice, the cheapest single-portal rate found in this sweep; bogdiehard/uk-find-a-tender-scraper (Find a Tender only) tiered $0.0028 tapering to $0.00196; one_dollar_store/uk-contracts-finder-tenders and thriftykiwi/uk-contracts-finder-scraper (both Contracts Finder only, unrelated to the already-named thriftykiwi/public-tenders-aggregator) each a flat $0.001/notice; prixodik/uk-eu-procurement-opportunity-monitor (Contracts Finder + EU TED, no Find a Tender) a flat $0.001/notice; tinyrex/us-uk-tenders-scraper (Contracts Finder + SAM.gov, no Find a Tender) tiered $0.0025 tapering to $0.002. Tied on the free plan but cheaper on paid tiers only: rowcraft/uk-tenders-scraper (dual-portal) tiered $0.003 tapering to $0.002 (Gold and above); seralifatih/uk-public-tenders (dual-portal) tiered $0.003 tapering to $0.0021; plainrows/public-tenders-search (Find a Tender + Contracts Finder + EU TED, a three-source superset) tiered $0.003 tapering to $0.0015 plus a per-run start fee; thirdwatch/uk-contracts-finder-scraper (Contracts Finder only, positioned for lead-gen/enrichment) tiered $0.004 (dearer on free) tapering to $0.002 (Gold and above, cheaper). None of the fourteen were run or tested by us — scope and price come from each listing's own live Store record, same as every other entry on this page. The fleet-wide lesson (the limit=100→limit=1000 fix applies to every Actor's niche sweep, not just this one) is filed in notes/LEARNINGS.md.
FAQ
I got 0 notices and the run still says "Succeeded" — what happened?
A run that delivers nothing always says why in its status message, and you are charged nothing in every case. (1) The run ran out of time before finishing — the message says so explicitly and tells you not to read anything into the 0 (it is not "nothing matched", it is "didn't finish looking"); raise the timeout or narrow the query and re-run. (2) A watched query found nothing new — every matching notice had already been delivered on an earlier run of that watchLabel; this is the expected result most of the time and nothing to fix. (3) Nothing matched your filters at all — the message names whichever text filter you actually set (searchQuery, keywordsAny, or neither, since an unset text filter means the search term itself is what to change) and lists the other usual causes in order: a malformed cpvCodes value, openOnly excluding award notices, too short an updatedWithinDays, or sources excluding the portal your notices are actually on. RUN_SUMMARY carries the same facts as data (scanned, filteredOut, malformedCpvCodes). Note that the Apify Console truncates any run status message at 500 characters, so what you see there is a deliberately compact rendering of the checklist, written to fit whole — no cause is ever cut off mid-sentence. The run log always carries the full-length version, with the complete list of malformed cpvCodes values when there are any.
How do I know a run returned everything — and that "0 from Find a Tender" means that portal really had nothing?
Every run writes a machine-readable RUN_SUMMARY record to its key-value store (v0.1.27+). Fetch it with no webhook needed:
GET https://api.apify.com/v2/actor-runs/<runId>/key-value-store/records/RUN_SUMMARY
{"mode": "search","sources": {"fts": { "label": "Find a Tender", "pages": 1, "scanned": 2, "delivered": 2,"exhausted": false, "failed": true, "lastError": "HTTP 503: Service Unavailable" },"cf": { "label": "Contracts Finder", "pages": 2, "scanned": 4, "delivered": 4,"exhausted": true, "failed": false, "lastError": null }},"sourcesFailed": ["fts"],"delivered": 6, "filteredOut": 0, "pages": 3, "pageCap": 50, "undelivered": 0,"complete": false,"incompleteReason": "source-failed","incompleteDetail": "Find a Tender stopped answering after 1 page(s) (HTTP 503: ...)"}
The field that matters is sources.<portal>.exhausted: it is true only when that portal answered until its feed ran out. When it is false, that portal's delivered count is a lower bound, not a total — which is the whole point, because Find a Tender is a thin feed (~7–8 tender-stage notices a day), so a genuine 0 and a dead portal produce the same number. complete: false also sets the run's status message in the Apify Console, with incompleteReason one of source-failed, page-cap, max-results, charge-limit, seed-cap or no-sources. Reading both feeds to the end is not the same as delivering everything: a short date window often fits in one page per portal, so a run can exhaust both feeds and still be cut short by maxResults — when that happens undelivered is the exact number of matching notices that were fetched but not handed over (raise maxResults by that much to get them). A short run still succeeds — the notices it did return are real and already charged — so complete is the only reliable check. The same object is included as summary on the webhookUrl payload. On a watchLabel run the record also carries baselineSize, skippedSeen and baselineTruncated/baselineTruncatedTotal (see "Baseline size cap" above) — all null on a plain search run.
What is the difference between the two portals?
Find a Tender carries above-threshold UK public contracts (the post-Brexit replacement for the UK's TED publication), including Scotland, Wales and Northern Ireland. Contracts Finder carries the sub-threshold contracts below those limits — a much larger flow, and the one most SMEs actually bid on. They are separate systems with separate APIs; a contract normally appears on one or the other, not both. Rows are deduplicated on ocid and notice id regardless.
Does searchQuery/buyerName handle accented words correctly? Yes, as of v0.1.13 — both fields and the notice text they're matched against are Unicode-normalized before comparing, so an accented word matches regardless of which of Unicode's two equivalent representations (composed vs. decomposed) you typed it in.
Does selecting more than one stages value work on Find a Tender? Yes, as of v0.1.18 — Find a Tender's own API silently returns zero results for a comma-joined multi-stage value (it wants each stage as its own repeated query parameter), so any run with stages: ["tender", "award"] or similar came back empty from that portal only, with no error. Fixed; Contracts Finder was never affected. Also as of v0.1.18: watchLabel now tracks delivery per notice (noticeId), not per procurement (ocid) — neither portal ever edits a published notice in place, so a later award or amendment always arrives as a new notice sharing the original's ocid, and the old ocid-keyed baseline would have silently swallowed it as "already delivered".
Did amended tender notices on Contracts Finder ever go missing from a default run? Yes — fixed in v0.1.81, and this was a real data-loss bug, not just a documentation gap. OCDS tags an amended release tenderAmendment, not plain tender (same pattern for awardUpdate on the award side), and Contracts Finder's own server-side stages=tender filter already treats tenderAmendment as part of the tender stage — it returns both the original and the amendment for the same ocid. Measured live 2026-10-18 on a 6-week window: 368 Contracts Finder tender-stage releases, 61 of them (16.6%) tagged tenderAmendment, spanning 39 of 316 distinct procurements (12.3%). Before the fix, this Actor's own client-side stage re-check compared tags for exact equality, so every tenderAmendment release failed it and was dropped — and because the ocid "keep the newest copy" dedup ran before that check, it had already marked the ocid as seen, so the still-matching original tender release for the same procurement was then discarded too, as a false duplicate. Net effect: on default input (stages: ["tender"]), the entire procurement vanished — not degraded, not shown stale, just absent — for every one of those 39 procurements. Verified directly: re-running the unfixed code against the same window returned 0 of 4 sampled amended procurements; the fixed code returns all 4, each as its current (amended) version. The fix does two things: the stage check now matches on a stage-name prefix (tenderAmendment/tenderUpdate/tenderCancellation all count as tender, awardUpdate/awardCancellation as award, matching OCDS's own tagging convention and what Contracts Finder's server already does), and the ocid dedup bookkeeping only runs after a release has passed the filter, so a non-matching release can no longer block a later, actually-matching release of the same procurement. See the full write-up for the live before/after measurement.
Do contract and implementation stages work the same way on both portals? No. Contracts Finder's stages filter is a real server-side validator for its other 3 values (it 400s on a genuinely bad value naming it), but for contract/implementation it silently accepts the parameter and returns the same releases stages=award would — measured live: identical award/awardUpdate tag distribution either way, and an unfiltered year-wide 800-release sample never produced a single release actually tagged contract or implementation. Contracts Finder's OCDS feed simply never labels a release either way, so it can never contribute a matching row for those two values, no matter what you set stages to. Find a Tender's own filter only ever recognized 3 values (planning/tender/award) — unchanged, re-verified live cycle 1534 — but its failure mode for anything else changed: cycle 838 found it silently returned 0 rows with no error for contract/implementation; as of sometime before cycle 1534 it now returns an explicit 400 Bad Request ("'stages' must be one of 'planning', 'tender', 'award'") instead. Since v0.1.37, whenever stages includes contract or implementation, the Actor stops sending Find a Tender a stages filter at all (fetching every stage from that portal) and applies the stage filter itself client-side instead — so this Actor never actually sends FTS a value that would 400, and the vendor's error-mode change has no effect on you. So only Find a Tender can ever match contract/implementation — if sources excludes fts, a stages request for either value returns nothing from Contracts Finder, and the run logs a warning and reports it in RUN_SUMMARY.cfBlindStagesRequested (v0.1.38+) rather than looking like a filter that simply found nothing.
What's the difference between searchQuery and keywordsAny? searchQuery is AND logic: every word in it must appear somewhere in the notice. keywordsAny is OR logic: the notice matches if it contains at least one of the phrases in the list. So searchQuery: "IT support" requires both words to appear (anywhere, not necessarily adjacent), while keywordsAny: ["software", "IT support", "cyber"] matches a notice mentioning any one of those three phrases — useful when you want everything across several related-but-different opportunity types without running the Actor three times. Set both together to combine them (AND of the searchQuery words, AND at least one keywordsAny phrase).
Does a short searchQuery word like IT or AI match inside other words? No — fixed in v0.1.43. Both searchQuery words and keywordsAny phrases are anchored to a word start: they still match longer words that begin with the term (consult finds "consultancy", support finds "supporting", which is usually what you want), but no longer match in the middle of one. Before v0.1.43 the match was a plain substring, so the two-letter word in searchQuery: "IT support" matched mitigation, site, architectural and military — measured live on a 14-day window, that example returned "Supply of Specialist Military Clothing", "UXO Site Surveys" and "Architectural Services" among its top 10, none of which are IT notices, and you were charged per result for them. Short, common letter pairs (IT, AI, HR, EV) were worst hit. If you actually want a mid-word or suffix match, put the longer stem in instead — there is no way to ask for "ends with".
So is searchQuery: "IT support" a good way to find IT notices? Still no, and v0.1.43 does not make it one — it only removes the indefensible half of the problem. searchQuery has no concept of stopwords, and word-start matching is deliberately a prefix match, so the two-letter word it legitimately matches its, item and iterative — all extremely common in procurement prose. Re-measured live after the fix, that query still returns notices like "Provision of Waste Removal" (its description contains "supporting the University in meeting its current... objectives"). This is not a bug; it is what a 2-letter substring filter can do. To actually find IT opportunities, filter on cpvCodes: ["72000000"] (the whole IT-services CPV subtree) — a classification code is what the buyer themselves assigned, so it does not depend on prose at all. Use searchQuery for distinctive multi-letter terms (cyber, helpdesk, datacentre), not for short initialisms.
How exactly does a cpvCodes entry match?
A code matches its whole CPV subtree and nothing outside it. CPV is hierarchical — first 2 digits are the division, then group, class and category — and trailing zeros are padding, so 72000000 (IT services) matches every 72xxxxxx code, and 72267000 also matches its children 72267100 and 72267200. The division is the widest a filter can ever go: 80000000 returns education notices only, never 85xxxxxx health ones. Fixed in v0.1.25 — before that, a division code whose second digit is 0 (30000000, 50000000, 60000000, 70000000, 80000000, 90000000) collapsed to a single digit and matched ten divisions at once. Measured on live data: cpvCodes: ["80000000"] returned 9 of 10 rows with no education CPV at all (they were 85xxxxxx health and social work), and ["70000000"] returned 0 of 10 rows in division 70 — engineering design, interpretation, R&D and payroll services instead. Codes like 72000000, 45000000 and 85000000, whose second digit is non-zero, were always correct. Note the subtree rule is the Actor's own client-side filter, applied to every CPV on the notice (headline plus all lot and item codes), so a notice qualifies if any one of its codes falls in the subtree.
I set a cpvCodes value that isn't 8 digits — what happens?
CPV codes are always exactly 8 digits, and since matching is entirely client-side against the two portals' own feeds (unlike a real search-API filter, there's no upstream to ask "is this code valid"), a malformed value is dangerous in a way that isn't just "returns nothing": the same trailing-zero-stripping every real code goes through can turn a wrong-digit-count numeric typo into a real, much wider CPV prefix by accident — measured live, "7200000" (7 digits, one short of "72000000") silently collapsed to prefix "72" and matched (and would have been billed for) the same rows a deliberate whole-division "72000000" filter would, not zero rows. Fixed in v0.1.2: any cpvCodes value that isn't 8 digits is excluded from matching entirely (it can never narrow OR widen the filter) and logged as a warning; RUN_SUMMARY.malformedCpvCodes lists them for a pipeline to check without reading logs. If excluding the bad value(s) leaves no well-formed cpvCodes value at all, the run correctly returns 0 results rather than silently matching everything. A well-formed 8-digit code that just has no current live tenders is a separate, legitimate 0-result case this check cannot catch — see the CPV-vocabulary FAQ above for how the subtree match itself works.
Why does a 10-day Find-a-Tender-only query only return ~75 notices?
Because that is genuinely how many there are — Find a Tender carries roughly 7–8 new tender-stage notices a day, and none on weekends. This is exactly why the Actor searches Contracts Finder too by default. Widen updatedWithinDays (or use dateFrom/dateTo) for a bigger set, or leave sources at its default.
In what order do results come back?
Newest-first within each portal, but the two portals are interleaved row-by-row when both are selected — so even a small maxResults gets a mix of both instead of one portal filling the whole quota first. Set sources: ["cf"] or ["fts"] if you want rows from only one portal.
Why is awardValueAmount sometimes missing on award notices?
Because the buyer did not publish a value. Where a value exists it is usually attached to the signed contract rather than the award, so this Actor reads contracts[].value and totals it (contractCount tells you how many contracts were summed, and awardValueSource says whether the number came from the award or the contracts).
Why isn't there a tenderStartDate field?
It used to exist (tender.tenderPeriod.startDate in the raw OCDS feed) but was removed as of v0.1.22 — measured 0/808 filled across 9 independent live slices spanning 2025-01 through 2026-08, on both Find a Tender and Contracts Finder, tender-stage and award-stage alike. UK buyers publish a submission deadline (deadlineDate) but essentially never publish a tender-window start date on either portal. deadlineDate remains, unaffected.
Why isn't there an aboveThreshold field on awards?
Same reason as tenderStartDate above: it used to exist on this Actor's award rows (award.aboveThreshold in the raw OCDS feed) but was removed as of v0.1.13 — measured 0/650 filled across 7 independent live slices spanning 2025-01 through 2026-10, on both portals' award-stage releases. Neither portal's OCDS feed has ever populated this key in any sample checked, not even on Find a Tender, whose entire portal is above-threshold-only by definition. If you need an above/below-threshold split, use source instead: every fts row is above-threshold by construction (that is Find a Tender's whole remit — see "What you get" above), while cf rows are a mix, so filtering source: "fts" is the reliable proxy this feed can actually support.
Do you need a proxy or an API key? No. Both portals' OCDS APIs are free, key-free and open-licensed (Open Government Licence v3). The Actor self-throttles per portal to stay under each API's rate limit and backs off politely if it still hits one.
Is this legal? Yes — it reads two official UK government open-data APIs under the OGL v3 licence. The contact details published on a notice are organisational procurement contacts, published by the buyer for exactly this purpose.
Why did my first watchLabel run return nothing? By design — the first run on a new label + filter combination is a baseline: it records everything currently matching so the next run can tell you what's new, and charges nothing.
Why does my watch never report anything new any more? Check whether the watched query has a fixed dateTo. That bound is sent to Find a Tender as updatedTo and to Contracts Finder as publishedTo, and both portals stamp those dates at the moment of publication — they are never backdated. Verified live 2026-10-12 using each portal's own monotonically increasing release counter as a proxy for "when did this row enter the feed": Find a Tender's 457 releases dated 2026-09-01 occupy notice ids 82421–82877, an exactly contiguous block, while that day's live feed sat at 95883 — not one old-dated release carries a recent id; Contracts Finder's internal release sequence is the same shape (2025-06-02 → ~847,800, 2026-01-05 → ~877,300, 2026-06-01 → ~900,000, 2026-10-09 → ~917,300, strictly increasing with publishedDate). So once the real calendar date passes a fixed dateTo, no newly published notice can satisfy the query again and the watch is permanently blind to new notices — while still finishing successfully with a perfectly healthy-looking "nothing new". Since v0.1.43 the Actor warns twice when watchLabel and dateTo are set together and reports watchDeadDateBound: true in RUN_SUMMARY and on the webhookUrl payload. The fix is to drop dateTo: dateFrom alone is safe to keep (a lower bound never goes dead), and leaving both out just means "the last updatedWithinDays days", which moves forward with time. Keep dateTo only for a genuinely historical one-off export, with watchLabel unset.
Can I reset or inspect a watch baseline? Yes. It is a plain JSON record in the fetchsmith-uk-tender-watch key-value store on your own account, keyed by your label plus a fingerprint of your filters. Delete the record to start over, or read seenIds to see exactly what has been delivered.
How is webhookUrl different from Apify's own platform webhooks?
Apify's platform webhooks are configured separately per Task/Actor via the Console or the Webhooks API — useful if you already live in the Apify Console, but extra setup if you're calling this Actor's API directly and just want a completion ping. webhookUrl is a plain input field: set it on the run itself and it POSTs a JSON body (actorRunId, defaultDatasetId, finishedAt, pushed, scanned, filtered, pagesScanned, summary — the same object as the RUN_SUMMARY record — and, if watchLabel is set, watchSeeding/watchNewCount) once the run finishes and every row is already pushed and charged. Especially useful with watchLabel on a scheduled run: your endpoint gets told how many brand-new notices landed without polling the dataset. It's best-effort — a slow or failing webhook only logs a warning, it never fails the run, changes the result set, or affects billing.
Do I get charged when a buyer publishes the same tender five times? No. Both portals routinely re-publish an identical notice under a brand-new OCID and a brand-new release id, so no ID-based dedup can catch it — measured live over 374 tender-stage notices across a 30-day window, 2.9% of rows were byte-identical republications (one Islington notice appeared 5 times inside 11 seconds; one Dundee notice 5 times over 3 days, with no field differing except the ids and the timestamp). Since build 0.1.35 the run keeps the newest copy of each and never charges for the rest; RUN_SUMMARY.republishedSuppressed reports how many were dropped. Two things it deliberately does not merge: a notice carried by both portals (those two rows differ in 16 fields — buyer contact details, delivery regions, legal basis, lot counts — so they're complementary, not redundant), and same-title notices whose descriptions differ (East Sussex publishes one notice per school-transport route under a single generic title; those are separate contracts, and a title match is not a duplicate).
A multi-lot notice's awardedSuppliers names a winner, but awardStatus says "unsuccessful" — is that a bug? It was, fixed in build 0.1.83. A notice with several lots carries one award per lot, and the lots don't always land the same way — one lot can be cancelled while another is genuinely awarded. Before 0.1.75, awardStatus always read the first award in the list, so a notice whose first lot fell through could report awardStatus: "unsuccessful" even while awardedSuppliers (flattened from every award) already named the winner of a different, successful lot — a visible contradiction in the same row. Real example, Find a Tender notice 097117-2026 (Coventry City Council): lot 1 cancelled (awards[0].status: "unsuccessful"), lot 2 awarded to University Hospital Coventry and Warwickshire (awards[1].status: "active") — the old code reported awardStatus: "unsuccessful" on a row that already named that hospital as the winner. Fixed by preferring any "active" award for awardStatus (and the fields derived from the same award — contractStartDate/contractEndDate/mainProcurementCategory), so the status now answers "was anything on this notice awarded" rather than "what happened to whichever lot sorts first". A new field, awardStatuses, is populated (only when the lots disagree) with every distinct status seen, so you can tell a fully-successful notice from a partially-successful one without re-fetching the raw OCDS. Measured live over 1,500 award-stage notices (Find a Tender, Aug–Oct 2026 window): 9 (0.6%) had mixed award statuses across their lots — rare, but the old behaviour was silently wrong on all 9.
Source code
https://github.com/Fetchsmith/fetchsmith/tree/main/actors/uk-find-a-tender-scraper
Related guides
Engineering write-ups behind this Actor:
- Amended tender notices were vanishing from this Actor's default output — a 16.6%-of-notices client-side filter bug, live-measured and fixed.
- The UK publishes every public contract as OCDS JSON — and the money isn't where you'd look
- When a dateTo filter silently excludes its own last day — and which government-data APIs actually do this
- Eight ways an "only new since last run" watch mode silently stops working — how
watchLabelis built and the traps it has to avoid. - We nearly charged our own buyers twice for rows they'd already paid for — a capped watch-mode baseline can silently evict old-but-current ids on a high-volume run, re-delivering (and re-billing) rows already paid for. Reproduced on this Actor, closed with truncation tracking.
- Eight government JSON APIs that need no key — and the specific way each one lies to you — how this API's silent-failure shape compares across all eight free government JSON APIs we scrape.
More tools: fetchsmith.com/tools