Carmentara

Get a Daily Feed of New Government Tenders (SAM.gov & EU TED), Filtered to Your Niche

By the Carmentara team

Stack of blank paper documents being sorted into a single neat line, one marked with a copper clip.

Stop checking SAM.gov and TED by hand, and never miss a niche RFQ again.

If you sell to the public sector, finding the bids is a daily chore. US federal opportunities are on SAM.gov. EU tenders above the EU thresholds are on TED (Tenders Electronic Daily). The two portals use different codes, different searches and different data formats, and deadlines don't wait. Skip a day or two and the RFQ that fit you perfectly has closed, or you're left writing the proposal in a rush.

The fix is a feed: one list of only the new tenders that match your niche, delivered every morning to Slack, a Google Sheet or your CRM. This guide sets one up in three steps, with copy-paste inputs that we tested on 4 October 2026.

Shortcut: the feed runs on the Government Tenders Scraper on Apify. It pulls from the official SAM.gov and TED APIs, and you pay $0.003 per tender it returns. TED works without a key.

Diagram: SAM.gov and TED feed the Government Tenders Scraper, which runs daily and keeps only new tenders. It writes one daily dataset that goes to Slack or Teams, Google Sheets, Notion or a CRM via Zapier, Make or n8n, and an AI fit score.
Two portals in, one daily list out.

Step 1: Define your target

Write down what a good tender looks like for you. Each item below maps to one field in the scraper's input:

  • Industry codes, EU (TED): cpvCodes, for example ["72000000", "48000000"] (IT services; software packages).
  • Industry codes, US (SAM.gov): naicsCodes, for example ["541512", "541519"] (computer systems design; other computer-related services).
  • Words in the notice (both): keywords, for example ["cybersecurity", "software maintenance"].
  • Where (both): countries as ISO codes, for example ["DE", "FR", "NL"]. On TED this is the buyer's country, on SAM.gov the place of performance.
  • Only notice types you can bid on (both): noticeTypes. TED uses codes like "cn-standard" (contract notice). SAM.gov uses names like "Solicitation" or "Sources Sought".
  • Date window (both): dateFrom and dateTo. With "dateFilter": "deadline", the window applies to the submission deadline instead of the publication date.

A few things we learned while testing:

  • Start broad on codes, then narrow with keywords. Buyers pick CPV and NAICS codes themselves, and they're often generic or slightly off. Look up NAICS codes at the US Census Bureau.
  • On SAM.gov, keywords only match titles. Lean on NAICS codes there. Each keyword × NAICS code is a separate SAM.gov request, and personal SAM.gov API keys have a small daily quota, so keep the combinations few.
  • Search TED in the buyer's language too. Many notices exist only in the local language, so add terms like "cybersécurité" or "Informationssicherheit".
  • Keep countries and noticeTypes to single-source runs. Both fields go to both sources, and the values differ per portal. In a mixed US + EU run, "countries": ["DE", "FR"] would filter out every SAM.gov notice. Use one task per source instead (that's what the inputs below do).
  • Set-asides (small business, 8(a), WOSB, SDVOSB, HUBZone) aren't an input filter. Turn on includeRaw for SAM.gov and filter on the official typeOfSetAside code afterwards. There's a snippet for that in Step 2.
Bar chart of 13 TED contract notices from France and the Netherlands published on 2 October 2026 that matched CPV 72 or 48, by main CPV division: IT services 10, software packages 1, architecture and engineering 1, office and computer equipment 1.
A CPV filter also catches tenders where IT is only a side code: here a motorway design job and ID scanners.

Closed tenders (deadline passed) and award notices are left out by default. Turn on includeClosed or includeAwardResults if you want them, for example to research who won what.

Step 2: Configure and run the scraper

Open the Government Tenders Scraper, switch the input to JSON and paste one of these. Both are valid against the scraper's input schema.

EU (TED): new IT and software calls for tender in Germany, France and the Netherlands. No key needed.

JSON
{
  "sources": ["ted"],
  "cpvCodes": ["72000000", "48000000"],
  "countries": ["DE", "FR", "NL"],
  "noticeTypes": ["cn-standard"],
  "maxResults": 100,
  "onlyNew": true
}

US (SAM.gov): solicitations and sources-sought notices for IT services. Get a free API key in your SAM.gov account (Account Details) and paste it into samApiKey.

JSON
{
  "sources": ["sam"],
  "naicsCodes": ["541512", "541519"],
  "noticeTypes": ["Solicitation", "Combined Synopsis/Solicitation", "Sources Sought"],
  "includeRaw": true,
  "maxResults": 100,
  "onlyNew": true,
  "samApiKey": "YOUR_SAM_GOV_API_KEY"
}

maxResults caps the number of tenders per run, so it also caps your cost. Start small (say 20) for the first try.

Prefer code? Here's the same idea in Python with the official apify-client package. This run covers both sources at once, so it leaves out countries:

Python
# pip install "apify-client>=3"
import os
from apify_client import ApifyClient

client = ApifyClient(os.environ["APIFY_TOKEN"])

run_input = {
    "sources": ["ted", "sam"],
    "keywords": ["cybersecurity", "software maintenance"],
    "cpvCodes": ["72000000", "48000000"],   # TED only
    "naicsCodes": ["541512", "541519"],     # SAM.gov only
    # "countries" applies to both sources. In a mixed US + EU feed, leave it out
    # (or run one task per source), otherwise one side filters everything away.
    "maxResults": 200,
    "onlyNew": True,                        # only notices not returned before
    "samApiKey": os.environ["SAM_API_KEY"],
}

run = client.actor("carmentara/procurement-radar").call(run_input=run_input)

for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item["source"], (item.get("publicationDate") or "")[:10], item["organization"], "|", item["title"][:80])
    print("   deadline:", item.get("deadline") or "n/a", "|", item["url"])

Every row has the same keys, whichever portal it came from. Two real rows from our test runs on 4 October 2026:

JSON
[
  {
    "source": "ted",
    "noticeId": "dbcfba0a-06a5-44df-a644-9e4c4718b53f",
    "title": "France – Maintenance of information technology software – Progiciel de gestion des signatures électroniques : maintenance, évolution et prestations associées",
    "organization": "Collectivité européenne d Alsace",
    "country": "FRA",
    "cpvCodes": ["72267100"],
    "noticeType": "cn-standard",
    "publicationDate": "2026-10-02T10:00:00.000Z",
    "deadline": "2026-11-02T10:00:00.000Z",
    "url": "https://ted.europa.eu/en/notice/-/detail/679048-2026"
  },
  {
    "source": "sam",
    "noticeId": "9974c0be61754301a86671f9d056ab9f",
    "title": "USHMM-RFI-2027-MSSP",
    "organization": "UNITED STATES HOLOCAUST MEMORIAL MUSEUM > UNITED STATES HOLOCAUST MEMORIAL MUSEUM > USHMM PROCUREMENT DIVISION",
    "country": "USA",
    "naicsCodes": ["541512"],
    "noticeType": "Presolicitation",
    "publicationDate": "2026-10-03T00:00:00.000Z",
    "deadline": "2026-10-30T21:00:00.000Z",
    "url": "https://sam.gov/opp/9974c0be61754301a86671f9d056ab9f/view"
  }
]

TED rows carry cpvCodes and SAM.gov rows carry naicsCodes. deadline, estimatedValue/estimatedCurrency and shortDescription are filled in whenever the source publishes them. No personal contact details are included.

Two cards showing the same fields for one real TED row (an electronic-signature software maintenance tender in France, CPV 72267100, deadline 2 November 2026) and one real SAM.gov row (a presolicitation under NAICS 541512, deadline 30 October 2026).
Every row has the same keys, whichever portal it came from.

Filter SAM.gov notices by set-aside. Run the SAM.gov input above (it has "includeRaw": true), then keep only the set-aside codes you qualify for. The codes are listed in the SAM.gov Opportunities API docs.

Python
# pip install "apify-client>=3"
# Needs a SAM.gov run with "includeRaw": true. Set DATASET_ID to that run's dataset ID.
import os
from apify_client import ApifyClient

WANTED = {"SBA", "SDVOSBC", "WOSB", "EDWOSB", "8A", "HZC"}  # SAM.gov set-aside codes you can bid on

client = ApifyClient(os.environ["APIFY_TOKEN"])
for item in client.dataset(os.environ["DATASET_ID"]).iterate_items():
    set_aside = (item.get("raw") or {}).get("typeOfSetAside")
    if item["source"] == "sam" and set_aside in WANTED:
        print(set_aside, "|", item["title"][:70], "|", item["url"])

Step 3: Schedule it daily

  1. In the Apify Console, click Save as a new task on your input (tasks). Make one task per source if you followed Step 1.
  2. Open Schedules, create a schedule with a daily cron expression such as 0 7 * * * in your timezone, and add your task(s) (Apify Schedules).
  3. Leave Only new since last run (onlyNew) on. The scraper remembers which notices it already returned for your setup, so each morning's run holds only the new ones.

The first run returns everything that matches in the default date window (the last 30 days). After that, runs are small, and the daily cost follows the number of new tenders in your niche: 20 new tenders in a day cost $0.06 plus the $0.00005 start fee.

Bar chart of days from publication to submission deadline for 11 of the same 13 TED IT tenders published on 2 October 2026. Three close within a week (3, 3 and 7 days); the rest range from 14 to 77 days. Two notices have no deadline in the data.
Published on a Friday, two of these close the following Monday (5 Oct).

Want to start from a working setup? There are two ready-made tasks: EU IT and software tenders daily and cybersecurity tenders in the US and EU.

What to do with the feed

Add these on your task's Integrations tab, so they fire after every scheduled run.

  • Slack: Apify's Slack integration posts to a channel when a run succeeds or fails. The message is a template you can edit, and it can link straight to the run's results.
  • Microsoft Teams: use Apify's Power Automate connector. It can start a flow when a run finishes and read the results, and the flow then posts to Teams. The connector needs a Power Automate plan that includes premium connectors.
  • Google Sheets: add the Google Sheets Import & Export integration in append mode with deduplicateByField set to noticeId. Each day's tenders land under the previous ones, with no duplicates.
  • Notion, HubSpot and other tools: use Apify's apps for Zapier, Make or n8n. Each one has ready Apify + Notion and Apify + HubSpot connections (Zapier: Notion, HubSpot; Make: Notion, HubSpot; n8n: Notion, HubSpot). A typical flow: when a run succeeds, create one Notion page or HubSpot deal per tender.
  • Your own system: a webhook sends an HTTP POST with the run details, including the dataset ID, to any URL you choose.

Score each tender's fit with AI

A daily feed is still a list you have to read. This script reads the latest run's results and asks an LLM to score each tender against your company profile from 0 to 10. For good fits, it also outlines a proposal. It uses the OpenAI Python client. Any OpenAI-compatible provider works if you set OPENAI_BASE_URL. Edit COMPANY to describe your business.

Python
# pip install "apify-client>=3" openai
# Env: APIFY_TOKEN, OPENAI_API_KEY (optional: OPENAI_BASE_URL for any OpenAI-compatible provider, LLM_MODEL)
import json
import os

from apify_client import ApifyClient
from openai import OpenAI

COMPANY = """We are a 12-person IT services firm. We build and maintain web applications
and run managed cybersecurity (SOC, vulnerability management). ISO 27001 certified.
We bid in Germany, France and the US federal market as a small business."""

apify = ApifyClient(os.environ["APIFY_TOKEN"])
llm = OpenAI()  # reads OPENAI_API_KEY (and OPENAI_BASE_URL if set) from the environment
model = os.environ.get("LLM_MODEL", "gpt-4o-mini")

# Latest successful run of the actor; set DATASET_ID to score a specific run's results instead.
dataset_id = os.environ.get("DATASET_ID")
dataset = apify.dataset(dataset_id) if dataset_id else (
    apify.actor("carmentara/procurement-radar").last_run(status="SUCCEEDED").dataset()
)

for t in dataset.iterate_items():
    tender = {k: t.get(k) for k in ("source", "title", "organization", "country", "cpvCodes",
                                    "naicsCodes", "noticeType", "deadline", "estimatedValue",
                                    "estimatedCurrency", "shortDescription", "url")}
    resp = llm.chat.completions.create(
        model=model,
        messages=[
            {"role": "system", "content": "You qualify public tenders for a company. Answer with JSON only: "
             '{"fit": 0-10 integer, "why": one sentence, "outline": [3 to 5 short proposal section titles]}'},
            {"role": "user", "content": f"Company:\n{COMPANY}\n\nTender:\n{json.dumps(tender, ensure_ascii=False)}"},
        ],
    )
    text = resp.choices[0].message.content.strip().removeprefix("```json").removesuffix("```")
    try:
        result = json.loads(text)
    except json.JSONDecodeError:
        print("Could not parse:", text[:200])
        continue
    print(f"[{result['fit']}/10] {tender['title'][:70]}\n   {result['why']}\n   {tender['url']}")
    if result["fit"] >= 7:
        for section in result["outline"]:
            print("   -", section)

Treat the score as a triage aid, and read the full notice before you decide to bid.

Can I do this for free?

Partly. Both portals have free saved searches with email alerts. On SAM.gov, filter Contract Opportunities by NAICS, set-aside and notice type, then save the search. On TED, create an account, filter by CPV code and country, then save the search. That gives you two inboxes in two formats, and nothing you can deduplicate, score or route.

Both official APIs are free too, if you have engineering time. Minimal requests:

Bash
curl "https://api.sam.gov/opportunities/v2/search?api_key=$SAM_API_KEY\
&postedFrom=09/01/2026&postedTo=10/03/2026&ncode=541511&limit=100"
Bash
curl -X POST https://api.ted.europa.eu/v3/notices/search \
  -H 'content-type: application/json' \
  -d '{"query":"classification-cpv IN (72000000 48000000) AND buyer-country IN (DEU EST) AND publication-date >= 20260901 SORT BY publication-date DESC",
       "fields":["publication-number","notice-title","buyer-name","buyer-country","classification-cpv","notice-type","publication-date"],
       "limit":50,"page":1}'

After that, you still have to map both schemas onto one shape, keep a "seen" list, schedule the job and handle retries. That's the part the scraper does for you.

Start your feed

Run the Government Tenders Scraper on Apify. Paste one of the inputs from Step 2, run it once with a small maxResults, then save it as a task and schedule it.

  • Price: $0.003 per tender notice ($3 per 1,000) plus a $0.00005 start fee per run. There's no monthly fee from us.
  • Apify's free plan includes $5 a month to spend in Apify Store, and no credit card is required (Apify pricing). At $0.003 per tender, that covers about 1,600 tenders a month.
  • Keys: TED needs none. For SAM.gov, bring your own free API key.

Missing a filter or a field? Open an issue on the scraper's Issues tab.