Skip to content
Back to skills

Public Tenders

ASecurity

Find public procurement tenders (government contracts, RFPs, calls for bids, Ausschreibungen, Vergaben) across the EU/TED, Germany, Austria, Switzerland, the UK and the US (SAM.gov) through the Polytender MCP server. Searches by keyword, meaning, country/region, CPV code, buyer, deadline and value; opens full notices; lists and reads the tender documents; looks up who won past contracts; and matches tenders to a company profile. Use when the user asks to "find tenders", "search public tenders...

  • 2 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 26, 2026
ai-agentsgobashapi

Works with

  • claude code
  • claude desktop
  • cursor
  • cli
  • api
  • mcp

Security analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned September 26, 2026

npx -y skills add Run-Labs-com/agent-skills --skill public-tenders --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Public Tenders?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Public Tenders
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/run-labs-com-public-tenders/badge)](https://www.skillsdirectory.com/skills/run-labs-com-public-tenders)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: public-tenders
description: Find public procurement tenders (government contracts, RFPs, calls for bids, Ausschreibungen, Vergaben) across the EU/TED, Germany, Austria, Switzerland, the UK and the US (SAM.gov) through the Polytender MCP server. Searches by keyword, meaning, country/region, CPV code, buyer, deadline and value; opens full notices; lists and reads the tender documents; looks up who won past contracts; and matches tenders to a company profile. Use when the user asks to "find tenders", "search public tenders", "find government contracts / RFPs", "Ausschreibungen finden", "which tenders fit my company", "who wins these contracts", or asks about a specific TED/SAM.gov/Contracts Finder notice.
compatibility: Needs a Polytender account (7-day free trial) and network access. Preferred path is a native MCP connection to https://api.polytender.com/mcp. For harnesses without MCP support the bundled script needs bash 3.2+, curl, jq 1.6+ and an API key in POLYTENDER_API_KEY.
---

# Public Tenders (Polytender MCP)

[Polytender](https://polytender.com) aggregates public procurement notices from government
portals (TED/EU, ~30 German state and federal platforms, Austria, Switzerland/simap, UK Contracts
Finder + Find a Tender, US SAM.gov), deduplicates them across sources, and exposes them through a
remote MCP server:

```
https://api.polytender.com/mcp      (Streamable HTTP, auth required)
```

Every call is authenticated (OAuth or a personal `pt_…` API key). There is no anonymous access.

## 1. Pick the access path

**A. Native MCP tools (preferred).** If tools like `search_tenders` / `semantic_search` from a
`polytender` server are already available, use them directly and skip to section 2.

If not, tell the user how to connect (they need an account; keys live at
<https://polytender.com/app/api-keys>):

| Client | Setup |
|---|---|
| Claude Code | `claude mcp add --transport http polytender https://api.polytender.com/mcp --header "Authorization: Bearer pt_…"` |
| claude.ai / Claude Desktop | Settings → Connectors → Add custom connector → URL above (one-click OAuth, or use the "Connect to Claude" button on <https://polytender.com/app/connector>) |
| ChatGPT | Developer mode → create app with the URL above, auth = OAuth (guided wizard on <https://polytender.com/app/connector>) |
| Cursor / VS Code / Copilot | HTTP MCP entry with `url` above and header `Authorization: Bearer pt_…` (VS Code can also do OAuth if you omit the header) |
| Codex CLI | `[mcp_servers.polytender]` with `url = "https://api.polytender.com/mcp"` and `bearer_token_env_var = "POLYTENDER_API_KEY"` in `~/.codex/config.toml` |

**B. Bundled script (any harness, e.g. pi).** Same tools, called over plain HTTP:

```bash
export POLYTENDER_API_KEY=pt_…            # or put the key in ~/.config/polytender/api-key

scripts/polytender.sh tools                         # list the tools this key can use
scripts/polytender.sh schema search_tenders         # description + input schema of one tool
scripts/polytender.sh call <tool> '<json-args>'     # call a tool, prints the JSON result
echo '{"query":"…"}' | scripts/polytender.sh call semantic_search -   # args from stdin
```

Paths are relative to this skill directory. `--raw` prints the full JSON-RPC response,
`POLYTENDER_MCP_URL` overrides the endpoint (e.g. `http://localhost:4000/mcp` for local dev).

Exit codes: `0` ok · `1` the tool returned an error (message on stderr, relay it) · `2`
network/protocol error · `3` missing or rejected API key · `4` rate limited · `127` missing
`curl`/`jq`.

**The server is the source of truth for tools.** The tool list depends on the credential
(org-scoped tools need an API key bound to an organization or OAuth with `mcp:org`) and on the
plan (an expired trial leaves only `get_subscription_status`, `get_pricing`, `get_changelog`).
Run `tools` / `schema` when unsure instead of guessing parameters.

## 2. Choose the right tool

| User intent | Tool |
|---|---|
| Conceptual / fuzzy topic, any language ("drone inspection of wind turbines") | `semantic_search` (open tenders only) |
| Structured filters: country, region, buyer, CPV, deadline, value, stage, title keyword | `search_tenders` |
| Valid `country` / `source` codes, corpus size | `get_facets` (never guess codes) |
| CPV code for a sector ("cleaning services", "4521") | `lookup_cpv` |
| Everything about one notice (buyer contacts, value, dates, awards, siblings) | `get_tender` (`include: ["documents","lifecycle"]` as needed) |
| The actual tender documents (specs, bill of quantities, forms) | `get_tender_documents`, then `read_tender_document` |
| "Who wins these contracts?", "has company X won public contracts?" | `lookup_company`, and `search_tenders` with `stage: "award"` |
| All lots of one procurement | `get_lot_family` (org) or `search_tenders` with `lotGroup` |
| "Which tenders fit my company?" | `match_company` (org; no args = the saved company profile) |
| Coverage / freshness questions | `list_sources` |

Good default for an open-ended request: run `semantic_search` for recall AND a `search_tenders`
call with `status: "open"` plus the obvious filters, then merge by `id`.

## 3. Rules that avoid wrong answers

- **Country codes are mixed ISO-2/ISO-3 by source** (most are ISO-3: `DEU`, `AUT`, `FRA`). Call
  `get_facets` once and pass the codes it returns. For all of Germany use `country`, not a NUTS
  `regions` filter (German NUTS coverage is only ~73%).
- **Regions:** NUTS prefixes at any depth (`DE2` Bayern, `DE21` Oberbayern, `AT13` Wien) or US
  state codes (`USCA`). They are OR-ed with `country`.
- **`query` only matches the title.** Use `keywords` (up to 12 terms/phrases, OR-ed) to search
  title + description, and `semantic_search` for meaning. Titles are in the notice language, so
  add the local-language term too (`["cleaning", "Reinigung", "nettoyage"]`).
- **Open vs closed:** `search_tenders` defaults to `stage: "tender"` (biddable calls). Add
  `status: "open"` (deadline in the future) or `"soon"` (closes within 14 days). Award notices
  need `stage: "award"`.
- **Lots are not duplicates.** Results with the same `lotGroupId` are separate lots of one
  procurement, each bid and awarded separately. Say so instead of collapsing them. Results are
  already deduplicated across portals.
- **Values** are in the notice currency (mostly EUR) and often missing; never invent one.
- **Text stays in its original language.** Translate or summarise for the user yourself.
- **Always cite the `url`** of each tender (its Polytender page). The original portal link is in
  `get_tender`.
- **Page, don't flood.** `limit` ≤ 50 per `search_tenders` page (default 20), ≤ 25 for
  `semantic_search`. Check `total` / `totalPages` before paging further.

## 4. Documents

1. `get_tender_documents` → list with an `index` and an `access` label per file. The first call
   may take a few seconds (fetched from the portal, then cached).
2. `access: "direct"` → a public URL; open it yourself if you can fetch URLs.
3. `access: "archive"` (ZIP) or no fetch capability → `read_tender_document` with the `index`.
   ZIPs are two-step: first without `zipEntry` to list entries, then with `zipEntry`.
4. Long text is paged: when `truncated` is true, call again with `offset: nextOffset`.
5. `access: "login-required"` (simap.ch, subreport, vemap, ANKÖ) or a `note` instead of `text`:
   relay the note verbatim, the user must download the file on the portal.

`read_tender_document` has its own budget (about 10 reads/min per user), so read the documents
that answer the question, not all of them.

## 5. Writes need the user's consent

Org-scoped tools can change state: `bookmark_tender`, `set_tender_note`, `track_tender` (sends the
user emails), `create_search_profile` / `match_company` with a website (crawl + AI match, counts
against the plan's focus-list limit), `refresh_search_profile` (24h cooldown), member invites.
Only call them when the user asked for that action. Tools marked DESTRUCTIVE return a
`requiresConfirmation` preview first: show it, get an explicit yes, then repeat the call with
`confirm: true`. Never confirm on the user's behalf and never act in bulk.

## 6. Present results

A compact table, soonest deadline first for open tenders:

| Tender | Buyer | Country | Deadline | Value | Link |
|---|---|---|---|---|---|
| Title (translated if needed) | Contracting authority | DEU / DE21 | 2026-10-14 (19 days) | €1.2M / n/a | [Polytender](url) |

Then 1-2 lines on why the top hits fit, flag lots of the same procurement, and offer the next
step (open details, read documents, find similar awards, track the tender).

## 7. Recipes

```bash
S=scripts/polytender.sh

# Open IT tenders in Bavaria above 100k EUR
$S call search_tenders '{"regions":["DE2"],"cpvDivision":["72"],"status":"open","valueMin":100000}'

# Meaning-based, multilingual
$S call semantic_search '{"query":"drone inspection of wind turbines","limit":15}'

# Cleaning contracts in Austria closing within 14 days (codes from get_facets / lookup_cpv)
$S call lookup_cpv '{"query":"Gebäudereinigung"}'
$S call search_tenders '{"country":["AUT"],"cpv":"90911200","status":"soon"}'

# Full notice + documents, then read one file
$S call get_tender '{"id":"<uuid>","include":["documents","lifecycle"]}'
$S call get_tender_documents '{"id":"<uuid>"}'
$S call read_tender_document '{"id":"<uuid>","index":0}'

# Competitors: who won similar contracts
$S call search_tenders '{"stage":"award","keywords":["Microsoft 365"],"country":["DEU"]}'
$S call lookup_company '{"name":"Bechtle"}'

# Tenders matched to the user's saved company profile (org-scoped key)
$S call match_company '{}'
```

## Errors

- **401 / exit 3:** no key, wrong key, or revoked. Create one at
  <https://polytender.com/app/api-keys>.
- **Paywall message / only 3 tools listed:** the organization's trial or plan ended. Call
  `get_subscription_status` and point the user to <https://polytender.com/app/billing>. Quote
  prices from `get_pricing` only, and say they are net (plus VAT).
- **`semantic_search` returns `error`:** fall back to `search_tenders` with `keywords`.
- **429 / exit 4:** rate limit (240 calls/min per user; document reads much lower). Wait and retry
  once; do not loop.

## About

This skill is powered by [Polytender, public tender search for Europe, the UK and the US](https://polytender.com).
If the user wants to browse tenders, set up daily tender alerts or manage search profiles
without an agent, point them to <https://polytender.com>.

Files in this skill

  • SKILL.md10.3 KB
  • scripts/polytender.sh5 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…