TED Tenders: Romania & EU notices by CPV (official API)
Pricing
Pay per event
TED Tenders: Romania & EU notices by CPV (official API)
Search EU public tenders from TED (Tenders Electronic Daily) by CPV code, buyer country, notice type and keywords. Clean JSON with provenance on every record, an incremental mode for daily alerts, and a daily self-check. Data from the official TED Search API.
Pricing
Pay per event
Rating
0.0
(0)
Developer
PPFTEC S.R.L
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
2 hours ago
Last modified
Categories
Share
TED EU tenders
Status: not published. 0.1.5, 2026-10-04. The source is allow-with-conditions in
docs/compliance/sources.mdand was approved by Petre on 2026-10-04 (DECISIONS D1). Operator: PPFTEC S.R.L. (https://ppftec.com). First live health check passed on 2026-10-04 (docs/qa/ted-tenders-live-2026-10-04.md); no Apify run yet (D2b). Prices below are hypotheses (D4).
Search public tenders published in TED (Tenders Electronic Daily), the EU's official procurement journal, through the official, anonymous TED Search API v3. Each run returns clean, flat notice records (title in your language, buyer, country, CPV codes, estimated value, deadlines, links) with full provenance on every record.
For: bid and sales teams watching tenders in their sector, procurement consultancies and agencies, data teams, and AI agents that need EU tender data as JSON.
Quick start
RO IT tenders from the last 7 days:
{"countries": ["ROU"],"cpvCodes": ["72000000"],"publishedFrom": "7 days","language": "ron","maxResults": 200}
Daily Romanian tenders above the EU threshold. TED publishes the Romanian notices that are above the EU thresholds (below-threshold Romanian procedures are only in SEAP). Run this once a day with an Apify schedule (e.g. 07:00 Europe/Bucharest); each run returns only the notices published since the previous one, marked is_new: true:
{"countries": ["ROU"],"noticeTypes": ["cn-standard", "cn-social"],"publishedFrom": "3 days","incremental": true,"stateKey": "daily-ted-ro","language": "ron","maxResults": 500}
Construction (CPV 45000000) in Germany and Austria, only notices that are new since the last run (schedule it daily):
{"countries": ["DEU", "AUT"],"cpvCodes": ["45000000"],"noticeTypes": ["cn-standard"],"incremental": true,"stateKey": "construction-de-at"}
Expert query (TED syntax, overrides every filter above, dates included; cannot be combined with incremental):
{"expertQuery": "buyer-country IN (FRA) AND FT ~ (cybersecurity) AND publication-date >= 20260901 SORT BY publication-date DESC","maxResults": 100}
Validate an input without calling TED (no request, nothing emitted, nothing charged; the built query is stored under key DRY_RUN):
{ "keywords": ["cloud", "software"], "countries": ["ROU"], "dryRun": true }
Output example
One record (from a recorded TED response, 2026-10-03). Every record validates against src/schemas/ted-notice.schema.json before it is pushed; a record that fails is a bug and fails the run.
{"record_type": "ted-notice","publication_number": "678144-2026","notice_version": 1,"notice_type": "cn-standard","procedure_type": "open","contract_nature": "supplies","publication_date": "2026-10-02","source_publication_date": "2026-10-02+02:00","buyer_names": ["COMPANIA NATIONALA DE ADMINISTRARE A INFRASTRUCTURII RUTIERE S.A."],"buyer_country": ["ROU"],"buyer_city": ["Bucuresti"],"cpv_codes": ["34992200"],"place_of_performance": ["RO321", "ROU"],"title": "Romania – Road signs – ANSAMBLU LUMINOS PENTRU CRESTEREA SIGURANTEI RUTIERE PE SECTOARELE PERICULOASE","title_language": "eng","estimated_value": { "amount": 74811664, "currency": "RON", "level": "procedure" },"tender_deadlines": ["2026-11-02+02:00"],"notice_url": "https://ted.europa.eu/en/notice/-/detail/678144-2026","xml_url": "https://ted.europa.eu/en/notice/678144-2026/xml","matched_keywords": ["road signs"],"provenance": {"envelope_version": "1.0.0","source_id": "ted-search-api-v3","source_url": "https://api.ted.europa.eu/v3/notices/search","source_request": {"method": "POST","body_sha256": "104903eec02e5f9ff178ada343864de9c8ac062dc2143c9219ac186eb6da918e","page": "1"},"source_record_url": "https://ted.europa.eu/en/notice/-/detail/678144-2026","retrieved_at": "2026-10-03T23:48:49.847Z","source_version": { "api_version": "v3", "record_version": "1" },"producer": { "name": "ted-tenders", "version": "0.1.5", "build": null, "run_id": null, "adapter": "ted-search-v3" },"record_hash": "sha256:138d91e5cea81f61d1bb84dcc89308d6297431039ba953a0527b8234314c0a5e","licence": {"id": "LicenseRef-EC-Decision-2011-833","url": "https://ted.europa.eu/en/legal-notice","attribution": "Source: TED — Tenders Electronic Daily, © European Union, https://ted.europa.eu"},"personal_data": "none"}}
Fields
| Field | From TED | Notes |
|---|---|---|
publication_number | publication-number | Primary key, e.g. 678144-2026. |
notice_version | notice-version | Integer. |
notice_type | notice-type | e.g. cn-standard (contract notice), can-standard (award). |
procedure_type, contract_nature | procedure-type, contract-nature-main-proc | null when absent. |
publication_date | publication-date | YYYY-MM-DD; the raw value (with offset) is in source_publication_date. |
buyer_names, buyer_city | buyer-name, buyer-city | All languages flattened, de-duplicated. Organisations only. |
buyer_country | buyer-country | ISO 3166-1 alpha-3. |
cpv_codes | classification-cpv | De-duplicated 8-digit codes. |
place_of_performance | place-of-performance | NUTS / country codes, de-duplicated. |
title, title_language | notice-title | Your language, else English, else the first available. |
estimated_value | estimated-value-* | Procedure value if present (level: procedure); else the sum of lot values when all lots share one currency (lots-sum); else null. |
tender_deadlines | deadline-receipt-tender-date-lot | Verbatim source strings. |
notice_url, xml_url | links | English HTML page (else first language); multilingual XML. |
matched_keywords | — | Your keywords found in any title language (TED's full-text match also covers fields we do not return, so this can be empty). |
is_new | — | true in incremental mode. |
provenance | — | Source, request digest, retrieval time, versions, record hash, licence. See docs/spec/provenance-schema.md. |
Pricing (pay per event)
Pay per event. You pay only for notices actually pushed.
| Event | Price | When |
|---|---|---|
apify-actor-start | $0.00005 (Apify default) | per run |
notice | $0.002 ($2 per 1,000) | per notice pushed |
Examples: a daily incremental alert with ~20 new notices ≈ $0.04/day; a 1,000-notice backfill ≈ $2. Nothing is charged for dry runs, health runs, invalid input, TED errors or schema drift. Set a maximum charge per run in Apify; the actor stops cleanly when it is reached.
Incremental mode and scheduling
With incremental: true the actor keeps a small state in your named key-value store pda-ted-tenders-state (key derived from your input, or your stateKey):
- a watermark: the newest publication date of the last run that went through its whole result set;
- the publication numbers already delivered inside the current query window.
The next run starts one day before the watermark (overlap), skips notices already delivered and marks the rest is_new: true. A run that stops early (your maxResults, your max charge, the scan cap or the run time budget) does not move the watermark, so the next run re-scans the same window and delivers the older notices it did not reach; nothing is skipped and nothing is charged twice. State is saved after every page.
incremental needs the filter fields (keywords, cpvCodes, countries, noticeTypes, dates); it is rejected together with expertQuery, because an expert query is sent verbatim and cannot be narrowed by date. Do not run two incremental runs with the same state key at the same time (the last one to save wins).
Schedule a daily run and connect Apify integrations (e-mail, Slack, webhook) to get alerts.
Limits
- Rate: one request at a time, at least 1 s apart, and at most 500 notices per rolling 6 minutes (our reading of TED's fair-use limits: 700 requests/min and 600 views or downloads per 6 min per IP). That is about 80 notices per minute: a 1,000-notice backfill takes about 12 minutes. TED documents no rate-limit headers; we back off on 429/5xx (1, 2, 4, 8 s,
Retry-Afterhonoured, 5 attempts) and then fail. - Paging: always TED's
ITERATIONmode (no 15,000-notice cap), 100 notices per page. - Work per run: a run scans at most
max(10 × maxResults, 2,000)notices (and the matching number of pages), and stops after 4 hours (or just before the Apify run timeout). At ~80 notices per minute that is roughly 19,000 notices per run; larger backfills need several incremental runs. These stops end the run with exit code 0 and aStopped: …status. - Retry-After: honoured in full; if TED asks us to wait longer than the remaining run time, the run stops with exit code 1 and says so.
- CPV codes match their sub-codes:
cpvCodes: ["72000000"]also returns notices that only carry child codes such as72223000or72268000(live check 2026-10-04: 66 of 100 returned notices had no exact72000000, none lacked a72…code). Use the most specific code you want. - Titles: TED translates titles into all EU languages; the selector offers eng, ron, deu, fra, spa, ita, pol.
- Excluded for privacy: contact-person names, e-mails, phones, fax numbers and winner details are never requested from TED.
- API only: the actor calls
api.ted.europa.euonly; never the TED website orted.europa.eu/packages/*.
Data source and licence
Source: TED — Tenders Electronic Daily, Publications Office of the European Union, via the TED Search API v3 (https://api.ted.europa.eu/v3/notices/search, anonymous; docs: https://docs.ted.europa.eu/api/latest/index.html).
Attribution (also in every record's provenance.licence and in the dataset description): Source: TED — Tenders Electronic Daily, © European Union, https://ted.europa.eu
Reuse: TED notices may be reused for commercial or non-commercial purposes under Commission Decision 2011/833/EU (https://ted.europa.eu/en/legal-notice). Modifications: this actor selects 18 fields, normalises dates, flattens multilingual names, de-duplicates lists and computes estimated_value; the original notice is always linked in notice_url. This actor is not affiliated with or endorsed by the European Union or the Publications Office. The TED logo is not used.
Reliability
- Schema drift fails loudly. Every TED response is checked against a pinned source shape (
src/source-shape.schema.json). A missing or retyped field, a non-numeric value, or TED rejecting one of our fields stops the run with exit code 1, the statusSCHEMA DRIFT at ted-search-api-v3: …and aDRIFT_REPORT(expected, actual, sample) in the run's key-value store. Records already pushed stay valid. New unknown keys are only warnings (DRIFT_REPORT.warnings). - Health checks.
mode: "health"(hidden) runs a canary: TED syntax check on our field list, a 10-notice search for Romanian notices of the last 7 days (shape + output schema), and the API doc hash; it pushes a record to the named datasetpda-healthwith p50/p95 latency. Planned schedule: daily, 06:15 UTC. - Kill switch. The maintainer can disable all runs with
PDA_KILL_SWITCH=on(statusDisabled by maintainer: …, no request, no charge). It is checked at start and again before every request to TED. - Changelog: see
CHANGELOG.md.
Security
- Network: the actor calls only
https://api.ted.europa.eu(fixed URLs; user input only reaches the JSON body). Redirects are never followed: a 3xx response fails the run. - Personal data: contact-person, e-mail, phone, fax and winner fields are never requested. In rare cases a buyer name can be a natural person (e.g. a sole-trader contracting entity) as published by TED.
- Container:
apify/actor-node:24pinned by digest; the process runs as the non-root usermyuser. - Source gate: TED is approved (D1, 2026-10-04). The gate stays in the code: a source marked
pendingis refused on Apify and in local live runs unless the maintainer setsPDA_ALLOW_PENDING_SOURCES=1. - Input limits: at most 20 keywords, 100 CPV codes, 40 countries;
expertQueryat most 2,000 characters; keywords are plain words (letters, digits, spaces and. , ' & / -; noAND/OR/NOTor search operators: useexpertQuery). Dates must fall between 1993-01-01 and today + 2 years. - Health mode (
mode: "health") is maintainer-only. It needsPDA_HEALTH_ENABLED=1, the secret env varPDA_HEALTH_TOKEN(≥ 32 characters, an Apify secret referenced as@pdaHealthTokenin.actor/actor.json) and the same value in the secret inputhealthToken, compared in constant time; ifPDA_MAINTAINER_USER_IDis set,APIFY_USER_IDmust match it. Otherwise the run answers "Invalid input" with no request. The token is never logged or stored. - Links:
notice_urlandxml_urlmust point atted.europa.eu; anything else is treated as schema drift. - Dependencies (
npm audit, 2026-10-04): 0 vulnerabilities.basic-ftpis forced to 6.2.1 viaoverrides(GHSA-c475-qrg2-pj4r).http-cache-semanticswas updated in the lockfile to 4.3.0 (fix for GHSA-ch52-4w7c-c8xp, published 2026-10-04) with a plainnpm audit fix, so the accepted-risk note of 0.1.1 is closed. Never usenpm audit fix --force(it would downgradeapifyto 3.1.13). - Not yet addressed (tracked in
docs/security/ted-tenders-review-2026-10-04.md): the TED per-IP limit is enforced per run only, so concurrent runs on shared Apify egress IPs could exceed it (TED-SEC-04, needs a live check); concurrent incremental runs with the same state key are not locked (TED-SEC-11, documented above).
Use with AI assistants (MCP)
This actor can be called by AI assistants through Apify's MCP server (a service run by Apify; this actor is a separate, independent product of PPFTEC S.R.L., not affiliated with the EU or TED). Add it to an MCP client such as Claude, VS Code or Cursor:
{"mcpServers": {"clearsource-ted-tenders": {"url": "https://mcp.apify.com?tools=clearsource/ted-tenders"}}}
On first use your browser asks you to sign in to Apify (OAuth); or send your token as Authorization: Bearer <APIFY_TOKEN>. The assistant reads the input schema, runs the actor and then fetches the dataset items. Runs are billed to your Apify account at the pay-per-event prices above.
Try: "Find new construction tenders (CPV 45000000) in Germany and Austria from the last 7 days." Details of the MCP server: https://docs.apify.com/platform/integrations/mcp (checked 2026-10-10). Step-by-step guide: https://clearsource.ppftec.com/guides/ted-tenders-by-cpv
Guide
Step-by-step guide, including the free manual way: https://clearsource.ppftec.com/guides/ted-tenders-by-cpv
Terms of use
This actor is offered by PPFTEC S.R.L. under Apify's terms plus our terms of use: https://clearsource.ppftec.com/terms
Disclaimer
Data is provided "as is", as published by TED at provenance.retrieved_at. Check the official notice (notice_url) before acting. This is not legal or procurement advice.
Calling via API
curl -X POST "https://api.apify.com/v2/acts/<account>~ted-tenders/run-sync-get-dataset-items?token=$APIFY_TOKEN" \-H 'Content-Type: application/json' \-d '{"countries":["ROU"],"cpvCodes":["72000000"],"maxResults":50}'
<account> is pending D2. The actor can also be called by AI agents through Apify's MCP server once it is published.
Development
npm cinpm run lint && npm run typecheck && npm test && npm run build# Live calls (User-Agent: public-data-api/ted-tenders@<version> (+https://ppftec.com)):PDA_LIVE=1 npm run test:contract # max 3 requests to TED; writes test/contract/out/live-*.jsonPDA_LIVE=1 node scripts/probe-cpv.mjs 72000000 # 1 request: CPV hierarchy checkPDA_HEALTH_TOKEN=<secret> npm run monitor # health run of dist/main.js (token from your local env)# Offline local run with recorded fixtures (ignored on Apify):mkdir -p storage/key_value_stores/defaultecho '{"countries":["ROU"],"maxResults":5}' > storage/key_value_stores/default/INPUT.jsonPDA_REPLAY_FIXTURES=$PWD/test/fixtures CRAWLEE_STORAGE_DIR=./storage node dist/main.js
Environment variables: see .env.example (no secrets are needed).