Government Tenders Scraper - TED, SAM.gov, Procurement Data avatar

Government Tenders Scraper - TED, SAM.gov, Procurement Data

Pricing

$2.90 / 1,000 tenders

Go to Apify Store
Government Tenders Scraper - TED, SAM.gov, Procurement Data

Government Tenders Scraper - TED, SAM.gov, Procurement Data

19,795 notices in one week from the EU TED Tenders Electronic Daily API, where paged search stops at 15,000; 48 buyer countries in one measured window. UK and US SAM.gov notices with buyer email and phone. Government tenders API, SAM.gov contract opportunities search, public procurement database.

Pricing

$2.90 / 1,000 tenders

Rating

0.0

(0)

Developer

Snow Leo Data

Snow Leo Data

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

3 days ago

Last modified

Share

TED, SAM.gov & UK Government Tenders and Procurement Scraper

Public procurement notices from four official feeds in one normalised dataset: TED (the European Union's Tenders Electronic Daily), UK Find a Tender, UK Contracts Finder and US SAM.gov Contract Opportunities.

One run, one row shape, one set of filters. A buyer looking for construction tenders above EUR 1 million closing this month writes that filter once, not four times.

48 buyer countries were present in a single measured window: 47 of them in one 7-day slice of TED (2026-09-04 to 2026-09-11, 19 795 notices), plus the United States from SAM.gov. Reproduce it with python3 tools/measure_sources.py.


What this Actor collects

SourceCoversNotices measured
TED — Tenders Electronic DailyEU, EEA, candidate countries; 47 buyer countries seen in one week19 795 in 7 days
UK Find a TenderUK contracts above thresholdcursor feed, no published total
UK Contracts FinderUK contracts below thresholdcursor feed, no published total
US SAM.gov Contract OpportunitiesUnited States federal2 456 posted on 2026-09-10 alone

Every one of the four is an open HTTPS endpoint that answers without an API key, without a proxy and without a browser. That was checked with live requests before this Actor was written, and it is why these four and not the dozen other portals that demand a registration key.

Why does the notice count matter more than the country count?

Because a country list is easy to pad. Anyone can name twenty portals in a description. The number that decides whether a monitor is useful is how many notices actually arrive per day and whether you get all of them — and that is where the two hard parts of this niche are.


The two things that break naive tender scrapers

What happens when a TED search returns more than 15 000 notices?

TED's search API accepts page and limit, and refuses any combination where page × limit exceeds 15 000, with the error SEARCH_WINDOW_TOO_WIDE. It does not warn you on the way in, and it does not tell you how many you lost.

Measured on 2026-09-11: a plain publication-date>=today(-7) query reports 19 795 matching notices. A client that walks pages therefore reaches 15 000 and stops, silently dropping 4 795 notices — 24.2% of the week.

This Actor uses TED's iteration cursor (paginationMode: ITERATION) instead of page numbers, which has no such ceiling. The full 7-day window was walked end to end in 80 requests of 250 and returned exactly 19 795 unique publication numbers, with no repeats.

The live test suite proves both halves of that claim on every run: t_ted_window_cap_is_real asks for page 61 and requires HTTP 400 mentioning 15000; t_ted_cursor_goes_past_the_cap collects more than 15 000 unique notices through the cursor.

How do you read a 250 MB file without downloading 250 MB?

SAM.gov does not publish a date parameter at all. Its Contract Opportunities extract is a single CSV of 249 740 888 bytes and you are expected to take the whole thing.

Measured over the first 25 000 rows on 2026-09-11: the file's PostedDate column is monotonically non-increasing — newest notices first. So the reader can stop as soon as the dates leave the requested window. For a 7-day window that is roughly 11 000 rows instead of the full file.

The saving is not theoretical: at the download rate measured that day, the full file took about 96 seconds of transfer before parsing began.

Because the whole trick rests on that ordering, the live suite re-checks it every run (t_sam_is_sorted_newest_first). If SAM ever changes the order, the test goes red rather than the data going quietly wrong.


What comes back

42 fields per notice. The ones that take work rather than copying:

  • days_to_deadline and is_open — computed at run time from the notice's closing date. is_open is false for awards and for passed deadlines, and null when no deadline was published, which is not the same thing.
  • cpv_division and cpv_division_label — the CPV sector spelled out in English ("45 — Construction work"). No source publishes the label, only the digits.
  • value_eur_estimate — the published amount converted at a fixed rate table dated 2026-09-11, so that one sort works across 19 currencies. It is named an estimate because it is one; value_amount and value_currency always carry exactly what the source said.
  • notice_type_labelcn-standard becomes "Contract notice".
  • buyer_country — ISO-3166 alpha-3 for every source. TED sends DEU, the UK feeds send GB, SAM.gov sends USA; you should not have to care.
  • change_typeNEW or UPDATED in monitor mode.

Plus the buyer's buyer_email, buyer_phone, buyer_contact_point, buyer_website, the award_winner_name on award notices, submission_url, and the notice's own public notice_url.

How full are those fields really?

Measured on 150 live rows from each source on 2026-09-11 with python3 tools/fill_rates.py 150:

FieldTEDFind a TenderContracts FinderSAM.gov
title, buyer_name, buyer_country, notice_url100%100%100%100%
buyer_email100%100%100%100%
buyer_phone100%21%50%44%
cpv_codes100%97%100%0%
deadline_date49%26%100%71%
value_amount68%35%82%27%
award_winner_name36%58%93%27%
naics_code0%0%0%99%

An empty cell here is the source not publishing the field, not this Actor failing to read it — with two exceptions that were this Actor failing to read it, and were fixed:

  • CPV on Find a Tender went from 13% to 97%. The obvious place to read a CPV code is tender.classification. On 30 live releases that field carried a code in 3 of them; tender.items[].additionalClassifications carried one in 15, and awards carry more. Reading only the obvious place leaves most of the feed with no sector at all, which then silently breaks every sector filter.
  • The public notice URL on Contracts Finder went from 7% to 100%. Only 7% of releases publish an HTML document link. The rest are reachable by stripping the trailing -<serial> from the release id, which is what the service's own notice pages do.

Filters, and why they run before you are charged

Every filter in the input form is applied before a row is written to the dataset. On a pay-per-result Actor that is the difference between paying for what you asked for and paying for what the source happened to publish.

Where the source can do the filtering, it does. Keyword, country, sector and notice type are pushed into TED's own query language, so non-matching notices are never even downloaded. The UK and US feeds have no text or sector parameters, so the same filters run locally for them — still before the push.

Why is the keyword filter not applied twice to TED rows?

Because TED's full-text index searches the whole notice document, and this Actor only downloads the fields it asked for. Re-running the keyword match locally would throw away rows TED had already matched on text we never received. Each source declares which filters it has already honoured (filters.SERVER_SIDE), and those are not repeated. There is a test for it, and a deliberate-breakage proof that the test catches its removal.

Can I exclude words, or watch one particular buyer?

Yes. Exclude keywords drops any notice containing one of your words in its title, description or buyer name. Buyer name contains keeps only notices from authorities whose name matches — one ministry, one council, one agency. Both run before billing, like every other filter. Closing dates can be bounded from both sides with Deadline from and Deadline to, or loosely with Deadline within N days.

What stops one source from eating the whole limit?

With several sources selected, maxItems is split evenly between them unless you override it with maxItemsPerSource. Without that split, a request for 200 notices from four sources returns 200 notices from whichever source runs first, and the run looks like a single-country scraper.

What does "only notices still open" actually drop?

Awards, and anything whose deadline has passed. It deliberately keeps notices with no published deadline: on these feeds a missing deadline is normal for prior information notices, and dropping them would hide a whole class of notice from everyone using the filter. Measured above: TED publishes a deadline on 49% of notices and Find a Tender on 26%.


Monitor mode

Turn on Monitor mode — only what changed and schedule the Actor daily.

It remembers which notices it has already delivered in a named key-value store, which survives between runs (the default store does not — it is wiped at the start of every run, and an incremental mode built on it quietly degrades into a full re-scrape that charges you again every morning).

Each delivered row is labelled NEW or UPDATED. A notice counts as UPDATED when its title, buyer, deadline, value, notice type or the first 2 000 characters of its description changed — not when the source touched some invisible internal field, which would make everything "updated" every day.

Unchanged notices are not returned at all unless you ask for them. You already paid for those rows once.

Memory holds 70 000 keys and discards the oldest first when it overflows.

What happens if a run is cut off half way through?

Rows are written to the dataset first and marked as delivered second, never the other way round. If the container is moved or the run times out mid-push, the notices that did not make it out are still unknown to the memory and arrive on the next run. The opposite order loses them permanently, which is the kind of bug you only find by testing for it — so there is a lifecycle test that cuts a multi-batch push in half and asserts that memory never runs ahead of delivery.


Output shapes for agents

  • Compact output returns 15 fields instead of 42 — the ones a language model or a spreadsheet actually reads.
  • Drop empty fields omits keys whose value is null or empty rather than sending them. Given the fill-rate table above, that is real noise removed, not cosmetics.
  • Include the description text can be turned off entirely when you only need metadata; descriptions are by far the largest part of a row.
  • Include the raw source record does the opposite: it adds a raw field holding the untouched payload the source sent, next to the normalised columns, for when you need something this Actor does not map yet.

Honest limitations

Named here rather than left for you to discover:

  • SAM.gov has no CPV codes and no works/supplies/services split. The US classifies with NAICS. naics_code is filled on 99% of SAM rows, but the sector filter in this form is CPV-based and therefore does not apply to SAM.gov. Filter US rows by keyword or by NAICS downstream.
  • The notice type filter applies to TED only. The UK feeds use OCDS tags and SAM.gov uses its own vocabulary; both are reported in notice_type and notice_stage, but the picker's eForms codes are TED's.
  • value_eur_estimate uses a fixed rate table, not live rates. The table is dated in the code (normalize.EUR_RATES_DATE). For a contract closing next month that is entirely adequate; for currency arbitrage it is not.
  • TED publishes a deadline on about half its notices and Find a Tender on about a quarter. That is the source, not the reader. Notices with no deadline keep days_to_deadline and is_open as null rather than guessing.
  • Descriptions come from one lot. A TED notice split into many lots has many descriptions; this Actor carries the first in your preferred language and the notice URL for the rest.
  • No award values for UK Contracts Finder awards without a published amount — 18% of the measured sample had none.
  • Tender documents are not downloaded. documents_url and submission_url point at them; the files themselves stay where they are. Some competitors fetch attachments, at the cost of a much slower and much more expensive run.
  • There is no sub-country region filter. Buyer city and postcode are published; NUTS and state-level filtering is not exposed in this form.
  • You cannot feed it individual notice URLs. This Actor searches the four APIs; it is not a single-page fetcher.
  • There are no proxy or concurrency knobs, because all four sources answer plain requests without one. If a source ever starts blocking, that is a change to this Actor, not a setting for you to tune.
  • No built-in webhook fields. Apify's own webhooks fire on run finish and do the same job without a second copy of the feature.
  • This Actor does not bid for you. It finds and normalises notices.

FAQ

Do I need an API key for any of this?

No. All four sources answer anonymous HTTPS requests. There is nothing to register for and nothing to renew.

How fresh is the data?

TED publishes on working days; the window measured on 2026-09-11 held 19 795 notices from the previous seven days. SAM.gov's extract had 2 456 notices posted on 2026-09-10. The two UK feeds are cursor-based and return whatever has been published up to the moment of the request.

Can I search in a language other than English?

Yes. TED publishes titles and descriptions in up to 24 languages, and Preferred language picks which one lands in the title and description columns. If a notice has no text in your language the next available one is used rather than leaving the row blank. languages_available tells you how many translations existed.

What is CPV and why should I filter by it?

CPV is the European Common Procurement Vocabulary: an eight-digit code whose first two digits name the sector. Picking division 45 in this form matches every code inside it — 45213316, 45000000 and the rest — rather than only notices tagged with the bare division code. Testing equality instead of the prefix is a common mistake and returns a tiny fraction of the real matches.

Can I write my own TED query?

Yes. Advanced — TED expert query takes TED's expert search syntax verbatim and replaces every generated clause, for example:

publication-date>=today(-3) AND buyer-country IN (DEU AUT) AND classification-cpv IN (72000000)

The other sources keep using the form's filters.

How do I get only the awards, to see who won what?

Set Stage to "Awards only". award_winner_name, award_winner_country, value_amount and value_currency carry the result. On the measured sample that is 36% of TED rows and 93% of Contracts Finder rows.

Why are there two UK sources?

They cover different money. Find a Tender carries contracts above the public procurement thresholds; Contracts Finder carries the below-threshold ones plus a large stream of awards. Most UK suppliers want both, and they are separate services with separate APIs.

What does the run report contain?

The REPORT record in the key-value store holds the date window, per-source delivered and scanned counts, how many rows each individual filter removed, the NEW/UPDATED/UNCHANGED tally, the list of buyer countries seen, and any source that failed. A source that fails does not kill the run; it is reported and the others continue.

How do I check the claims in this README?

Every number above comes from a tool shipped with this Actor, in tools/:

  • python3 tools/measure_sources.py — notice counts, country count, the 15 000 ceiling and the SAM ordering, all against the live APIs.
  • python3 tools/fill_rates.py 150 — the fill-rate table.
  • python3 tests/run_all.py --live — logic, deliberate-breakage proof, lifecycle and live-source suites.
  • python3 tools/verify_readme.py — checks that the numbers written here still match the code and the schemas.

What else is this called?

Names buyers use: government tenders scraper, public procurement data, TED contract notices, SAM.gov opportunities, procurement deadline tracker. Every row carries the days left to the submission deadline, so a deadline tracker is the same feed read by date.

Other names for the same feed: public sector bidding data, TED Europa tenders, solicitation notices, contracting authority or buyer lookup, open tender procedure type, CPV code search. They map to the fields value_amount, source_label, notice_type, buyer_name, procedure_type and cpv_codes.