Normalized search over public-sector tenders in TED (EU official procurement notices) -- Spain + EU (NEXUS candidate #16)
- ✓Actively maintained (<30d)
- ✓Clear description
- ✓Documented (README)
- !No standard license detected
git clone https://github.com/nexus-mcp-infra/public-tenders-es-euTools overview
# Public Tenders ES/EU
Normalized search over public-sector tenders published in TED (Tenders Electronic Daily, the EU's
official procurement notice platform) -- covers Spain plus the rest of the EU. NEXUS candidate #16 --
**manual build, not FORGE-generated**, same manual-Cloud-Run-asset pattern as candidates #3/#4/#6/#8/#9/#13.
- `POST /search-public-tenders {"country": "ESP", "keyword": null, "cpv_prefix": null, "days_back": 30, "limit": 20}`
-- **$0.01/call.**
- MCP tool `search_public_tenders` at `/mcp`, same params -- **currently free**, see "Known limitations".
- `GET /health`, `GET /.well-known/agent-card.json`, `GET /openapi.json` (has `x-payment-info`),
`GET /.well-known/402index-verify.txt` (402index claim verification file).
## Source: pivoted from the original brief, with explicit sign-off (2026-08-23)
The task brief named `contrataciondelsectorpublico.gob.es/sindicacion/sindicacion_643/` (Spain's national
PLACSP ATOM sindication feed) as "ya grounded de la sesión anterior". Verified live at the start of this
session via two independent fetch paths (`curl`, `WebFetch`, with and without a browser User-Agent): **403
Forbidden**, and the domain's own `robots.txt` is `Disallow: /` for the entire site -- a declared,
site-wide block, not a rate limit. That would very likely also block Cloud Run (a datacenter IP range,
the common target of this kind of WAF rule). Flagged to the user before writing any code (not silently
substituted) -- user chose to pivot to TED.
**TED (Tenders Electronic Daily, `api.ted.europa.eu`)** is the EU Publications Office's own official
public-procurement notice search API -- free, keyless, no documented auth. Verified live and field-by-field
this session (a third-party README got query-field names wrong on the first pass -- e.g. claimed
`country-buyer` where the real API only accepts `buyer-country` -- so every field/operator below was
confirmed against the real API's own responses, not trusted secondhand):
- `POST https://api.ted.europa.eu/v3/notices/search` with a JSON body of `query` (expert-search string),
`fields` (array of eForms field names to return), `limit`, `scope` (`"ACTIVE"` or `"ALL"`).
- Query fields confirmed live: `publication-date>=YYYYMMDD` (8-digit, no dashes), `buyer-country=ESP`
(ISO-3166 alpha-3), `FT~"phrase"` (full-text), `AND`, `SORT BY publication-date DESC`.
- Output fields confirmed live: `publication-number`, `notice-title` (multilingual dict, keys like `spa`/
`eng`), `buyer-name` (multilingual dict of lists), `buyer-country`, `publication-date`,
`classification-cpv` (list of CPV codes), `total-value` (not present on every notice type -- e.g.
qualification-system notices don't carry one, this is normal, not an error), `notice-type`, `links`
(per-language `html`/`pdf`/`xml` detail URLs).
- Real query tested: `buyer-country=ESP` alone matched 26,314 notices in the `ACTIVE` scope at the time of
this check; a `publication-date` + `buyer-country` combo matched 1,058 in an 8-day window -- real, current
volume, not a stale/empty endpoint.
TED already covers Spain (any tender above the EU publication threshold appears with `buyer-country=ESP`)
plus the rest of the EU, so it fits the "España/UE" framing of the original brief at least as well as the
blocked national sub-threshold feed would have -- arguably better, since it's EU-wide by construction and
not blocked. **Scope difference from what the blocked source would have given**: TED only carries notices
above the EU publication threshold (a real, non-trivial floor -- Spain's own sub-threshold tenders, the
ones PLACSP's national feed alone would have carried, are NOT in TED). This asset does not claim to cover
those; `total_matching_notices` and each result's `source: "TED"` field make the actual coverage explicit
to a buyer, not implied to be broader than it is.
## State (db_constraint exception)
Every sibling manual asset (`url-metadata-api`, `agent-verification-api`, `document-conversion-api`,
`new-x402-listings-feed`, `onchain-activity-index`, `x402-receipt-verifier`) is fully stateless -- each call
hits its upstream fresh, no NEXUS-owned table beyond the standard `traffic_events`/`revenue_events`/
`mcp_call_events` telemetry trio. This is the first of the 8 manual/FORGE assets to need the case-by-case
exception: a new table, **`tenders_query_cache`** (Supabase migration `add_tenders_query_cache_candidate16`,
project `ieduhdgfjdeffvzxvihf`), read+written only by this asset.
**Why**: TED's own docs state there is no documented rate limit but ask callers to be reasonable with
request volume on a shared public resource. Caching identical repeat queries (same country/keyword/
cpv_prefix/days_back/limit, bucketed to the hour) for 1 hour is being a good citizen of that guidance, not
gratuitous state -- a cache hit is marked `"cache_hit": true` in the response so a buyer can see when this
happened.
**This is NOT the state the original PLACSP-scraping design would have needed.** That design (walking an
unfilterable ATOM feed, tracking "already seen" tender IDs to detect deltas -- the same shape as
`new-x402-listings-feed`'s catalog-walk cache) doesn't apply here: TED's own server-side
`publication-date>=` filtering already answers "what's new since X" without this asset needing to remember
anything about individual tenders itself. The only state kept is a short-lived response cache, not a tender
index.
**RLS**: `anon` gets `SELECT` + `INSERT` on `tenders_query_cache` only -- no `UPDATE`/`DELETE` granted, same
insert-append discipline as every other NEXUS telemetry table. A cache "overwrite" is really just a new row
with a newer `cached_at`; the lookup query always picks the freshest row within the TTL window. Stale rows
past the 1-hour TTL are never deleted -- unbounded growth over the 7-day probation window is negligible
(one row per distinct query per hour) and accepted for now; a real cleanup would need either a scheduled job
or a `DELETE` grant this asset doesn't otherwise need.
## Deploy target: Cloud Run
Same pipeline as candidates #4/#3/#6/#8/#9/#13 -- see `skills/infra-deploy-ops`.
```bash
# 1. First deploy -- PUBLIC_DOMAIN not known yet, every real request 421s until step 2.
./scripts/deploy_cloud_run.sh public-tenders-es-eu manual_assets/public-tenders-es-eu
# 2. Grab the printed *.run.app URL, then (only if it differs from env-vars.deploy.yaml's guess):
gcloud run services update public-tenders-es-eu --region us-central1 --project nexus-505016 \
--update-env-vars PUBLIC_DOMAIN=<the-real-domain>
```
## Known limitations (left unfixed on purpose -- CLAUDE.md SS3, no gate without evidence it's needed)
- **MCP tool calls are not charged.** Same in-process-call pattern as every other manual asset in this
codebase.
- **Only EU-publication-threshold tenders.** See "Source" above -- Spain's sub-threshold national tenders
are not in TED and not covered by this asset.
- **`cpv_prefix` is filtered client-side, after fetching.** TED's query syntax doesn't support CPV-prefix
matching server-side; this asset over-fetches (3x `limit`, capped at 50) when `cpv_prefix` is set, then
filters in-process. A caller with a very narrow `cpv_prefix` against a broad `days_back` window could get
fewer real matches than exist in TED's full result set beyond what was over-fetched.
- **Cache is a plain 1-hour TTL, not invalidated on new TED publications.** A query repeated within the same
hour bucket can return results that are up to ~1 hour stale relative to TED. Disclosed via `cache_hit` in
every response.
- **No per-caller rate limiting.** Fine for a 7-day disposable measurement window.
- **`tenders_query_cache` rows are never deleted.** See "State" above.
## Quality gate (2026-08-23, from design not retroactive)
Same 2-agent process as candidates #3/#4/#6/#8/#9/#13 (security lens; functional+quality+buyer-experience
lens) -- results filled in below once run.
## Measurement (candidate #16, 7-day window)
7-day window from 2026-08-23 (real deploy date) -> decision point 2026-08-30. Source of truth:
`traffic_events`/`revenue_events`/`mcp_call_events` (`asset_name = 'public-tenders-es-eu'`), not Cloud Run
logs. Day 7: if zero real traffic (filtering crawlers), pause/delete the Cloud Run service
(`gcloud run services delete public-tenders-es-eu --region us-central1 --project nexus-505016`).
What people ask about public-tenders-es-eu
What is nexus-mcp-infra/public-tenders-es-eu?
+
nexus-mcp-infra/public-tenders-es-eu is tools for the Claude AI ecosystem. Normalized search over public-sector tenders in TED (EU official procurement notices) -- Spain + EU (NEXUS candidate #16) It has 0 GitHub stars and its last recorded update is dated 2026-08-23.
How do I install public-tenders-es-eu?
+
You can install public-tenders-es-eu by cloning the repository (https://github.com/nexus-mcp-infra/public-tenders-es-eu) or following the README instructions on GitHub. ClaudeWave also provides quick install blocks on this page.
Is nexus-mcp-infra/public-tenders-es-eu safe to use?
+
Our security agent has analyzed nexus-mcp-infra/public-tenders-es-eu and assigned a Trust Score of 62/100 (tier: OK). See the full breakdown of passed checks and flags on this page.
Who maintains nexus-mcp-infra/public-tenders-es-eu?
+
nexus-mcp-infra/public-tenders-es-eu is maintained by nexus-mcp-infra. The last recorded GitHub activity is dated 2026-08-23, with 0 open issues.
Are there alternatives to public-tenders-es-eu?
+
Yes. On ClaudeWave you can browse similar tools at /categories/tools, sorted by popularity or recent activity.
Deploy public-tenders-es-eu to your cloud
Ship this repo to production in minutes. Each platform spins up its own environment with editable env vars.
Maintain this repo? Add a badge to your README
Drop the badge into your GitHub README to show it's tracked on ClaudeWave. Each badge links back to this page and reflects the live Trust Score.
[](https://claudewave.com/repo/nexus-mcp-infra-public-tenders-es-eu)<a href="https://claudewave.com/repo/nexus-mcp-infra-public-tenders-es-eu"><img src="https://claudewave.com/api/badge/nexus-mcp-infra-public-tenders-es-eu" alt="Featured on ClaudeWave: nexus-mcp-infra/public-tenders-es-eu" width="320" height="64" /></a>More Tools
A single CLAUDE.md file to improve Claude Code behavior, derived from Andrej Karpathy's observations on LLM coding pitfalls.
An AI skill that provides design intelligence for building professional UI/UX across multiple platforms.
🪨 why use many token when few token do trick — Claude Code skill that cuts 65% of tokens by talking like caveman
CLI proxy that reduces LLM token consumption by 60-90% on common dev commands. Single Rust binary, zero dependencies
The best-benchmarked open-source AI memory system. And it's free.
The fastest, litest AI Gateway. Rust core with Python SDK. Call 100+ LLM APIs in OpenAI (or native) format with cost tracking, guardrails, load balancing, and logging [Bedrock, Azure, OpenAI, Anthropic, OpenAI, VertexAI, vLLM, Nvidia NIM]