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.
NEW-T1 (QA re-verification): a run counts as complete (watermark may advance) only if the last page was fully processed; a page cut short by maxResults no longer counts as complete even when TED returns no iteration token. Golden test: one page, 3 notices (2026-10-03, 09-30, 09-28), null token, maxResults: 1, three incremental runs → each notice emitted exactly once.
scripts/check-shared.mjs + npm run check-shared: src/core must be byte-identical across actors/*/src/core (exit 1 on a differing or missing file).
0.1.4 — 2026-10-04 (unpublished)
TED-SEC-08 (re-verification): health mode now needs the maintainer token, same rule as actors/ro-company-status: PDA_HEALTH_ENABLED=1 + secret input healthToken equal (constant time) to the secret env PDA_HEALTH_TOKEN (≥ 32 chars, @pdaHealthToken in .actor/actor.json) + optional PDA_MAINTAINER_USER_ID = APIFY_USER_ID. healthToken is a hidden secret input field and is excluded from the state key.
npm run monitor reads PDA_HEALTH_TOKEN from the local environment, refuses without it and deletes the temporary INPUT.json after the run.
QA-3 closed: User-Agent public-data-api/ted-tenders@0.1.3 (+https://ppftec.com) (operator PPFTEC S.R.L., DECISIONS D3 provisional); fixture recorder uses the same contact.
First live contract test (3 requests, 2026-10-04T11:31:57Z): all fail-level checks green; p50 220 ms, p95 720 ms. Report: docs/qa/ted-tenders-live-2026-10-04.md.
docSha256 pinned to e0cfe4e8…2ff8 (api-v3.yaml, 1,830 fields enum values, all 18 of ours present).
CPV hierarchy answered (1 extra live request, scripts/probe-cpv.mjs): classification-cpv IN (72000000) matches child codes; documented in README Limits.
Live contract test now logs every request (method, URL, User-Agent, start time, status, latency, size) to test/contract/out/.
0.1.2 — 2026-10-04 (unpublished)
Source approved: TED_SOURCE.allowListStatus = 'approved' (Petre, 2026-10-04, DECISIONS D1, TED only). The gate code and its tests stay; tests inject a pending status through RunDeps.sourceStatus.
TED-SEC-07: relative dates are bounded (max 50 years) and every resolved date must lie in [1993-01-01, today + 2 years]; "999999 years" is now an input error instead of a crash.
TED-SEC-08: mode: "health" requires PDA_HEALTH_ENABLED=1 (set by npm run monitor and the maintainer's schedule); otherwise invalid input, no request.
TED-SEC-06: caps of 20 keywords, 100 CPV codes, 40 countries, 2,000-character expertQuery (also in .actor/input_schema.json as maxItems/maxLength); keywords limited to plain words, no AND/OR/NOT or operators.
Security-review observation: notice_url/xml_url must be on ted.europa.eu, otherwise drift.
Dependencies: http-cache-semantics 4.3.0 (fixed release published 2026-10-04) via npm audit fix (no --force); npm audit now reports 0 vulnerabilities; accepted-risk note removed from the README.
README: "Daily Romanian tenders above the EU threshold" example (D7-B, daily TED-RO).
Live calls stay off until the User-Agent has a contact URL (QA-3): test:contract skips and scripts/record-fixtures.mjs refuses until then.
0.1.1 — 2026-10-04 (unpublished)
Fix round for docs/qa/report-ted-tenders-2026-10-04.md and docs/security/ted-tenders-review-2026-10-04.md.
QA-1: incremental watermark advances only when a run processed its whole result set; early stops re-scan the same window next time, and seen is pruned only below the query window (no skipped or re-charged notices).
QA-2: Retry-After honoured in full (120 s cap removed); a wait beyond the run time budget aborts with exit 1 and a clear message.
QA-4: the pending-source gate also refuses local live runs (main, monitor, test:contract) unless PDA_ALLOW_PENDING_SOURCES=1; dry runs and recorded-fixture runs still work offline.
QA-5: kill switch re-checked before every request and retry.
QA-6: redirects are never followed (redirect: 'manual', 3xx fails without retry) and the response host is checked.
QA-7: health doc check uses membership in the OpenAPI fields enum (new dependency yaml), not a substring match.
TED-SEC-01: incremental + expertQuery rejected as invalid input.
TED-SEC-02: per-run caps on scanned notices (max(10 × maxResults, 2,000)) and pages; 4-hour run time budget (also bounded by ACTOR_TIMEOUT_AT); defaultRunOptions.timeoutSecs 14,400 in .actor/actor.json.
TED-SEC-03/09: base image apify/actor-node:24 pinned by digest, USER myuser, npm ci --ignore-scripts in the runtime stage, .dockerignore excludes .env*, .git, docs; engines.node>=22, @types/node 22.
TED-SEC-05/10: basic-ftp overridden to 6.2.1; .npmrc (legacy-peer-deps) deleted; http-cache-semantics recorded as accepted risk in the README.
First build per docs/spec/actors-wave1.md section 2: TED Search API v3 search with ITERATION paging, 18 fixed fields, mapping to ted-notice 1.0.0 with provenance envelope 1.0.0.
Kill switch, allow-list gate (source pending D1), dry run, incremental state, drift detection with DRIFT_REPORT, health mode.
Politeness per docs/compliance/sources.md row e: 1 request/s, at most 500 notices per 6 minutes.